事件生成与匹配#

引言#

使用 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()

事件使用帕斯卡命名法。

Colang 约定事件名称使用帕斯卡命名法书写。事件可以有可选参数,参数之间用逗号分隔,并可以有默认值。您可以在 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 运算符等同于创建一系列发送语句,其中两个事件都将被生成。

# 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")

请参阅 定义流 - 流分组 部分,了解更多关于 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 等容器类型参数匹配事件的方式如下:

列表 (List):

事件 Event(list_param=<actual list>),其列表参数为 list_param,与匹配语句 match Event(list_param=<expected list>) 匹配,如果

  • 列表 <expected list> 的长度等于或小于作为接收事件一部分的接收列表 <actual list> 的长度。

  • <expected list> 中的所有项与 <actual list> 中的对应项匹配。比较列表中相同位置的项。如果一个项本身是一个容器,则会根据该容器类型的规则进行递归检查。

在以下示例中,主流程包含一个期望事件 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
  • 第一个事件不匹配,因为期望的列表有更多项。

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

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

集合 (Set):

事件 Event(set_param=<actual set>),其集合参数为 set_param,与匹配语句 match Event(set_param=<expected set>) 匹配,如果

  • 集合 <expected set> 的大小等于或小于接收事件的接收集合 <actual set> 的大小。

  • <expected set> 中的所有项与 <actual set> 中的一个项匹配。<expected set> 中的项将与 <actual set> 中的所有项进行比较,直到找到匹配项或未找到。如果一个项本身是一个容器,则会根据该容器类型的规则进行递归检查。

在以下示例中,主流程包含一个期望事件 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
  • 第一个事件不匹配,因为期望的集合有更多项。

  • 第二个事件匹配,因为所有期望的项都存在(顺序无关紧要)

字典 (Dictionary):

事件 Event(dict_param=<actual dictionary>),其字典参数为 dict_param,与匹配语句 match Event(dict_param=<expected dictionary>) 匹配,如果

  • 字典 <expected dictionary> 的大小等于或小于接收事件的接收字典 <actual dictionary> 的大小

  • <expected dictionary> 中所有存在的字典项与 <actual dictionary> 中的对应项匹配。项根据其键和值进行比较。如果一个值本身是一个容器,则会根据该值类型的规则进行递归检查。

在以下示例中,主流程包含一个期望事件 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 还支持使用 Colang 函数 regex() 进行事件参数匹配的 Python 正则表达式。如果在匹配语句中用作参数值,它将检查接收到的事件参数是否包含与定义模式至少一个匹配项,就像 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

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