跳转至

对话流

对话分支

一个对话结构一般都会设计成一个树状的流结构,这里的每个故事就是一个真正的对话示例,在每个故事的底部,点击 Branch Story 按钮试试看:

:::: tabs

::: tab "Conversation builder"

Branching button

:::

::: tab "Botfront Markdown"

Branching button

:::

::::

以下几个时间可以作为一个对话分支的开始:

  • 每个来自用户的会话(包含意图和实体)
  • slots (有上下文)

根据意图做对话流分支

会话分支最常见的情况是针对不同的意图做不同的分支处理,参考下面这个例子中关于 happysad 两个分支的实现:

:::: tabs

::: tab "Conversation builder"

Branch 1: happy

Branch 2: sad

:::

::: tab "Botfront Markdown"

Branch 1: happy

Branch 2: sad

:::

::: tab "Standard Rasa"

在 Rasa 中,这个逻辑是通过创建两个不同的故事来实现的,这样一来就造成了两个故事的内容有一半是重复的,在类似本例中的这种简单例子中这并不算什么大问题,但当你的故事编的分层、复杂时,Rasa 这种处理方式会让故事本身的维护变得困难

``` md {3} * chitchat.greet - utter_hi_how_are_you * chitchat.i_am_happy - utter_awesome

``` md {3}
* chitchat.greet
  - utter_hi_how_are_you
* chitchat.i_am_sad
  - utter_i_have_a_bad_day_myself

:::

::::

根据实体做对话流的分支

另一种针对对话流分支的情况是针对不同的实体做分支,你可以先看下如何在对话中针对实体做标注.

试想这样一个例子:用户可以要求预定 eco 或者 business 类型,如果没有明确说明的意图由默认的逻辑处理,实现方式如下:

:::: tabs

::: tab "Conversation builder"

Branch 1: book-eco

Branch 2: book-business

Branch 3: book-undecided

:::

::: tab "Botfront Markdown"

Branch 1: book-eco

Branch 2: book-business

Branch 3: book-undecided

:::

::: tab "Standard Rasa"

相应的逻辑在 Rasa 中需要三个不同的故事来处理,可以看到有不少的内容其实是重复的:

``` md {3} * chitchat.greet - utter_hi_how_are_you * book{"class":"eco"} - utter_eco

``` md {3}
* chitchat.greet
  - utter_hi_how_are_you
* book{"class":"business"}
  - utter_business

``` md {3} * chitchat.greet - utter_hi_how_are_you * book - utter_which_class

:::

::::

::: warning 注意!不能这么做
按上面的操作步骤训练并测试一下,你会发现当你输入  `/book` 机器人会像预期一致的返回 `utter_which_class`,但当你输入 `/book{"class":"eco"}` 或者 `/book{"class":"business"}` 时,你会发现回复的内容是随机的!这是因为这个意图的值还没有被保存,Rasa 只针对这个意图的 `class` 是否在用户会话中出现来决定对话的分支。

要让上面的例子起作用,你需要 **创建一个 slot**,在本例中我们创建一个名为 **categorical** 的 slot,然后添加两个分类,分别叫 **business** 和 **eco**,重新训练,然后就一切正常了。

![Categorical slot](../../../images/branching_7.png)

:::

### 根据槽做对话流的分支

一旦你定义了一个跟实体名字一样的槽,所有从用户会话中解析出来的这个实体值都会被填充到这个槽里面,而且这个槽的值在该会话周期中会一直存在,除非它被显式修改或者重置。比如上面的例子中,无论用户说的是 _I want to book in economy_ `book{"class":"eco"}` 还是  _I want to book in business_`book{"class":"business"}`, 你都能根据槽信息进行对话流的分支处理。

**用例**: 用户想取消预订,但只取消 `business` 房间,你就可以想下面这样子实现对话流分支:

:::: tabs

::: tab "Conversation builder"

![Branch 1: cancel-eco](../../../images/branching_cob_8.png)

![Branch 2: cancel-business](../../../images/branching_cob_9.png)

:::

::: tab "Botfront Markdown"

![Branch 1: cancel-eco](../../../images/branching_bf_8.png)

![Branch 2: cancel-business](../../../images/branching_bf_9.png)

:::

::: tab "Standard Rasa"

_In Rasa, you would have to write two additional separate stories:_

``` md {2}
* cancel.booking
  - slot{"class":"eco"}
  - utter_booking_not_cancellable

