【Rasa】入门案例学习

Rasa初体验--构建对话机器人

NLU数据

python 复制代码
version: "3.1"

nlu:
  - intent: greet
    examples: |
      - Hi
      - Hey!
      - Hello
      - Good day
      - Good morning

  - intent: subscribe
    examples: |
      - I want to get the newsletter
      - Can you send me the newsletter?
      - Can you sign me up for the newsletter?

  - intent: inform
    examples: |
      - My email is example@example.com
      - random@example.com
      - Please send it to anything@example.com
      - Email is something@example.com

格式概述

开源Rasa使用YAML作为一种统一且可扩展的方式来管理所有训练数据,包括NLU数据、故事和规则。

你可以将训练数据拆分为任意数量的YAML文件,每个文件可以包含NLU数据、故事和规则的任意组合。训练数据解析器使用顶级键来确定训练数据类型。

领域使用与训练数据相同的YAML格式,也可以扩多个文件拆分或合并到一个文件中。领域包括响应和表单的定义。

领域

领域定义了对话机器人的全部操作。它指定对话机器人的意图、实体、槽、响应、表单和动作。它还定义了对话会话的配置。

多个领域文件

领域可以定义为单个YAML文件,也可以拆分为目录中的多个文件。

当拆分为多个文件的时候,领域内容可被直接读取并自动合并在一起。

你可以通过运行如下命令在命令行界面中训练具有拆分领域文件的模型:

python 复制代码
rasa train --domain path_to_domain_directory
意图

领域文件中的intents键列出了NLU数据和对话训练数据中使用的所有意图。

忽略某些意图的实体

要忽略某些实体或者显示地仅考虑某些实体,你可以使用如下语法:

python 复制代码
intents:
- greet:
    use_entities:
        - name
        - first_name
- farewall:
    ignore_entities:
        - location
        - age
        - last_name

对于任何单一意图,只能是use_entities或ignore_entities

这些意图的排除实体将不被特征化,因此不会影响下一个动作的预测,当你有一个不关心被提取的实体的意图时,这很有用。

实体

实体部分列出了可以由NLU管道中的任何实体提取器提取的所有实体。

python 复制代码
entities:
    - PERSON
    - time
    - membership_type
    - priority

当使用多个领域文件时,可以在任何领域文件中指定实体,并可以在任何领域文件中被任何意图使用或忽略。

如果使用实体角色和分组功能,你还需要在本节中列出实体的角色和组。

例如:

python 复制代码
entities:
    - city:
        roles:
            - from
            - to
    - topping:
        groups:
            - 1
            - 2
    - size:
        groups:
            - 1
            - 2

默认情况下,实体会影响动作预测。为了防止提取到的实体影响特定意图的对话,你可以忽略某些意图的实体。

要忽略所有意图的实体,不必在每个意图的ignore_entities标志下列出,可以在实体下将influence_conversation设置为false

python 复制代码
entities:
- location:
    influence_conversation: false

此语法与将实体添加到领域中每个意图的ignore_entities列表具有相同的效果

显式设置influence_conversation: true 不会改变任何行为,因为这是默认设置

槽式对话机器人的记忆。它作为键值存储用于存储用户提供的信息(例如:家乡)以及收集的有关外部的信息(例如:数据库查询结果)

槽在领域的slots部分中定义,包括它们的名称、类型和他们是否以及如何影响对话机器人的行为。

以下示例定义了一个名为"slot_name"的槽,类型为text,预定义槽映射from_entity

python 复制代码
slots:
    slot_name:
        type: text
    mappings:
    - type: from_entity
      entity: entity_name
槽和对话行为

你可以使用influence_conversation属性来指定槽是否影响对话

如果你想在槽中存储信息而不影响对话,请在定义槽时设置influence_conversation:false

以下示例定义了一个age槽,它将存储有关用户年龄的信息,但不会影响对话的流程。这意味着对话机器人每次预测下一个动作的时候都会忽略槽的值。

python 复制代码
slots:
    age:
        type: text
        influence_conversation: false
槽类型

①文本类型槽

类型:text

用途:存储文本值

示例:

python 复制代码
slots:
    cuisine:
        type: text
        mappings:
        - type: from_entity
          entity: cuisine

描述:如果influence_conversation设置为true,对话机器人的行为将根据slot是否设置而改变。但是不同的文本不会进一步影响对话,这意味着如下两个故事是相等的:

python 复制代码
stories:
- story: French cuisine
  steps:
    - intent: inform
    - slot_was_set:
        - cuisine: frech

- story: Vietnamese cuisine
  steps:
    - intent: inform
    - slot_was_set:
        - cuisine: vietnamese

②布尔类型槽

类型:bool

用途:存储true或false值

示例:

python 复制代码
slots:
    is_authenticated:
        type: bool
        mappings:
            - type: custom

③分类型槽

类型:categorical

用途:存储N个可选值之一的槽

示例:

python 复制代码
slots:
    risk_level:
        type: categorical
        values:
            - low
            - medium
            - high
        mappings:
            - type: custom

描述:other__会自动添加到用户定义的值中。所有遇到的未在槽值中明确定义的值都映射到__other。__other__不应用作用户定义的值,负责它将作为所有未见过值映射到的默认值。

④浮点类型槽

类型:float

用途:存储实数

示例:

python 复制代码
slots:
    temperature:
        type: float
        min_value: -100.0
        max_value: 100.0
        mappings:
        - type: custom

描述:如果该值介于min_value和max_value之间,则使用数字指定的值。所有低于min_value的值将被视为min_value,所有高于max_value的值将被视为max_value。

⑤列表类型槽

类型:list

用途:存储列表值

示例:

python 复制代码
slots:
    shopping_items:
        type: list
        mappings:
        - type: from_entity
          entity: shopping_item

描述:如果influence_conversation设置为true,对话机器人的行为将根据列表是否为空而改变。存储在槽中的列表长度不会影响对话。只有列表长度是零还是非零才重要。

⑥任意类型槽

类型:any

用途:存储任意数据(可以为任意类型,例如:词典或列表)

示例:

python 复制代码
slots:
    shopping_items:
        type: any
        mappings:
        - type: custom

描述:any类型槽在对话期间总是被忽略。对于any类型槽,influence_conversation属性不能设置为true。如果要存储影响对话的自定义数据结构,请使用自定义槽类型。

⑦自定义槽

也许你的餐厅预订系统最多只能处理6个人的预订。在这种情况下,你希望槽的值影响下一个选定的动作。你可以通过自定义槽类型来做到这一点。

下面代码定义了一个名为NumberOfPeopleSlot的自定义槽类型。特征化定义了如何将槽的值转换为向量,以便开源Rasa机器学习模型可以处理它。NumberOfPeopleSlot有三个可能的值,可以用长度为2的向量表示。

(0,0):未设置

(1,0):介于1和6之间

(0,1):多于6

定义my_custom_slots.py如下:

python 复制代码
from rasa.shared.core.slots import Slot

class NumberOfPeopleSlot(Slot):

    def feature_dimensionality(self):
        return 2

    def as_feature(self):
        r = [0.0] * self.feature_dimensionality()
        if self.value:
            if self.value <= 6:
                r[0] = 1.0
            else:
                r[1] = 1.0
        return r

你可以将自定义槽类型作为独立的Python模块实现,与自定义动作代码分开。将自定义槽的代码保存在名为__init__.py的空文件同级目录中,以便将其识别为Python模块。然后,你可以通过其他模块引用自定义槽类型。

例如,假设你已将上面的代码保存在addons/my_custom_slots.py中,这是一个与你的对话机器人相关的目录:

则你的自定义槽类型的模块路径为addons.my_custom_slots.NumberOfPeopleSlot:

python 复制代码
slots:
    people:
        type: addons.my_custom_slots.NumberOfPeopleSlot
        influence_conversation: true
        mappings:
        - type: custom
槽映射

开源Rasa带有4个预定义的映射,用于根据最新的用户消息填充槽。

除了预定义的映射,你还可以定义自定义槽映射。所有自定义槽映射都应该包含custom类型的映射。

槽映射被指定为领域文件中mappings键下的YAML字典列表。槽映射按照他们在领域中列出的书序排列优先级,第一个找到的槽映射将用于填充槽。

默认行为是在每个用户消息之后应用槽映射,而不考虑对话上下文。

