事件生成与匹配

简介

在使用 Colang 时,我们假设有一个公共事件通道,其中包含交互系统中发生的所有相关事件。从 Colang 的角度来看,相关事件是建模用户与机器人之间交互所需的所有事件。使用 Colang,您可以监听此通道上的事件,以及将新事件发布到通道,供其他组件读取

Event channel

公共事件通道,所有组件都可以在其中读取和写入。

使用 Colang,您可以定义一组事件生成规则(交互模式),这些规则取决于通道中的事件。让我们首先看看 Colang 中事件的简单定义

重要提示

事件定义

<EventName>[(param=<value>[, param=<value>]…)]

示例

# Bot utterance action start event
StartUtteranceBotAction(script="Hello", intensity=1.0)

# Event containing the final user utterance
UtteranceUserActionFinished(final_transcript="Hi")

# A custom event with no parameters
XYZ()

事件使用 Pascal 命名风格。

Colang 使用约定,事件名称使用 Pascal 命名风格编写。事件可以具有可选参数,参数之间用逗号分隔,并可能具有默认值。您可以在 Colang 语句中使用这些事件,以构建作为语句序列的交互模式

重要提示

交互模式定义

<statement 1>
<statement 2>
.
.
<statement N>

语句按顺序逐个处理。我们将了解不同类型的语句,从事件生成语句开始

重要提示

事件生成语句定义

send <Event> [as $<event_ref>] [and|or <Event> [as $<event_ref>]…)]

示例

send StartUtteranceBotAction(script="Hello") as $utterance_event_ref

这将在事件通道上生成一个 UMIM 事件,以便其他系统组件再次接收。我们还介绍了事件匹配语句

重要提示

事件匹配语句定义

match <Event> [as $<event_ref>] [and|or <Event> [as $<event_ref>]…)]

示例

match UtteranceUserActionFinished(final_transcript="Hi") as $user_utterance_event_ref

Colang 运行时处理的新事件将与所有活动的事件匹配语句进行比较。如果到目前为止所有之前的语句都已成功处理,则事件匹配语句被认为是活动的。仅当事件名称和事件匹配语句的所有提供的参数都等于已处理的事件时,事件匹配语句才被认为是成功的。否则,匹配语句将继续等待匹配事件。

现在让我们用一个例子来展示这一点

events/event_matching/main.co
flow main
    match Event1() # Statement 1
    send Success1() # Statement 2
    match Event2(param="a") # Statement 3
    send Success2() # Statement 4
> /Event3
> /Event1()
Event: Success1
> /Event2(param="b")
> /Event2(param="a")
Event: Success2

我们看到,只有在收到正确的事件时,匹配语句才会继续进行。现在让我们再运行一次,演示所谓的部分匹配

> /Event1(param="a")
Event: Success1
> /Event2(param="a", other_param="b")
Event: Success2

由此我们可以看出,只要语句中提供的所有参数与事件的参数匹配,即使事件的某些参数在语句中缺失,匹配语句也算作成功。部分匹配被认为是不如指定所有参数的匹配那么具体的匹配。

注意

部分事件匹配是指事件匹配语句未指定事件的所有可用参数的匹配。此类语句匹配一组事件,这些事件的指定参数等于预期值。

我们可以将生成的事件分配给引用,以便稍后访问其属性

events/send_event_reference/main.co
flow main
    send StartUtteranceBotAction(script="Smile") as $event_ref
    send StartGestureBotAction(gesture=$event_ref.script)
    match RestartEvent()
Smile

Gesture: Smile

请注意,我们没有从事件匹配语句开始流程,而是直接生成了一个事件。因此,这两个事件将在开始时立即生成。即使这两个事件是按顺序生成的,但从用户的角度来看,它们可以被认为是并发事件,因为它们几乎在没有时间差的情况下发送出去。我们在末尾添加了一个事件匹配语句,以便主流程不会无限循环。

类似地,任何事件匹配语句都可以借助引用捕获观察到的事件

events/match_event_reference/main.co
flow main
    match UtteranceUserActionFinished() as $event_ref
    send StartUtteranceBotAction(script=$event_ref.final_transcript)