``` md {2} * cancel.booking - slot{"class":"business"} - utter_booking_canceled

:::

::::

如你所见,`- slot{"class":"..."}` 槽决定了对话流的走向。

::: tip 如果相关的 class 还没有赋值会怎样...
这种情况下你可以添加一个名为 **not_set** 的类别到 `class` 槽里,并把它的值初始化为 **not_set** ,这样,当出现没有设定的类别分支时流程就会走到这里,问题解决!

:::

:::: tabs

::: tab "Conversation builder"

![Branch 3: cancel-notset](../../../images/branching_cob_10.png)

:::

::: tab "Botfront Markdown"

![Branch 3: cancel-notset](../../../images/branching_bf_10.png)

:::

::: tab "Standard Rasa"

``` md {2}
* cancel.booking
  - slot{"class":"not_set"}
  - utter_which_class

:::

::::

对话分支是如何处理的

底层实现上,Botfront 用的是 Rasa 的 checkpoints 机制,当你点击 branch story 的时候,父故事和子故事会被 checkpoints 无缝的连接起来,无需前台做处理。

根据其它特性做对话流分支

在每个故事底下的面包屑导航部分都明确的标明了你当前所在的分支。

Branching breadcrumbs

对分支的添加是没有上限的,你可以根据需要任意添加、重命名分支。

:::: tabs

::: tab "Conversation builder"

Rename branch

:::

::: tab "Botfront Markdown"

Rename branch

:::

::::

如果要删除一个分支,只需简单的点击后面的垃圾桶图标即可:

:::: tabs

::: tab "Conversation builder"

Delete branch

:::

::: tab "Botfront Markdown"

Delete branch

:::

::::

::: tip 注意 删除最后两个分支中的一个同时也会自动删除另一个,另一个分支中的内容会自动添加到父故事中去。 :::

:::: tabs

::: tab "Conversation builder"

Delete last branch

:::

::: tab "Botfront Markdown"

Delete last branch

:::

::::

关联故事

故事之间的连接是解决故事内容重复的一个非常有效的手段,而且为不同的故事创建连接也很简单,你只要简单的在故事栏右下角选一个故事作为目标故事就可以了,任何一个故事都可以被连接到另一个故事或者被其他故事连接,这个特性在一些需要经常重复的对话流中特别有用,比如在很多故事里面,最后都需要加一个 feedback 流。

:::: tabs

::: tab "Conversation builder"

Link story

Story linked

:::

::: tab "Botfront Markdown"

Link story

Story linked

:::

::::

当一个故事被别的故事连接,在这个故事的编辑框顶部会出现一个小黄条,用以提示连接关系。

:::: tabs

::: tab "Conversation builder"

Destination story

:::

::: tab "Botfront Markdown"

Destination story

:::

::::

点一下小黄条会列出所有已连接的故事。

:::: tabs

::: tab "Conversation builder"

Destination story detail

:::

::: tab "Botfront Markdown"

Destination story detail

:::

::::

目标故事在它所连接的故事删除之前不能被删除:

:::: tabs

::: tab "Conversation builder"

Destination story no delete

Initial story no delete

:::

::: tab "Botfront Markdown"

Destination story no delete

Initial story no delete

:::

::::

分支逻辑也会自动被连接到设定的目标故事。

:::: tabs

::: tab "Conversation builder"

Linked branch 1: Good

Linked branch 2: Sad

:::

::: tab "Botfront Markdown"

Linked branch 1: Good

Linked branch 2: Sad

:::

::::

::: tip 过多的使用连接会导致你的故事线变复杂,降低可阅读性。而且过多的使用连接故事还有可能导致训练时间变长。 :::

关联一个故事到它本身

只有为一个故事创建了对话分支之后才可以把这个故事连接到它自身,比如说,你在某个场景里面创建了一个菜单对话,这个菜单的最后一个选项是 "go back" 到该故事逻辑的最开始处。 虽然这样,当我们想把一个故事连接到它自身的时候,这个故事还是需要首先被别的故事连接才行,比如被一个对话流的开场故事作为目标故事,如果不是这样,就无法使用这个特性。

下面是一个带开场故事的典型的示例(菜单逻辑):

Self linking schema

一个简单的自连接故事:

:::: tabs

::: tab "Conversation builder"

Self-linked story

:::

::: tab "Botfront Markdown"

Self-linked story

:::

::::

故事关联的工作原理

跟对话流分支的原理一样,Botfront 也是用 Rasa 的 checkpoints 机制 实现了故事之间的连接,当你点击 Link to 按钮的时候,源故事和目标故事通过 checkpoints 被无缝的连接在一起,有一点需要注意,故事里面不支持 > checkpoints 特性。


最后更新: July 6, 2021