请注意,你还可以为可选参数intent和not_intent定义意图列表。

①from_entity

from_entity槽映射根据提取的实体填充槽。需要如下参数:

entity:用于填充槽的实体

如下参数是可选的,可用于进一步指定映射何时应用:

intent:仅在预测此意图时应用映射

not_intent:预测此意图时不应用映射

role:仅当提前的实体具有此角色时才应用映射

group:仅当提取的实体数据有该组时才应用映射

python 复制代码
entities:
- entity_name
slots:
    slot_name:
        type: any
        mappings:
        - type: from_entity
          entity: entity_name
          role: role_name
          group: group_name
          intent: intent_name
          not_intent: excluded_intent

②from_text

from_text映射将使用最后一个用户消息的文本来填充slot_name槽。如果intent_name为None,则无论意图名称如何,都会填充槽。否则,只有当用户的意图时intent_name是才会填充槽。

如果消息的意图时excluded_intent,则槽映射将不适用。

python 复制代码
slots:
    slot_name:
        type: text
        mappings:
        - type: from_text
          intent: intent_name
          not_intent: excluded_intent

③from_intent

如果用户意图是intent_name,则from_intent映射将使用my_value值填充slot_name槽。如果你选择不指定参数意图,则只要未在not_intent参数下列出该意图,则无论消息的意图如何,都将应用槽映射。

如下参数是必须的:

value:填充slot_name槽的值

如下参数是可选的,可用于进一步指定槽映射将何时应用:

intent

not_intent

python 复制代码
slots:
    slot_name:
        type: any
        mappings:
        - type: from_intent
          value: my_value
          intent: intent_name
          not_intent: excluded_intent

④from_trigger_intent

如果表单具有intent_name意图的用户消息激活,则from_trigger_intent映射将使用my_value值填充slot_name槽。如果消息的意图是excluded_intent,则槽映射将不适用。

python 复制代码
slots:
    slot_name:
        type: any
        mappings:
        - type: from_trigger_intent
          value: my_value
          intent: intent_name
          not_intent: excluded_intent
映射条件

要仅在表单的上下文中应用槽映射,请在槽映射的conditions键中指定表单的名称。即在active_loop键中列出映射适用的表单名称。

条件还可以包括请求槽的名称。如果requested_slot未提及,如果提取了相关信息,则将设置该槽,而不管表单正在请求哪个槽。

python 复制代码
slots:
    slot_name:
        type: text
        mappings:
        - type: from_text
          intent: intent_name
          conditions:
          - active_loop: your_form
            requested_slot: slot_name
          - active_loop: another_form

①自定义槽映射

当预定义映射不适合你的用例时,可以使用到槽验证动作来定义自定义槽映射。你必须将此槽映射定义为custom类型,例如:

python 复制代码
slots:
    day_of_week:
        type: text
        mappings:
        - type: custom
          action: action_calculate_day_of_week

你还可以使用custom槽映射来列出会话过程中将由任意自定义动作填充的槽,方法是列出类型而不列出特定动作。例如:

python 复制代码
slots:
    handoff_completed:
        type: boolean
        mappings:
        - type: custom

此槽不会在每个用户轮次时更新,但只会在预测为其返回SlotSet事件的自定义动作时更新。

②初始槽值

你可以为领域文件中的槽提供初始值:

python 复制代码
slots:
    num_fallbacks:
        type: float
        initial_value: 0
        mappings:
        - type: custom

高层级结构

每个文件可以包含一个或多个带有训练数据的键,一个文件可以包含多个键,但每个键在单个文件中只能出现一次。可用的键有:

version

nlu

stories

rules

你应该在所有YAML训练数据文件中指定version键。

示例:

python 复制代码
version: "3.1"


nlu:
- intent: greet
  examples: |
    - Hey
    - Hi
    - hey there [Sara](name)

- intent: faq/language
  examples: |
    - What language do you speak?
    - Do you only handle english?


stories:
- story: greet and faq
  steps:
    - intent: greet
    - action: utter_greet
    - intent: faq
    - action: utter_faq


rules:
- rule: Greet user
  steps:
    - intent: greet
    - action: utter_greet

要指定测试故事,你需要将其放在一个单独的文件中:(tests/test_stories.yml)

python 复制代码
stories:
- story: greet and ask language
  steps:
    - user: |
        hey
      intent: greet
    - action: utter_greet
    - user: |
        what language do you speak
      intent: faq/language
    - action: utter_faq

测试故事使用与故事相同的格式,除了用户消息步骤可以包含一个user用来指定用户消息的实际文本标注实体。

NLU训练数据

NLU训练数据由按意图分类的用户话语样本组成。训练样本还可以包括实体。实体是从用户信息中提取的结构化信息。你还可以在训练数据中添加额外的信息,例如正则表达式和查找表,来帮助模型正确的识别意图和实体。

NLU训练数据在nlu键下定义。可以在此键下添加的项目有:

①按用户意图分组的训练样本,例如可选带标注的实体

python 复制代码
nlu:
- intent: check_balance
  examples: |
    - What's my [credit](account) balance?
    - What's the balance on my [credit card account]{"entity":"account","value":"credit"}

②同义词

python 复制代码
nlu:
- synonym: credit
  examples: |
    - credit card account
    - credit account

③正则表达式

python 复制代码
nlu:
- regex: account_number
  examples: |
    - \d{10,12}

④查找表

python 复制代码
nlu:
- lookup: banks
  examples: |
    - JPMC
    - Comerica
    - Bank of America

训练样本

训练样本按照意图分组并列在examples键下。通常会在每一行列出一个样本,如下所示:

python 复制代码
nlu:
- intent: greet
  examples: |
    - hey
    - hi
    - what's up

但是,如果有自定的NLU组件并且需要样本元数据,也可以使用扩展格式:

python 复制代码
nlu:
- intent: greet
  examples: 
    - text: |
        hi
      metadata:
        sentiment: neutral
    - text: |
        hey there!

metadata键可以包含任意键值数据,这些数据与样本相关联并可被NLU管道中的组件访问。

在上面的示例中,情感元数据可以被管道中的自定义组件用于情感分析。

你还可以再意图级别指定此元数据。在这种情况下,metadata键的内容将传递给每个意图样本。

python 复制代码
nlu:
- intent: greet
  metadata:
    sentiment: neutral
  examples:
  - text: |
        hi
  - text: |
        hey there!

如果你想制定检索意图,则NLU样本如下:

python 复制代码
nlu:
- intent: chitchat/ask_name
  examples: |
    - What is your name?
    - May I know your name?
    - What do people call you?
    - Do you have a name for yourself?

- intent: chitchat/ask_weather
  examples: |
    - What's the weather like today?
    - Does it look sunny outside today?
    - Oh, do you mind checking the weather for me please?
    - I like sunniy days in Berlin.

所有检索意图都添加了一个后缀用于标识对话机器人的特定响应键。在上面的例子中,ask_nameask_weather 是后缀。后缀与检索意图名称由 / 分隔。

实体

实体是从用户消息中提取的结构化信息。

在训练样本中实体采用实体名称进行标注。除了实体名称之外,你还可以使用同义词,角色和分组来标注实体。

在训练样本中,实体标注如下所示:

python 复制代码
- intent: check_balance
  examples: |
    - how much do I have on my [savings](account) account
    - how much money is in my [checking]{"entity": "account"} account
    - What's the balance on my [credit card account]{"entity":"account","value":"credit"}

标注一个实体的完整语法为:

python 复制代码
[<entity-text>]{"entity":"<entity name>","role":"<role name>","group":"<group name>","value":"<entity synonym>"}

role、group、value可选,value字段表示同义实体。

同义词

同义词通过将提取的实体映射到一个值而非提取的文字来规范化训练数据。

python 复制代码
nlu:
- synonym: credit
  examples: |
    - credit card account
    - credit account

你还可以通过指定实体的值在训练样本中定义同义词:

python 复制代码
nlu:
- intent: check_balance
  examples: |
    - how much do I have on my [credit card account]{"entity":"account","value":"credit"}
    - how much do I owe on my [credit account]{"entity":"account","value":"credit"}

正则表达式

你可以使用RegexFeaturizer和RegexEntityExtractor组件用正则表达式来改进意图分类和实体提取