> Hello!

Hello!

通过这种方式,您可以访问事件参数,例如用户话语的最终文本记录,并使用它(例如)让机器人重复用户说的话。

事件分组

Colang 的另一个强大功能是使用关键字 andor 对事件进行分组的选项

events/event_grouping/main.co
flow main
    match UtteranceUserActionFinished(final_transcript="hi") and UtteranceUserActionFinished(final_transcript="you")
    send StartUtteranceBotAction(script="Success1")
    match UtteranceUserActionFinished(final_transcript="A") or UtteranceUserActionFinished(final_transcript="B")
    send StartUtteranceBotAction(script="Success2")
> hi
> you

Success1

> A

Success2

> you
> hi

Success1

> B

Success2

您可以看到,使用 and 组合的事件只有在两个事件都被观察到后才会匹配。另一方面,使用关键字 or 分组的事件将在观察到其中一个事件后立即匹配。通过这种分组,可以使用括号来增强运算符优先级(默认情况下 or 的优先级高于 and),从而构建更复杂的事件匹配条件

events/event_grouping_advanced/main.co
flow main
    match ((UtteranceUserActionFinished(final_transcript="ok") or UtteranceUserActionFinished(final_transcript="sure"))
            and GestureUserActionFinished(gesture="thumbs up"))
        or ((UtteranceUserActionFinished(final_transcript="no") or UtteranceUserActionFinished(final_transcript="not sure"))
            and GestureUserActionFinished(gesture="thumbs down"))
    send StartUtteranceBotAction(script="Success")
> ok
> /GestureUserActionFinished(gesture="thumbs up")

Success

> no
> /GestureUserActionFinished(gesture="thumbs down")

Success

> sure
> /GestureUserActionFinished(gesture="thumbs down")

Success

> not sure
> /GestureUserActionFinished(gesture="thumbs down")

Success

重要提示

请注意,如何通过使用适当的缩进将组拆分为多行,以更好地可视化子分组。

我们还可以使用分组运算符 andor 来生成事件

and 运算符等效于创建一系列 send 语句,其中生成两个事件。

# This statement ...
send StartUtteranceBotAction(script="Hi") and StartGestureBotAction(gesture="Wave")
# ... is equivalent to the following sequence
send StartUtteranceBotAction(script="Hi")
send StartGestureBotAction(gesture="Wave")

or 运算符的工作方式类似于随机选择器,它只会选择要发送的事件之一

# This statement ...
send StartGestureBotAction(gesture="Ping") or StartGestureBotAction(gesture="Pong")
# ... will be evaluated at runtime as one of these two options (at random)
# Option 1:
send StartGestureBotAction(gesture="Ping")
# Option 2:
send StartGestureBotAction(gesture="Pong")

请参阅 定义 Flows - Flow Grouping 章节,了解有关 or 分组的底层机制的更多信息。

这是一个展示分组运算符的示例

events/event_groups/main.co
flow main
    send StartUtteranceBotAction(script="Hi") and StartGestureBotAction(gesture="Wave")
    send StartGestureBotAction(gesture="Ping") or StartGestureBotAction(gesture="Pong")
    match RestartEvent()

您可以自己尝试迭代几次。

Hi

Gesture: Wave
Gesture: Ping

> /RestartEvent

Hi

Gesture: Wave
Gesture: Ping

> /RestartEvent

Hi

Gesture: Wave
Gesture: Pong

参数类型

Colang 支持许多基本的 Python 值类型:boolstrfloatintlistsetdict

这是一个基于整数参数的事件匹配的简单示例

events/integer_parameter_match/main.co
flow main
    match Event(param=42)
    send StartUtteranceBotAction(script="Success")
> /Event()
> /Event(param=3)
> /Event(param=42)

Success

我们看到,只有参数等于 42 的最后一个事件与匹配语句匹配。使用容器类型参数(如 listsetdict)匹配事件的工作方式如下

列表:

事件 Event(list_param=<实际列表>) 带有列表参数 list_param,如果满足以下条件,则匹配匹配语句 match Event(list_param=<预期列表>)

  • 列表 <预期列表> 的长度等于或小于接收到的列表 <实际列表> 的长度,后者是接收到的事件的一部分。

  • <预期列表> 中的所有项都与 <实际列表> 中的对应项匹配。比较列表中相同位置的项。如果某项本身是容器,则将根据该容器类型的规则递归检查它。

在以下示例中,主流程包含一个期望匹配事件 Event 的匹配语句。

events/list_parameters/main.co
flow main
    match Event(param=["a","b"])
    send StartUtteranceBotAction(script="Success")

使用一些输入事件运行此流程会得到以下序列

> /Event(param=["a"])
> /Event(param=["b","a"])
> /Event(param=["a","b","c"])

Success
  • 第一个事件不匹配,因为预期列表的项更多。

  • 第二个事件不匹配,因为预期列表中的顺序不同

  • 第三个事件匹配,因为两个列表的所有项都匹配(在相同的位置)

集合:

事件 Event(set_param=<实际集合>) 带有集合参数 set_param,如果满足以下条件,则匹配匹配语句 match Event(set_param=<预期集合>)

  • 集合 <预期集合> 的大小等于或小于接收到的事件的接收到的集合 <实际集合> 的大小。

  • <预期集合> 中的所有项都与 <实际集合> 中的项匹配。<预期集合> 中的项将与 <预期集合> 中的所有项进行比较,直到找到匹配项或未找到匹配项为止。如果某项本身是容器,则将根据该容器类型的规则递归检查它。

在以下示例中,主流程包含一个期望匹配事件 Event 的匹配语句。

events/set_parameters/main.co
flow main
    match Event(param={"a","b"})
    send StartUtteranceBotAction(script="Success")

使用一些输入事件运行此流程会得到以下序列

> /Event(param={"a"})
> /Event(param={"b","a","c"})

Success
  • 第一个事件不匹配,因为预期集合的项更多。

  • 第二个事件匹配,因为所有预期项都可用(顺序无关紧要)

字典:

事件 Event(dict_param=<实际字典>) 带有字典参数 dict_param,如果满足以下条件,则匹配匹配语句 match Event(dict_param=<预期字典>)

  • 字典 <预期字典> 的大小等于或小于接收到的事件的接收到的字典 <实际字典> 的大小

  • <预期字典> 中的所有可用字典项都与 <实际字典> 中的对应项匹配。项根据其键和值进行比较。如果某个值本身是容器,则将根据该值类型的规则递归检查它

在以下示例中,主流程包含一个期望匹配事件 Event 的匹配语句。

events/dictionary_parameters/main.co
flow main
    match Event(param={"a": 1})
    send StartUtteranceBotAction(script="Success")

使用一些输入事件运行此流程会得到以下序列

> /Event(param={"a": 2})
> /Event(param={"b": 1})
> /Event(param={"b": 1, "a": 1})

Success
  • 第一个事件不匹配,因为项 “a” 的值与预期项值不同

  • 第二个事件不匹配,因为它中没有键值为 “a” 的项

  • 第三个事件匹配,因为所有预期项在其中都可用

正则表达式

此外,Colang 还支持 Python 正则表达式用于事件参数匹配,使用 Colang 函数 regex()。如果在匹配语句中用作参数值,它将检查接收到的事件参数是否包含与定义的模式的至少一个匹配项,就像 Python 的 re.search(pattern, parameter_value) 中一样

events/regular_expression_parameters/main.co
flow main
    match Event(param=regex("(?i)test.*"))
    send StartUtteranceBotAction(script="Success 1")
    match Event(param=regex("1\d*0"))
    send StartUtteranceBotAction(script="Success 2")
    match Event(param=["a",regex(".*"),"b"])
    send StartUtteranceBotAction(script="Success 3")
> /Event(param="Test123")

Success 1

> /Event(param=123450)

Success 2

> /Event(param=["a", "0", "b"])

Success 3

有了这个,您现在可以构建非常强大的匹配模式!