事件生成与匹配#

介绍#

当使用 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")

请参阅“定义流 - 流分组”部分,以了解有关 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 值类型:bool、str、float、int、list、set 和 dict。

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

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 的最后一个事件与匹配语句匹配。使用容器类型参数(如 list、set 或 dict)匹配事件的工作方式如下

列表:

如果事件 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()。如果在 match 语句中用作参数值,它将检查接收到的事件参数是否包含至少一个与定义的模式匹配的项,就像 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

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