定义正则表达式的格式如下:

python 复制代码
nlu:
- regex: account_number
  examples: |
    - \d{10,12}

这里account_number是正则表达式的名称。

当用作RegexFeaturizer的特征时,正则表达式的名称无关紧要。

使用RegexEntityExtractor时,正则表达式的名称应与你要提取的实体名称匹配。

查找表

查找表适用于生成不区分大小写的正则表达式模式的单词列表。格式如下:

python 复制代码
nlu:
- lookup: banks
  examples: |
    - JPMC
    - Bank of America

当你在训练数据中提供查找表时,该表的内容将组合成一个大的正则表达式。此正则表达式用于检查每个训练样本以查看它是否包含查找表中词条的匹配项。

查找表正则表达式的处理方式与直接在训练数据中指定正则表达式相同,可以与RegexFeaturizer和RegexEntityExtractor一起使用。查找表的名称受制于正则表达式功能名称相同的约束。

对话训练数据

故事和规则都是用户和机器人之间对话的表示。它们用于训练对话管理模型。故事用于训练机器学习模型,来识别对话中的模式并泛化至未见过的对话路径。规则描述了应该始终遵循相同路径并用于训练RulePolicy的小段对话。

故事

故事由以下部分组成:

story:故事的名称。名称可以为任意值,不用做训练。

metadata:任意且可选,不用于训练。你可以使用它来存储有关数据的相关信息,例如:作者。

steps列表:构成故事的用户消息和动作。

python 复制代码
stories:
- story: Greet the user
  metadata: 
    author: Somebody
    key: value
  steps:
    - intent: greet
    - action: utter_greet

每个步骤可为如下之一:

1.一个用户消息,由意图和实体表示

2.一个or语句,包含两个或多个用户消息

3.一个动作

4.一个表单

5.一个设置事件的槽

6.一个检查点,其将故事与另一个故事相连

用户消息

所有用户消息都会指定intent键和一个可选的entities键

在编写故事时,你不必处理用户发送的消息的具体内容。相反,你可以利用NLU管道的输出,其使用意图和实体的组合来表示与用户发送的具有相同含义的所有可能消息

用户消息遵循如下格式:

python 复制代码
stories:
- story: user message structure
  steps:
    - intent: intent_name
      entities:
        - entity_name: entity_value
    - action: action_name

例如,要表达 I want to check my credit balance这句话,且其中credit为一个实体:

python 复制代码
stories:
- story: story with entities
  steps: 
    - intent: account_balance
      entities:
        - account_type: credit
    - action: action_credit_account_balance

此处包含实体很重要,因为策略会根据意图和实体的组合来学习预测下一个动作(但是你可以使用use_entities属性来改变此行为)

动作

对话机器人执行的所有动作都使用action键指定,后跟动作的名称。在编写故事时,你会遇到两种类型的动作:

响应:以utter_开头并向用户发送特定消息。例如:

python 复制代码
stories:
- story: story with a response
  steps:
    - intent: greet
    - action: utter_greet

自定义动作:以action_开头,运行任意代码并发送任意数量的消息(或不发送)

python 复制代码
stories:
- story: story with a custom action
  steps:
    - intent: feedback
    - action: action_store_feedback
表单

表单是一种特定类型的自定义动作,其中包含在一组所需槽并要求用户提供此信息上的循环逻辑。你可以在领域的forms部分定义一个表单。一旦定义,你应该指定表单的预期路径作为规则。

你应该在故事中包含表单的中断或其他非预期路径,以便模型可以泛化至未见过的对话序列。作为故事的一个步骤,表单采用以下格式:

python 复制代码
stories:
- story: story with a form
  steps:
    - intent: find_restaurant
    - action: restaurant_form
    - active_loop: restaurant_form
    - active_loop: null
    - action: utter_restaurant_found

action步骤激活表单后开始在所需的槽上执行循环。active_loop:restaurant_form步骤表示当前有一个活动表单。与slot_was_set步骤相似,form步骤不会将表单设置为活动状态,而是指示它应该已经被激活。同样,active_loop:null步骤表示在采取后续步骤之前不应该激活任何表单。

表单可以被中断并保持活动状态。在这种情况下,中断应该出现在action: <form_to_activate>步骤之后,然后是active_loop: <active form>步骤。

表单的中断如下所示:

python 复制代码
stories:
- story: interrupted food
  steps: 
    - intent: request_restaurant
    - action: restaurant_form
    - intent: chitchat
    - action: utter_chitchat
    - active_loop: restaurant_form
    - active_loop: null
    - action: utter_slots_values

槽事件通过slot_was_set键进行指定,同时带有槽的名称和可选的槽值

槽充当机器人的记忆。槽由默认动作action_extract_slots根据领域中指定的槽映射设置,或由自定义动作设置。它们在slot_was_set步骤中被故事引用。例如:

python 复制代码
stories:
- story: story with a slot
  steps:
    - intent: celebrate_bot
    - slot_was_set:
        - feedback_value: positive
    - action: utter_yay

这意味着故事要求feedback_value槽的值为positive,对话才能够继续

是否需要包含槽的值取决于槽类型以及该值是否可以或应该影响对话。如果值无关紧要,例如:text槽,你可以只列出槽的名称:

python 复制代码
stories:
- story: story with a slot
  steps:
    - intent: greet
    - slot_was_set:
        - name
    - action: utter_greet_user_by_name

默认情况下,任何槽的初始值为null,你可以使用它来检查槽是否未设置:

python 复制代码
stories:
- story: Frech cuisine
  steps:
    - intent: inform
    - slot_was_set:
        - cuisine: null

槽如何工作:

故事并不设置槽。如果槽映射适用,则槽必须由默认动作action_extract_slots设置,或者在slot_was_set步骤之前由自定义动作设置。

检查点

检查点使用checkpoint键指定,可以在故事的开头或结尾。

检查点是将故事连接在一起的方式。它们可以是故事的第一步,也可以是最后一步。如果是故事的最后一步,则将该故事与其他故事相连,该故事以训练模型时的同名检查点开始。

如下是一个检查点结尾,以及以相同检查点开始的故事示例:

python 复制代码
stories:
- story: story_with_a_checkpoint_1
  steps:
    - intent: greet
    - action: utter_greet
    - checkpoint: greet_checkpoint

- story: story_with_a_checkpoint_2
  steps:
    - checkpoint: greet_checkpoint
    - intent: book_flight
    - action: action_book_flight

故事开头的检查点可以以设置的槽为条件,例如:

python 复制代码
stories:
- story: story_with_a_conditional_checkpoint
  steps:
    - checkpoint: greet_checkpoint
      #This checkpoint should only apply if slots are set to the specified value
      slot_was_set:
        - context_scenario: holiday
        - holiday_name: thanksgiving
    - intent:greet
    - action: utter_greet_thanksgiving

检查点可以帮助简化训练数据并减少其中的冗余,但不要过度使用它们。

使用大量的检查点会让故事难以理解。

如果在不同的故事中经常重复一系列步骤,则使用检查点是有意义的。但是没有检查点的故事更容易阅读和编写。

或语句

or步骤是以相同的方式处理多个意图或槽事件的方法,同时无需为每个意图编写单独的故事。

例如,如果你要求用户确认某事,你可能希望以相同的方式处理affirm和thankyou意图。带有or步骤的故事在训练时转换为多个单独的故事。

例如,以下故事将在训练时转换为两个故事:

python 复制代码
stories:
- story: story with OR
  steps:
    - intent: signup_newsletter
    - action: utter_ask_confirm
    - or:
        - intent: affirm
        - intent: thankyou
    - action: action_signup_newsletter

你还可以将or语句与槽事件一起使用。如下意味着故事需要name槽被设置的值为joe或bob。这个故事将在训练时被转换为两个故事:

python 复制代码
stories:
- story: story with or
  steps:
    - intent: greet
    - action: utter_greet
    - intent: tell_name
    - or:
        - slot_was_set:
            - name: joe
        - slot_was_set:
            - name: bob
    # ...next actions

注意:过度使用检查点和或语句会减慢训练速度。

规则

规则列在rules键下,看起来同故事类似。规则也有一个steps键,其中包含与故事相同的步骤列表。

规则还可以包含conversation_started和condition键。这些用于指定规则适用的条件。

一个带有条件的规则如下所示:

python 复制代码
rules:
- rule: only say "hey" when the user provided a name
  condition:
    - slot_was_set:
        - user_provided_name: true
  steps:
    - intent: greet
    - action: utter_greet

测试故事

测试故事用于检查一个消息是否被正确的进行分类和动作预测。

测试故事使用与故事相同的格式,除了用户消息步骤可以包含一个user用来指定用户消息的实际文本和标注实体。

如下是一个测试故事的例子:

python 复制代码
stories:
- story: A basic end-to-end test
  steps:
    - user: |
        hey
      intent: greet
    - action: utter_ask_howcanhelp
    - user: |
        show me [chinese]{"entity":"cuisine"} restaurant
      intent: inform
    - action: utter_ask_location
    - user: |
        in [Paris]{"entity":"location"}
      intent: inform
    - action: utter_ask_price

端到端训练

通过端到端训练,你不必处理NLU管道提取的消息的特定意图。相反,你可以使用user键将用户消息的文本直接放在故事中。

这些端到端的用户消息遵循如下格式:

python 复制代码
stories:
- story: user message structure
  steps:
    - user: the actual text of the user message
    - action: action_name

此外,你可以添加可由TED策略提取的实体标签。实体标签的语法与NLU训练数据中的语法相同。

例如,一下故事包含用户消息I can always go for sushi。通过使用NLU训练数据中的语法[sushi](cuisine),你可以将sushi标记为cuisine类型的一个实体。

python 复制代码
stories:
- story: story with entities
  steps:
    - user: I can always go for [sushi](cuisine)
    - action: utter_suggest_cuisine

同样,你可以将对话机器人消息直接放在故事中,方法是使用bot键,后面为机器人的消息文本。

包含机器人消息的故事示例如下:

python 复制代码
stories:
- story: story with an end-to-end response
  steps:
    - intent: greet
      entities: 
        - name: Ivan
    - bot: Hello,a person with a name!

你还可以设置一个混合的端到端的故事:

python 复制代码
stories:
- story: full end-to-end story
  steps:
  - intent: greet
    entities:
    - name: Ivan
  - bot: Hello, a person with a name!
  - intent: search_restaurant
  - action: utter_suggest_cuisine
  - user: I can always go for [sushi](cuisine)
  - bot: Personally, I prefer pizza, but sure let's search sushi restaurants
  - action: utter_suggest_cuisine
  - user: Have a beautiful day!
  - action: utter_goodbye

Rasa端到端训练与标准Rasa完全集成。这意味着你可以混合故事,其中一些步骤由动作或意图定义,其他步骤由用户消息或对话机器人响应直接定义。

响应

python 复制代码
responses:
    utter_greet:
        - text: |
            Hello! How can I help you?
        - text: |
            Hi!
    utter_ask_email:
        - text: |
            What is your email address?
    utter_subscribed:
        - text: |
            Check your inbox at {email} in order to finish subscribing to the newsletter.
        - text: |
            You're all set! Check your inbox at {email} to confirm your subscription.

定义响应

响应位于领域文件或者单独的responses.yml文件的responses键下。每个响应名称都应该以utter_开头。例如,你可以在响应utter_greet和utter_bye下添加问候和再见的响应。

python 复制代码
intents:
    - greet

responses:
    utter_greet:
        - text: "Hi there!"
    utter_bye:
        - text: "See you!"

如果在对话机器人中使用检索意图,还需要为对话机器人对这些意图的回复添加响应:

python 复制代码
intents:
    - chitchat

responses:
    utter_chitchat/ask_name:
        - text: Oh yeah, I am called the retrieval bot.
    
    utter_chitchat/ask_weather:
        - text: Oh, it does look sunny right now in Berlin.

在响应中使用变量

你可以使用变量将信息插入到响应中。在响应中,变量用大括号括起来。例如,如下的name变量:

python 复制代码
responses:
    utter_greet:
    - text: "Hey, {name}. How are you?"

当使用utter_greet响应时,Rasa会自动使用名为name的槽中找到的值来填充变量。如果槽值不存在或为空,则该变量将填充为None

填充变量的另一种方法是在自定义动作中。在自定义动作代码中,你可以为响应提供值来填充特定变量。如果将Rasa SDK用于动作服务,可以将变量的值作为关键字参数传递给dispatcher.utter_message:

python 复制代码
dispatcher.utter_message(
    template="utter_greet",
    name="Sara"
)

比如在Rasa SDK中可以这样写:

python 复制代码
from rasa_sdk import Action, Tracker
from rasa_sdk.executor import CollectingDispatcher

class CustomActionExample(Action):
    def name(self) -> str:
        return "action_custom_example"

    def run(self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:
        name = "Sara"
        dispatcher.utter_message(template="utter_greet", name=name)
        return []

在这个例子中,自定义动作action_custom_example使用"utter_message"方法触发一个名为"utter_greet"的模板,并为模板中的"name"变量提供值Sara

如果您使用的是不同的自定义动作服务,可以通过在服务返回的响应中添加额外的参数来提供值。例如:

python 复制代码
{
  "events":[
    ...
  ],
  "responses":[
    {
      "template":"utter_greet",
      "name":"Sara"
    }
  ]
}

响应变体

为给定的响应提供多种响应变体以供选择可以使得对话机器人的回复更有趣:

python 复制代码
responses:
    utter_greet:
    - text: "Hey, {name}. How are you?"
    - text: "Hey, {name}. How is your day going?"

在上例中,当utter_greet被预测为下一个动作时,Rasa将随机选择两个响应变体中的一个来使用。

特定频道的响应变体

要根据用户连接的频道指定不同的响应变体,请使用特定于频道的响应变体。

在如下示例中,channel键的第一个响应变体特定于slack频道,而第二个变体则不是特定于通道:

python 复制代码
responses:
    utter_ask_game:
    - text: "Which game would you like to play on Slack?"
      channel: "slack"
    - text: "Which game would you like to play?"

注意:确保 channel 键与输入频道的 name() 方法返回的值相匹配。如果你使用的是内置频道,此值也将与 credentials.yml 文件中使用的频道名称相匹配。

当对话机器人在给定响应名称下寻找合适的响应变体时,它会首先尝试从当前频道的特定频道辩题中进行选择。如果不存在此类变体,则对话机器人将从非频道特定的响应变体中进行选择。

注意:

对于每个响应,至少有一个没有channel键的响应变体,这使得对话机器人在所有环境中都可以正确的响应。

条件响应变体

也可以使用条件响应变体基于一个或多个槽值来选择特定响应变体。

条件响应变体在领域或响应YAML文件中定义,类似于标准响应变体,但具有额外的condition键。此键指定name和value槽约束的列表。

当在对话期间触发响应时,将根据当前对话状态检查每个条件响应变化的约束。如果所有约束槽值都等于当前对话状态的对应槽值,则响应变体可以被对话机器人使用。

在下面的示例中,将定义一个具有一个约束的条件响应变体,即logged_in槽设置为true:

domain.yml

python 复制代码
slots:
    logged_in:
        type: bool
        influence_conversation: False
        mappings:
        - type: custom
    name:
        type: text
        influence_conversation: False
        mappings:
        - type: custom

responses:
    utter_greet:
    - condition:
        - type: slot
          name: logged_in
          value: true
      text: "Hey, {name}.Nice to see you again!How are you?"
    - text: "Welcome. How is your day going?"

stories.yml

python 复制代码
stories:
- story: greet
  steps:
    - action: action_log_in
    - slot_was_set:
        - logged_in: true
    - intent: greet
    - action: utter_greet

在上述示例中,只要执行utter_greet动作并将logged_in槽设置为true,就会使用第一个响应变体("Hey, {name}. Nice to see you again! How are you?")。没有条件的第二个辩题将被视为默认值,并在logged_in不等于true时使用。

注意:

强烈建议始终提供没有条件的默认响应变体,以防止没有条件响应与已填充槽匹配的情况发生。

在对话期间,Rasa将从所有满足约束条件的响应变体中进行选择。如果有多个符合条件的条件响应变体,Rasa将随机选择一个。例如,考虑如下响应:

python 复制代码
responses:
    utter_greet:
    - condition:
        - type: slot
          name: logged_in
          value: true
      text: "Hey, {name}.Nice to see you again! How are you?"

    - condition:
        - type: slot
          name: eligible_for_upgrade
          value: true
      text: "Welcome, {name}. Did you know you are eligible for a free upgrade?"
    - text: "Welcome. How is your day going?"

如果logged_in和eligible_for_upgrade都设置为true,那么第一个和第二个响应变体都可以使用,并且将由对话机器人以相同的概率选择。

你可以继续使用特定于频道的响应变体以及条件响应变体,如下例所示:

java 复制代码
slots:
  logged_in:
    type: bool
    influence_conversation: False
    mappings:
    - type: custom
  name:
    type: text
    influence_conversation: False
    mappings:
    - type: custom

responses:
  utter_greet:
    - condition:
        - type: slot
          name: logged_in
          value: true
      text: "Hey, {name}. Nice to see you again on Slack! How are you?"
      channel: slack
    - text: "Welcome. How is your day going?"

Rasa将按以下顺序优先选择响应:

1.匹配频道的条件响应

2.匹配频道的默认响应

3.未匹配频道的条件响应

4.未匹配频道的默认响应

富响应

可以通过添加视觉和交互元素来丰富响应。

按钮

java 复制代码
responses:
    utter_greet:
    - text: "Hey! How are you?"
      buttons:
      - title: "great"
        payload: "/mood_great"
      - title: "super sad"
        payload: "/mood_sad"

按钮列表中的每个按钮都应该有两个键:

title:按钮上显示的文本

payload:单击按钮时从用户发送给助手的消息

如果你希望按钮将实体也传递给对话机器人:

java 复制代码
responses:
    utter_greet:
    - text: "Hey! Would you like to purchase motor or home insurance?"
      buttons:
      - title: "Motor insurance"
        payload: '/inform{{"insurance":"motor"}}'
      - title: "Home insurance"
        payload: '/inform{{"insurance":"home"}}'

可以通过如下方式传递多个实体:

java 复制代码
'/intent_name{{"entity_type_1":"entity_value_1","entity_type_2":"entity_value_2"}}'

图片

你可以通过在image键下提供图像的URL来将图像添加到响应中:

java 复制代码
utter_cheer_up:
- text: "Here is something to cheer you up:"
  image: "https://i.imgur.com/esltfjrwls.jpg"

自定义输出荷载

你可以使用custom键将任意输出发送到输出频道。输出频道接受存储在custom键下的对象作为JSON有效荷载。

如下是如何将日期选择器发送到Slack输出频道的示例:

java 复制代码
responses:
  utter_take_bet:
  - custom:
      blocks:
      - type: section
        text:
          text: "Make a bet on when the world will end:"
          type: mrkdwn
        accessory:
          type: datepicker
          initial_date: '2019-05-21'
          placeholder:
            type: plain_text
            text: Select a date

在对话中使用响应

将响应作为动作调用

如果响应的名称以utter_开头,则可以直接将响应用作动作,而不会在领域的actions部分中列出。

将响应添加到领域中:

java 复制代码
responses:
    utter_greet:
    - text: "Hey! How are you?"

你可以在故事中使用相同的响应作为动作:

java 复制代码
stories:
- story: greet user
  steps:
    - intent: greet
    - action: utter_greet

当utter_greet动作运行时,它会将响应中的消息发送回用户

从自定义动作中调用响应

你可以使用响应从自定义动作中生成响应消息。如果你使用Rasa SDK作为动作服务,可以使用调度程序生成响应消息,例如:

actions.py

java 复制代码
from rasa_sdk.interfaces import Action

class ActionGreet(Action):
    def name(self):
        return "action_greet"

    def run(self,dispatcher,tracker,domain):
        dispatcher.utter_message(template="utter_greet")
        return []

如果你使用不同的自定义动作服务,服务应返回如下JSON来调用utter_greet响应:

java 复制代码
{
    "events":[],
    "responses":[
        {
            "template":"utter_greet"
        }
    ]
}

故事

故事是用于训练对话机器人根据用户之前的对话内容做出正确响应的示例对话。故事的格式展示了用户消息的意图,然后是对话机器人的动作和响应。

你的第一个故事应该为一个对话流,其中对话机器人以简单直接的方式来帮助用户实现他们的目标。之后,可以为用户不想提供信息或切换到其他主题的情况添加故事。

java 复制代码
stories:
- story: greet and subscribe
  steps:
    - intent: greet
    - action: utter_greet
    - intent: subscribe 
    - action: newsletter_form
    - active_loop: newsletter_form

编写对话数据

对话数据包括用于Rasa对话机器人对话管理模型训练数据的故事和规则。精心编写的对话数据可以使得对话机器人能够可靠地遵循设置的对话路径并泛化道预期外的路径。

设计故事

在设计故事时,需要考虑两种对话交互:预期的和非预期的路径。预期的路径描述了当用户遵循预期的对话流程,并在收到提示时始终提供必要的信息。但是,用户经常会因为问题、闲聊或询问其他信息而偏离预期的路径,我们称这些为非预期的路径。

我们建议在设计非预期的路径时采用对话驱动的开发。

何时编写故事或规则

规则是对话管理用于处理应始终遵循相同路径的对话片段的一种训练数据。

规则在实现如下场景中很有用:

单轮交互:有些消息不需要上下文就可以进行回答。规则是一种将意图映射到响应的简单方法,可以为这些消息指定固定的答案。

回退行为:结合FallbackClassifier,可以编写规则来响应具有一定回退行为的低置信度用户消息。

表单:激活和提交表单通常都会遵循固定的路径。你可以编写规则来处理表单的非预期输入。

因为规则无法泛化未遇到过的对话,因此你应该将他们保留为单轮对话片段,并使用故事来训练多轮对话。

一个对话机器人将意图为greet的用户消息返回固定响应utter_greet的规则示例如下:

java 复制代码
rules:
- rule: Greeting Rule
  steps:
    - intent: greet
    - action: utter_greet

对于多轮交互,你需要定义一个故事,例如:

java 复制代码
stories:
- story: Greeting and ask user how they're doing
  steps:
  - intent: greet
  - action: utter_greet
  - action: utter_ask_how_doing
  - intent: doing_greet
  - action: utter_happy

管理对话流

以下是管理故事中对话流的一些提示:

何时使用槽来影响对话

槽充当的是对话机器人的内存。当定义一个槽时,你可以定义一个槽是否应该影响对话,influence_conversation属性设置为false的槽仅用于存储信息;influence_conversation属性设置为true的槽可以基于存储的信息来影响对话流。

java 复制代码
stories:
- story: Welcome message, premium user
  steps:
   - intent: greet
   - action: action_check_profile
   - slot_was_set:
     - premium_account: true
   - action: utter_welcome_premium

- story: Welcome message, basic user
  steps:
   - intent: greet
   - action: action_check_profile
   - slot_was_set:
     - premium_account: false
   - action: utter_welcome_basic
   - action: utter_ask_upgrade

如果你不希望槽影响对话流,则应将槽的influence_conversation属性设置为false,不需要在故事中对不影响对话的槽包含slot_was_set事件。

实现分支逻辑

在编写故事时,有时候下一个动作将取决于一个自定义动作的返回值。在这些情况下,重要的是要在返回槽和直接使用自定义动作代码来影响对话机器人下一步做什么之间找到平衡。

当某个值仅用于确定对话机器人的响应的情况时,可以考虑将决策逻辑嵌入自定义动作中,而不是在故事中使用一个特征化的槽。浙江有助于降低整体复杂性并使得故事更易于管理。

例如,你可以将这些故事:

java 复制代码
stories:
- story: It's raining now
  steps:
  - intent: check_for_rain
  - action: action_check_for_rain
  - slot_was_set:
    - rainint: true
  - action: utter_is_raining
  - action: utter_bring_umbrella

- story: It isn't raining now
  steps:
  - intent: check_for_rain
  - action: action_check_for_rain
  - slot_was_set:
    - raining: false
  - action: utter_not_raining
  - action: utter_no_umbrella_needed

通过如下自定义动作代码:

python 复制代码
def run(self,dispatcher,tracker,domain):
    is_raining=check_rain()
    if is_raining:
        dispatcher.utter_message(template="utter_is_raining")
        dispatcher.utter_message(template="utter_bring_umbrella")
    else:
        dispatcher.utter_message(template="utter_not_raining")
        dispatcher.utter_message(template="utter_no_bring_needed")
    return []

转换成一个单独的故事:

python 复制代码
stories:
- story: check for rain
  steps:
    - intent: check_for_rain
    - action: action_check_for_rain

在某个值用于影响未来的动作流的情况下,则返回一个特征化槽来确定故事。例如,如果你想收集有关新用户的信息,而不是返回用户的信息,则故事可能如下:

python 复制代码
stories:
- story: greet new user
  steps:
    - intent: greet
    - action: check_user_status
    - slot_was_set:
        - new_user: true
    - action: utter_greet
    - action: new_user_form
    - active_loop: new_user_form
    - active_loop: null

- story: greet returning user
  steps:
    - intent: greet
    - action: check_user_status
    - slot_Was_set:
        - new_user: false
    - action: utter_greet
    - action: utter_how_can_help

使用或语句和检查点

或语句和检查点可用于减少必须编写的故事数量。但你应该谨慎使用它们。过度使用或语句和检查点会减慢训练速度,创建过多的检查点会让你的故事难以理解。

或语句

在对话机器人以相同方式处理不同意图或槽事件中,可以使用或语句作为创建新故事的替代方法。

python 复制代码
stories:
- story: newsletter signup
  steps:
    - intent: signup_newsletter
    - action: utter_ask_confirm_signup
    - intent: affirm
    - action: action_signup_newsletter

- story: newsletter signup, confirm via thanks
  steps:
    - intent: signup_newsletter
    - action: utter_ask_confirm_signup
    - intent: thanks
    - action: action_signup_newsletter

用或语句合并为一个故事:

python 复制代码
stories:
- story: newsletter signup with or
  steps:
    - intent: signup_newsletter
    - action: utter_ask_confirm_singup
    - or:
        - intent: affirm
        - intent: thanks
    - action: action_signup_newsletter

在训练阶段,这个故事将被划分为两个原始故事

检查点

检查点对于将故事模块化成经常重复的单独部分时很有用。例如,如果你希望对话机器人在每个对话流结束时询问用户反馈,可以使用检查点来避免在每个故事结束时包含反馈交互:

python 复制代码
stories:
- story: beginning of conversation
  steps:
  - intent: greet
  - action: utter_greet
  - intent: goodbye
  - action: utter_goodbye
  - checkpoint: ask_feedback

- story: user provides feedback
  steps:
  - checkpoint: ask_feedback
  - action: utter_ask_feedback
  - intent: inform
  - action: utter_thank_you
  - action: utter_anything_else

- story: user doesn't have feedback
  steps:
  - checkpoint: ask_feedback
  - action: utter_ask_feedback
  - intent: deny
  - action: utter_no_problem
  - action: utter_anything_else

注意:检查点旨在使不同故事中重复使用某些对话部分变得更加容易。但是强烈反对在现有检查点内部使用检查点,这会显著增加训练时间并使故事难以理解。

在故事中创建逻辑中断

在设计对话流时,经常会创建长的故事来从头到尾补货完整的对话交互。在很多情况下,由于需要考虑分支路径,这会增加需要训练故事的数量。相反,可以考虑将较长的故事分成较小的对话块来处理子任务。

一个用户处理信用卡丢失的预期路径可能如下所示:

python 复制代码
stories:
- story: Customer loses a credit card, reviews transactions, and gets a new card
  steps:
  - intent: card_lost
  - action: check_transactions
  - slot_was_set:
    - reviewed_transactions: ["starbucks"]
  - action: utter_ask_fraudulent_transactions
  - intent: inform
  - action: action_update_transactions
  - intent: affirm
  - action: utter_confirm_transaction_dispute
  - action: utter_replace_card
  - action: mailing_address_form
  - active_loop: mailing_address
  - active_loop: null
  - action: utter_sent_replacement
  - action: utter_anything_else
  - intent: affirm
  - action: utter_help

处理一个信用卡丢失设计一系列子任务,包括用于欺诈交易的消费历史检查、确认替换卡的邮寄地址、然后跟进用户的其他要求。在对话中,对话机器人会在多个地方提示用户输入,创建需要用到的分支路径。

例如:当提示utter_ask_fraudulent_transactions时,如果并不适用,用户可能会以deny意图进行响应。当用户被问及对话机器人是否可以帮助他们时,用户可以以deny意图进行响应。

我们可以将这个长故事分成几个小故事:

python 复制代码
stories:
- story: Customer loses a credit card
  steps:
  - intent: card_lost
  - action: utter_card_locked
  - action: spending_history_form
  - active_loop: spending_history_form
  - active_loop: null
  - slot_was_set:
    - reviewed_transactions: ["starbucks"]
  - action: utter_ask_fraudulent_transactions

- story: Customer reviews transactions and gets a new card
  steps:
  - action: utter_ask_fraudulent_transactions
  - intent: inform
  - action: action_update_transactions
  - slot_was_set:
    - reviewed_transactions: ["target", "starbucks"]
  - intent: affirm
  - action: utter_confirm_transaction_dispute
  - action: utter_replace_card
  - action: mailing_address_form
  - active_loop: mailing_address
  - active_loop: null
  - action: utter_sent_replacement
  - action: utter_anything_else

- story: Customer has something else they need help with
  steps:
  - action: utter_anything_else
  - intent: affirm
  - action: utter_help

处理上下文切换

通常,用户不会用你向他们询问的消息做出回应,而是会用不相关的问题来偏离预期的路径。

使用规则进行上下文切换

在单轮插入语情况下,可以使用规则而非故事来处理上下文切换。

在这个示例中,用户在支付信用卡账单的过程中询问账户余额,之后被引导回信用卡支付表单。因为无论上下文如何询问用户余额都应该始终得到相同的响应,因此你可以在现有流程中创建一个可以自动触发的规则:

python 复制代码
rules:
- rule: Check my account balance
  steps:
  - intent: check_account_balance
  - action: action_get_account_balance

默认情况下,表单将持续保持活动状态并重新提示必要的信息,而无需创建额外的训练故事。

使用故事进行上下文切换

当用户的插入语需要多轮对话时,你需要编写额外的故事来处理上下文切换。如果你有两个不同对话流并希望用户能在流之间切换,则需要创建故事来指定切换将如何发生以及如何维护上下文。

例如,如果你想在用户询问时切换上下文,然后在询问完成后返回原始流程:

你需要创建一个故事来描述这种上下文切换交互:

python 复制代码
stories:
- story: Context switch from credit card payment to money transfer
  steps:
    - intent: pay_credit_card
    - action: credit_card_payment_form
    - active_loop: credit_card_payment_form
    - intent: transfer_money
    - active_loop: null
    - action: transfer_money_form
    - active_loop: transfer_money_form
    - active_loop: null
    - action: utter_continue_credit_card_payment

管理对话数据文件

你可以将训练数据作为单个文件或包含多个文件的目录提供给开源Rasa。在编写故事和规则时,通常最好根据所表示的对话类型创建单独的文件。

例如,你可以创建一个chitchat.yml文件来处理闲聊,创建一个faqs.yml来处理faq。

表单

在很多情况下,对话机器人需要从用户收集信息。例如:当一个用户想要订阅时事通讯时,对话机器人必须询问其电子邮箱地址。

python 复制代码
slots:
    email:
        type: text
        mappings:
        - type: from_text
          condition:
          - active_loop: newsletter_form
          - requested_slot: email


forms:
    newsletter_form:
        required_slots:
        - email

用法

要在开源Rasa中使用表单,你需要确保将规则策略添加到策略配置中。例如:

python 复制代码
policies:
- name: RulePolicy

定义表单

通常将表单添加到领域中的forms部分来定义表单。

表单的名称也可以是你在故事或者规则中用于处理表单执行的动作的名称。

你需要为必需的required_slots键指定槽名称列表。

如下示例表单restaurant_form将填充cuisine和num_people槽。

python 复制代码
entities:
- cuisine
- number

slots:
    cuisine:
        type: text
        mappings:
        - type: from_entity
          entity: cuisine
    num_people:
        type: any
        mappings:
        - type: from_entity
          entity: number

forms:
    restaurant_form:
        required_slots:
            - cuisine
            - number

可以再ignored_intents键下为整个表单定义要忽略的意图列表。在ignored_intents下列出的意图将被添加到每个槽映射的not_intent键中。

例如,如果你不希望在chitchat意图时填写表单所需槽,那么需要定义如下内容(在表单名称之后和ignored_intents意图关键字下):

python 复制代码
entities:
- cuisine
- number
slots:
  cuisine:
    type: text
    mappings:
    - type: from_entity
      entity: cuisine
  num_people:
    type: any
    mappings:
    - type: from_entity
      entity: number
forms:
  restaurant_form:
    ignored_intents:
    - chitchat
    required_slots:
        - cuisine
        - num_people

一旦表单动作第一次被调用,表单就会被激活并提示用户输入下一个所需的槽。它通过查找名为utter_ask_<form_name><slot_name>或utter_ask<slot_name>(如果未找到前者)的响应来执行此动作。确保在领域文件中为每个必须的槽定义这些响应。

激活表单

要激活表单,你需要添加一个故事或规则,它描述了对话机器人应该何时运行表单。在特定意图触发表单的情况下,可以使用如下示例规则:

python 复制代码
rules:
- rule: Activate form
  steps:
  - intent: request_restaurant
  - action: restaurant_form
  - active_loop: restaurant_form

停用表单

填写完所有必须的槽后,表单将自动停用。

你可以使用规则或者故事来描述对话机器人在表单结束时的行为。如果不添加适用的故事或规则,对话机器人将在表单完成后自动接收下一条用户消息。

如下示例在表单your_form填满所有必须的槽后立即运行utter_submit和utter_slots_values

python 复制代码
rules:
- rule: Submit form
  conditon:
  - active_loop: restaurant_form
  steps:
  - action: restaurant_form
  - active_loop: null
  - slot_was_set:
    - requested_slot: null
  - action: utter_submit
  - action: utter_slots_values

槽映射

从3.0版本开始,槽映射在领域的slots部分中定义。此更改允许在多个表单中重复使用相同的槽映射,从而消除不必要的重复。

为非预期的表单路径编写故事/规则

用户不会总是回复要求他们提供的信息。通常,用户会提出问题、闲聊、改变主意,或者以其他方式偏离预期的路径。

当表单处于活动状态时,如果用户的输入未填充请求的槽,则表单动作的执行将被拒绝,即表单将自动引发ActionExecutionRejection。以下是表单将引发ActionExecutionRejection的特定场景:

请求一个槽,但用户没有用他们的最后一条信息填充槽,并且没有定义用于验证槽或提取槽的自定义动作

请求一个槽,但用于验证槽或提取槽的自定义动作未返回任何SlotSet事件

要有意拒绝表单执行,还可以返回ActionExecutionRejected事件作为自定义验证或槽映射的一部分。

要处理可能导致表单执行被拒绝的情况,你可以编写包含预期中断的规则或故事。例如,如果你希望用户与对话机器人闲聊,可以添加一个规则来处理这个问题:

python 复制代码
rules:
- rule: Example of an unhappy path
  condition:
    - active_loop: restaurant_form
  steps:
  - intent: chitchat
  - action: utter_chitchat
  - action: restaurant_form
  - active_loop: restaurant_form

在某些情况下,用户可能会在表单动作的中间改变主意并决定不继续它们的初识请求。在这种情况下,对话机器人应该获取要求的槽。

使用action_deactivate_loop默认动作可以优雅地处理此类情况。该动作将停用表单并重置请求的槽。此类对话示例故事如下所示:

python 复制代码
stories:
- story: User interrupts the form and doesn't want to continue
  steps:
  - intent: request_restaurant
  - action: restaurant_form
  - active_loop: restaurant_form
  - intent: stop
  - action: utter_ask_continue
  - intent: stop
  - action: action_deactivate_loop
  - active_loop: null

进阶用法

使用自定义动作可完全自定义表单。

校验表单输入

从用户输入中提取槽值后,可以验证提取的槽。默认情况下,Rasa仅在请求槽后验证是否填充了任何槽。

可以通过自定义动作validate_<form_name>来验证任何提取的槽。确保将此动作添加到领域的actions部分:

python 复制代码
actions:
- validate_restaurant_form

执行表单时,它将在每个用户轮流验证最新填充的槽后运行自定义动作。

此自定义动作可以扩展FormValidationAction类来简化验证提取槽的过程。在这种情况下,你需要为每个提取的槽编写名为validate_<slot_name>函数。

如下示例显示了一个自定义动作的视线,该动作验证名为cuisine的槽是否有效。

python 复制代码
from typing import Text, List, Any, Dict

from rasa_sdk import Tracker, FormValidationAction
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk.types import DomainDict


class ValidateRestaurantForm(FormValidationAction):
    def name(self) -> Text:
        return "validate_restaurant_form"

    @staticmethod
    def cuisine_db() -> List[Text]:
        """Database of supported cuisines"""

        return ["caribbean", "chinese", "french"]

    def validate_cuisine(
        self,
        slot_value: Any,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: DomainDict,
    ) -> Dict[Text, Any]:
        """Validate cuisine value."""

        if slot_value.lower() in self.cuisine_db():
            # validation succeeded, set the value of the "cuisine" slot to value
            return {"cuisine": slot_value}
        else:
            # validation failed, set this slot to None so that the
            # user will be asked for the slot again
            return {"cuisine": None}

你还可以扩展Action类并使用tracker.slots_to_validate检索提取的槽,以完全自定义验证过程。

自定义槽映射

如果预定义的槽映射都不适用你的用例,可以使用自定义动作 validate_<form_name> 编写自己的提取代码。开源 Rasa 将在表单运行时触发此动作。

如果使用的是 Rasa SDK,我们建议你扩展提供的 FormValidationAction。使用 FormValidationAction 时,需要三个步骤提取自定义槽:

  1. 为应该以自定义方式映射的每个槽定义一个方法 extract_<slot_name>

  2. 在领域文件中,对于表单的 required_slots,列出所有必须的槽,包括预定义和自定义映射。

此外,可以重写 required_slots 方法来添加动态请求的槽,可以在动态表单行为部分获取更多信息。

如下示例显示了一个表单的实现,该表单以自定义方式提取槽 outdoor_seating,以及使用预定义映射的槽。extract_outdoor_seating 方法根据关键字 outdoor 是否出现在最后一个用户消息中来设置槽 outdoor_seating

python 复制代码
from typing import Dict, Text, List, Optional, Any

from rasa_sdk import Tracker
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk.forms import FormValidationAction


class ValidateRestaurantForm(FormValidationAction):
    def name(self) -> Text:
        return "validate_restaurant_form"

    async def extract_outdoor_seating(
        self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
    ) -> Dict[Text, Any]:
        text_of_last_user_message = tracker.latest_message.get("text")
        sit_outside = "outdoor" in text_of_last_user_message

        return {"outdoor_seating": sit_outside}

默认情况下,FormValidationAction会自动将requested_slot设置为required_slots中指定的第一个未填充的槽。

动态表单行为

默认情况下,开源 Rasa 将在领域文件中为表单列出的槽中请求下一个空槽。如果使用自定义槽映射和 FormValidationAction,它将要求 required_slots 方法返回第一个空槽。如果 required_slots 中的所有槽都已经填满,则表单将被停用。

如果需要,可以动态更新表单的所需槽。例如,当你需要根据前一个槽的填充方式获得更多详细信息或想要更改请求槽的顺序时,这很有用。

如果你使用 Rasa SDK,我们建议使用 FormValidationAction 并覆盖 required_slots 以适应动态行为。你应该为每个不使预定义映射的槽实现一个方法 extract_<slot name>,如自定义槽映射中所述。如下示例将询问用户是否想坐在阴凉处或阳光下,以防他们想说坐在外面。

python 复制代码
from typing import Text, List, Optional

from rasa_sdk.forms import FormValidationAction

class ValidateRestaurantForm(FormValidationAction):
    def name(self) -> Text:
        return "validate_restaurant_form"

    async def required_slots(
        self,
        domain_slots: List[Text],
        dispatcher: "CollectingDispatcher",
        tracker: "Tracker",
        domain: "DomainDict",
    ) -> List[Text]:
        additional_slots = ["outdoor_seating"]
        if tracker.slots.get("outdoor_seating") is True:
            # If the user wants to sit outside, ask
            # if they want to sit in the shade or in the sun.
            additional_slots.append("shade_or_sun")

        return additional_slots + domain_slots

相反,如果想在特定条件下从领域文件中定义表单的required_slots中删除一个槽,应该将domain_slots复制到一个新变量并将更改应用于该新变量,而不是直接修改domain_slots。直接修改domain_slots可能会导致意外行为。例如:

python 复制代码
from typing import Text, List, Optional

from rasa_sdk.forms import FormValidationAction

class ValidateBookingForm(FormValidationAction):
    def name(self) -> Text:
        return "validate_booking_form"

    async def required_slots(
        self,
        domain_slots: List[Text],
        dispatcher: "CollectingDispatcher",
        tracker: "Tracker",
        domain: "DomainDict",
    ) -> List[Text]:
        updated_slots = domain_slots.copy()
        if tracker.slots.get("existing_customer") is True:
            # If the user is an existing customer,
            # do not request the `email_address` slot
            updated_slots.remove("email_address")

        return updated_slots

requested_slot槽

python 复制代码
stories:
- story: explain cuisine slot
  steps:
  - intent: request_restaurant
  - action: restaurant_form
  - active_loop: restaurant
  - slot_was_set:
    - requested_slot: cuisine
  - intent: explain
  - action: utter_explain_cuisine
  - action: restaurant_form
  - active_loop: null

- story: explain num_people slot
  steps:
  - intent: request_restaurant
  - action: restaurant_form
  - active_loop: restaurant
  - slot_was_set:
    - requested_slot: cuisine
  - slot_was_set:
    - requested_slot: num_people
  - intent: explain
  - action: utter_explain_num_people
  - action: restaurant_form
  - active_loop: null

使用自定义动作请求下一个槽

一旦表单确定用户接下来必须填写哪个槽,它将执行 utter_ask_<form_name>_<slot_name>utter_ask_<slot_name> 动作来要求用户提供必要的信息。如果常规话术不够,可以使用自定义动作 action_ask_<form_name>_<slot_name>action_ask_<slot_name> 来请求下一个槽。

python 复制代码
from typing import Dict, Text, List

from rasa_sdk import Tracker
from rasa_sdk.events import EventType
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk import Action


class AskForSlotAction(Action):
    def name(self) -> Text:
        return "action_ask_cuisine"

    def run(
        self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
    ) -> List[EventType]:
        dispatcher.utter_message(text="What cuisine?")
        return []

如果槽有多个询问选项,Rasa 将按照如下顺序排列优先级:

  1. action_ask_<form_name>_<slot_name>
  2. utter_ask_<form_name>_<slot_name>
  3. action_ask_<slot_name>
  4. utter_ask_<slot_name>

规则

规则描述了对话中应该始终遵循的相同路径,无论在之前的对话中说过什么。

我们希望对话机器人能够始终以特定动作来响应一个特定意图,因此我们需要使用规则将动作映射到意图。

在代码块中,我们添加了一条规则,该规则在用户表达"订阅"意图时触发 newsletter_form。我们还添加了一条规则用于一旦提供了所有必需的信息就触发 utter_subscribed 动作。第二条规则仅在 newsletter_form 开始激活时适用,一单它不再处于激活状态(active_loop: null)后,表单就完成了。

python 复制代码
rules:
  - rule: activate subscribe form
    steps:
    - intent: subscribe
    - action: newsletter_form
    - active_loop: newsletter_form

  - rule: submit form
    condition:
    - active_loop: newsletter_form
    steps:
    - action: newsletter_form
    - active_loop: null
    - action: utter_subscribed

规则简介

规则是一种用于训练对话机器人的对话管理模型的训练数据。规则描述了应该始终遵循的相同路径的简短对话。

不要过度使用规则。规则非常适合处理小型的特定对话模式,但与故事不同,规则没有能力泛化至未见过的对话路径。结合规则和故事,可以使对话机器人变得更加鲁棒并能够处理真实的用户行为。

编写规则

在开始编写规则之前,你必须确保将规则策略添加到模型配置中:

python 复制代码
policies:
- name: RulePolicy

可以将规则添加到训练数据的rules部分。

要表明规则可以在对话中的任何点使用,请从启动会话的意图开始,然后添加对话机器人应执行的动作来响应意图。

python 复制代码
rules:
- rule: Say `hello` whenever the user sends a message with intent `greet`
  steps:
  - intent: greet
  - action: utter_greet

此示例规则适用于对话开始以及用户决定在正在进行的对话中发送带有greet意图的消息时。

仅作为规则出现在训练数据中而不出现在故事中的对话轮次将在预测时被 TEDPolicy 等仅机器学习策略忽略。

python 复制代码
rules:
- rule: Say `hello` whenever the user sends a message with intent `greet`
  steps:
  - intent: greet
  - action: utter_greet

stories:
- story: story to find a restaurant
  steps:
  - intent: find_restaurant
  - action: restaurant_form
  - action: utter_restaurant_found

例如,如果如上所述定义了问候规则并且不将其添加到任何故事中,则在RulePolicy预测utter_greet之后,TEDPolicy将进行预测,就像没有发生greet和utter_greet轮次一样。

用于对话开始的规则

要编写仅适用于对话开始的规则,请在规则中添加一个conversation_start: true

python 复制代码
rules:
- rule: Say hello when the user starts a conversation with intent greet
  conversation_start: true
  steps:
  - intent: greet
  - action: utter_greet

如果用户稍后在对话中发送带有greet的意图的消息,规则将不匹配。

有条件的规则

条件描述了为适用规则而必须满足的要求。为此,请在condition键下添加有关先前对话的任何信息:

python 复制代码
rules:
- rule: Only say hello if the user provided a name
  condition:
  - slot_was_set:
    - user_provided_name: true
  steps:
  - intent: greet
  - action: utter_greet

你可以在条件下包含的可能信息包括slot_was_set事件和active_loop事件。

在规则结束跳过等待用户输入

默认情况下,规则将在完成最后一步后等待下一条用户信息:

python 复制代码
rules:
- rule: Rule which will wait for user message when it was applied
  steps:
  - intent: greet
  - action: utter_greet
  # - action: action_listen
  # Every rule implicitly includes a prediction for `action_listen` as last step.
  # This means that Rasa Open Source will wait for the next user message.

如果你想将下一个动作预测交给另一个故事或规则,请将wait_for_user_input:false添加到你的规则中:

python 复制代码
rules:
- rule: Rule which will not wait for user message once it was applied
  steps:
  - intent: greet
  - action: utter_greet
  wait_for_user_input: false

这表明对话机器人应该在等待更多用户输入之前执行另一个动作。

规则和表格

当表单处于活动状态时,对话机器人将根据表单的定义方式进行预测,而忽略规则。如果出现以下情况,规则将再次可用:

表单填充了所有必须的槽

表单拒绝执行

运行

第一步: rasa train

用于训练NLU数据和故事成为一个模型,保存至./models

第二步:rasa shell

加载训练好的模型并让你可以通过命令行与机器人交谈


Rasa入门案例到此为止~

相关推荐
m0_6896182812 分钟前
数学建模助力干细胞研究,配体纳米簇如何影响干细胞命运
笔记·数学建模
是十一月末1 小时前
机器学习之KNN算法预测数据和数据可视化
人工智能·python·算法·机器学习·信息可视化
羊村懒哥1 小时前
tomcat-安装笔记(包含虚拟主机配置)
java·笔记·tomcat
百流1 小时前
scala基础学习_运算符
开发语言·学习·scala
百流1 小时前
scala基础学习(数据类型)-数组
开发语言·学习·scala
虾球xz2 小时前
游戏引擎学习第61天
java·学习·游戏引擎
qq_430583972 小时前
QT笔记- QTreeView + QFileSystemModel 当前位置的保存与恢复 #选中 #保存当前索引
开发语言·笔记·qt
山顶夕景2 小时前
【ML】机器学习中常见的25个数学公式
人工智能·数学·机器学习
Crossoads2 小时前
【汇编语言】外中断(一)—— 外中断的魔法:PC机键盘如何触发计算机响应
android·开发语言·数据库·深度学习·机器学习·计算机外设·汇编语言
Zik----2 小时前
Anaconda搭建Python虚拟环境并在Pycharm中配置(小白也能懂)
开发语言·人工智能·python·机器学习·pycharm