Rasa初体验--构建对话机器人
NLU数据
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文件,也可以拆分为目录中的多个文件。
当拆分为多个文件的时候,领域内容可被直接读取并自动合并在一起。
你可以通过运行如下命令在命令行界面中训练具有拆分领域文件的模型:
rasa train --domain path_to_domain_directory
意图
领域文件中的intents键列出了NLU数据和对话训练数据中使用的所有意图。
忽略某些意图的实体
要忽略某些实体或者显示地仅考虑某些实体,你可以使用如下语法:
intents:
- greet:
use_entities:
- name
- first_name
- farewall:
ignore_entities:
- location
- age
- last_name
对于任何单一意图,只能是use_entities或ignore_entities
这些意图的排除实体将不被特征化,因此不会影响下一个动作的预测,当你有一个不关心被提取的实体的意图时,这很有用。
实体
实体部分列出了可以由NLU管道中的任何实体提取器提取的所有实体。
entities:
- PERSON
- time
- membership_type
- priority
当使用多个领域文件时,可以在任何领域文件中指定实体,并可以在任何领域文件中被任何意图使用或忽略。
如果使用实体角色和分组功能,你还需要在本节中列出实体的角色和组。
例如:
entities:
- city:
roles:
- from
- to
- topping:
groups:
- 1
- 2
- size:
groups:
- 1
- 2
默认情况下,实体会影响动作预测。为了防止提取到的实体影响特定意图的对话,你可以忽略某些意图的实体。
要忽略所有意图的实体,不必在每个意图的ignore_entities标志下列出,可以在实体下将influence_conversation设置为false
entities:
- location:
influence_conversation: false
此语法与将实体添加到领域中每个意图的ignore_entities列表具有相同的效果
显式设置influence_conversation: true 不会改变任何行为,因为这是默认设置
槽
槽式对话机器人的记忆。它作为键值存储用于存储用户提供的信息(例如:家乡)以及收集的有关外部的信息(例如:数据库查询结果)
槽在领域的slots部分中定义,包括它们的名称、类型和他们是否以及如何影响对话机器人的行为。
以下示例定义了一个名为“slot_name”的槽,类型为text,预定义槽映射from_entity
slots:
slot_name:
type: text
mappings:
- type: from_entity
entity: entity_name
槽和对话行为
你可以使用influence_conversation属性来指定槽是否影响对话
如果你想在槽中存储信息而不影响对话,请在定义槽时设置influence_conversation:false
以下示例定义了一个age槽,它将存储有关用户年龄的信息,但不会影响对话的流程。这意味着对话机器人每次预测下一个动作的时候都会忽略槽的值。
slots:
age:
type: text
influence_conversation: false
槽类型
①文本类型槽
类型:text
用途:存储文本值
示例:
slots:
cuisine:
type: text
mappings:
- type: from_entity
entity: cuisine
描述:如果influence_conversation设置为true,对话机器人的行为将根据slot是否设置而改变。但是不同的文本不会进一步影响对话,这意味着如下两个故事是相等的:
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值
示例:
slots:
is_authenticated:
type: bool
mappings:
- type: custom
③分类型槽
类型:categorical
用途:存储N个可选值之一的槽
示例:
slots:
risk_level:
type: categorical
values:
- low
- medium
- high
mappings:
- type: custom
描述:__other__会自动添加到用户定义的值中。所有遇到的未在槽值中明确定义的值都映射到__other__。__other__不应用作用户定义的值,负责它将作为所有未见过值映射到的默认值。
④浮点类型槽
类型:float
用途:存储实数
示例:
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
用途:存储列表值
示例:
slots:
shopping_items:
type: list
mappings:
- type: from_entity
entity: shopping_item
描述:如果influence_conversation设置为true,对话机器人的行为将根据列表是否为空而改变。存储在槽中的列表长度不会影响对话。只有列表长度是零还是非零才重要。
⑥任意类型槽
类型:any
用途:存储任意数据(可以为任意类型,例如:词典或列表)
示例:
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如下:
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:
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:仅当提取的实体数据有该组时才应用映射
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,则槽映射将不适用。
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
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,则槽映射将不适用。
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未提及,如果提取了相关信息,则将设置该槽,而不管表单正在请求哪个槽。
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类型,例如:
slots:
day_of_week:
type: text
mappings:
- type: custom
action: action_calculate_day_of_week
你还可以使用custom槽映射来列出会话过程中将由任意自定义动作填充的槽,方法是列出类型而不列出特定动作。例如:
slots:
handoff_completed:
type: boolean
mappings:
- type: custom
此槽不会在每个用户轮次时更新,但只会在预测为其返回SlotSet事件的自定义动作时更新。
②初始槽值
你可以为领域文件中的槽提供初始值:
slots:
num_fallbacks:
type: float
initial_value: 0
mappings:
- type: custom
高层级结构
每个文件可以包含一个或多个带有训练数据的键,一个文件可以包含多个键,但每个键在单个文件中只能出现一次。可用的键有:
version
nlu
stories
rules
你应该在所有YAML训练数据文件中指定version键。
示例:
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)
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键下定义。可以在此键下添加的项目有:
①按用户意图分组的训练样本,例如可选带标注的实体
nlu:
- intent: check_balance
examples: |
- What's my [credit](account) balance?
- What's the balance on my [credit card account]{"entity":"account","value":"credit"}
②同义词
nlu:
- synonym: credit
examples: |
- credit card account
- credit account
③正则表达式
nlu:
- regex: account_number
examples: |
- \d{10,12}
④查找表
nlu:
- lookup: banks
examples: |
- JPMC
- Comerica
- Bank of America
训练样本
训练样本按照意图分组并列在examples键下。通常会在每一行列出一个样本,如下所示:
nlu:
- intent: greet
examples: |
- hey
- hi
- what's up
但是,如果有自定的NLU组件并且需要样本元数据,也可以使用扩展格式:
nlu:
- intent: greet
examples:
- text: |
hi
metadata:
sentiment: neutral
- text: |
hey there!
metadata键可以包含任意键值数据,这些数据与样本相关联并可被NLU管道中的组件访问。
在上面的示例中,情感元数据可以被管道中的自定义组件用于情感分析。
你还可以再意图级别指定此元数据。在这种情况下,metadata键的内容将传递给每个意图样本。
nlu:
- intent: greet
metadata:
sentiment: neutral
examples:
- text: |
hi
- text: |
hey there!
如果你想制定检索意图,则NLU样本如下:
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_name
和 ask_weather
是后缀。后缀与检索意图名称由 /
分隔。
实体
实体是从用户消息中提取的结构化信息。
在训练样本中实体采用实体名称进行标注。除了实体名称之外,你还可以使用同义词,角色和分组来标注实体。
在训练样本中,实体标注如下所示:
- 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"}
标注一个实体的完整语法为:
[<entity-text>]{"entity":"<entity name>","role":"<role name>","group":"<group name>","value":"<entity synonym>"}
role、group、value可选,value字段表示同义实体。
同义词
同义词通过将提取的实体映射到一个值而非提取的文字来规范化训练数据。
nlu:
- synonym: credit
examples: |
- credit card account
- credit account
你还可以通过指定实体的值在训练样本中定义同义词:
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组件用正则表达式来改进意图分类和实体提取
定义正则表达式的格式如下:
nlu:
- regex: account_number
examples: |
- \d{10,12}
这里account_number是正则表达式的名称。
当用作RegexFeaturizer的特征时,正则表达式的名称无关紧要。
使用RegexEntityExtractor时,正则表达式的名称应与你要提取的实体名称匹配。
查找表
查找表适用于生成不区分大小写的正则表达式模式的单词列表。格式如下:
nlu:
- lookup: banks
examples: |
- JPMC
- Bank of America
当你在训练数据中提供查找表时,该表的内容将组合成一个大的正则表达式。此正则表达式用于检查每个训练样本以查看它是否包含查找表中词条的匹配项。
查找表正则表达式的处理方式与直接在训练数据中指定正则表达式相同,可以与RegexFeaturizer和RegexEntityExtractor一起使用。查找表的名称受制于正则表达式功能名称相同的约束。
对话训练数据
故事和规则都是用户和机器人之间对话的表示。它们用于训练对话管理模型。故事用于训练机器学习模型,来识别对话中的模式并泛化至未见过的对话路径。规则描述了应该始终遵循相同路径并用于训练RulePolicy的小段对话。
故事
故事由以下部分组成:
story:故事的名称。名称可以为任意值,不用做训练。
metadata:任意且可选,不用于训练。你可以使用它来存储有关数据的相关信息,例如:作者。
steps列表:构成故事的用户消息和动作。
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管道的输出,其使用意图和实体的组合来表示与用户发送的具有相同含义的所有可能消息
用户消息遵循如下格式:
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为一个实体:
stories:
- story: story with entities
steps:
- intent: account_balance
entities:
- account_type: credit
- action: action_credit_account_balance
此处包含实体很重要,因为策略会根据意图和实体的组合来学习预测下一个动作(但是你可以使用use_entities属性来改变此行为)
动作
对话机器人执行的所有动作都使用action键指定,后跟动作的名称。在编写故事时,你会遇到两种类型的动作:
响应:以utter_开头并向用户发送特定消息。例如:
stories:
- story: story with a response
steps:
- intent: greet
- action: utter_greet
自定义动作:以action_开头,运行任意代码并发送任意数量的消息(或不发送)
stories:
- story: story with a custom action
steps:
- intent: feedback
- action: action_store_feedback
表单
表单是一种特定类型的自定义动作,其中包含在一组所需槽并要求用户提供此信息上的循环逻辑。你可以在领域的forms部分定义一个表单。一旦定义,你应该指定表单的预期路径作为规则。
你应该在故事中包含表单的中断或其他非预期路径,以便模型可以泛化至未见过的对话序列。作为故事的一个步骤,表单采用以下格式:
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>步骤。
表单的中断如下所示:
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步骤中被故事引用。例如:
stories:
- story: story with a slot
steps:
- intent: celebrate_bot
- slot_was_set:
- feedback_value: positive
- action: utter_yay
这意味着故事要求feedback_value槽的值为positive,对话才能够继续
是否需要包含槽的值取决于槽类型以及该值是否可以或应该影响对话。如果值无关紧要,例如:text槽,你可以只列出槽的名称:
stories:
- story: story with a slot
steps:
- intent: greet
- slot_was_set:
- name
- action: utter_greet_user_by_name
默认情况下,任何槽的初始值为null,你可以使用它来检查槽是否未设置:
stories:
- story: Frech cuisine
steps:
- intent: inform
- slot_was_set:
- cuisine: null
槽如何工作:
故事并不设置槽。如果槽映射适用,则槽必须由默认动作action_extract_slots设置,或者在slot_was_set步骤之前由自定义动作设置。
检查点
检查点使用checkpoint键指定,可以在故事的开头或结尾。
检查点是将故事连接在一起的方式。它们可以是故事的第一步,也可以是最后一步。如果是故事的最后一步,则将该故事与其他故事相连,该故事以训练模型时的同名检查点开始。
如下是一个检查点结尾,以及以相同检查点开始的故事示例:
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
故事开头的检查点可以以设置的槽为条件,例如:
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步骤的故事在训练时转换为多个单独的故事。
例如,以下故事将在训练时转换为两个故事:
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。这个故事将在训练时被转换为两个故事:
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键。这些用于指定规则适用的条件。
一个带有条件的规则如下所示:
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用来指定用户消息的实际文本和标注实体。
如下是一个测试故事的例子:
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键将用户消息的文本直接放在故事中。
这些端到端的用户消息遵循如下格式:
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类型的一个实体。
stories:
- story: story with entities
steps:
- user: I can always go for [sushi](cuisine)
- action: utter_suggest_cuisine
同样,你可以将对话机器人消息直接放在故事中,方法是使用bot键,后面为机器人的消息文本。
包含机器人消息的故事示例如下:
stories:
- story: story with an end-to-end response
steps:
- intent: greet
entities:
- name: Ivan
- bot: Hello,a person with a name!
你还可以设置一个混合的端到端的故事:
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完全集成。这意味着你可以混合故事,其中一些步骤由动作或意图定义,其他步骤由用户消息或对话机器人响应直接定义。
响应
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下添加问候和再见的响应。
intents:
- greet
responses:
utter_greet:
- text: "Hi there!"
utter_bye:
- text: "See you!"
如果在对话机器人中使用检索意图,还需要为对话机器人对这些意图的回复添加响应:
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变量:
responses:
utter_greet:
- text: "Hey, {name}. How are you?"
当使用utter_greet响应时,Rasa会自动使用名为name的槽中找到的值来填充变量。如果槽值不存在或为空,则该变量将填充为None
填充变量的另一种方法是在自定义动作中。在自定义动作代码中,你可以为响应提供值来填充特定变量。如果将Rasa SDK用于动作服务,可以将变量的值作为关键字参数传递给dispatcher.utter_message:
dispatcher.utter_message(
template="utter_greet",
name="Sara"
)
比如在Rasa SDK中可以这样写:
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
如果您使用的是不同的自定义动作服务,可以通过在服务返回的响应中添加额外的参数来提供值。例如:
{
"events":[
...
],
"responses":[
{
"template":"utter_greet",
"name":"Sara"
}
]
}
响应变体
为给定的响应提供多种响应变体以供选择可以使得对话机器人的回复更有趣:
responses:
utter_greet:
- text: "Hey, {name}. How are you?"
- text: "Hey, {name}. How is your day going?"
在上例中,当utter_greet被预测为下一个动作时,Rasa将随机选择两个响应变体中的一个来使用。
特定频道的响应变体
要根据用户连接的频道指定不同的响应变体,请使用特定于频道的响应变体。
在如下示例中,channel键的第一个响应变体特定于slack频道,而第二个变体则不是特定于通道:
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
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
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将随机选择一个。例如,考虑如下响应:
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,那么第一个和第二个响应变体都可以使用,并且将由对话机器人以相同的概率选择。
你可以继续使用特定于频道的响应变体以及条件响应变体,如下例所示:
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.未匹配频道的默认响应
富响应
可以通过添加视觉和交互元素来丰富响应。
按钮
responses:
utter_greet:
- text: "Hey! How are you?"
buttons:
- title: "great"
payload: "/mood_great"
- title: "super sad"
payload: "/mood_sad"
按钮列表中的每个按钮都应该有两个键:
title:按钮上显示的文本
payload:单击按钮时从用户发送给助手的消息
如果你希望按钮将实体也传递给对话机器人:
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"}}'
可以通过如下方式传递多个实体:
'/intent_name{{"entity_type_1":"entity_value_1","entity_type_2":"entity_value_2"}}'
图片
你可以通过在image键下提供图像的URL来将图像添加到响应中:
utter_cheer_up:
- text: "Here is something to cheer you up:"
image: "https://i.imgur.com/esltfjrwls.jpg"
自定义输出荷载
你可以使用custom键将任意输出发送到输出频道。输出频道接受存储在custom键下的对象作为JSON有效荷载。
如下是如何将日期选择器发送到Slack输出频道的示例:
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部分中列出。
将响应添加到领域中:
responses:
utter_greet:
- text: "Hey! How are you?"
你可以在故事中使用相同的响应作为动作:
stories:
- story: greet user
steps:
- intent: greet
- action: utter_greet
当utter_greet动作运行时,它会将响应中的消息发送回用户
从自定义动作中调用响应
你可以使用响应从自定义动作中生成响应消息。如果你使用Rasa SDK作为动作服务,可以使用调度程序生成响应消息,例如:
actions.py:
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响应:
{
"events":[],
"responses":[
{
"template":"utter_greet"
}
]
}
故事
故事是用于训练对话机器人根据用户之前的对话内容做出正确响应的示例对话。故事的格式展示了用户消息的意图,然后是对话机器人的动作和响应。
你的第一个故事应该为一个对话流,其中对话机器人以简单直接的方式来帮助用户实现他们的目标。之后,可以为用户不想提供信息或切换到其他主题的情况添加故事。
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的规则示例如下:
rules:
- rule: Greeting Rule
steps:
- intent: greet
- action: utter_greet
对于多轮交互,你需要定义一个故事,例如:
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的槽可以基于存储的信息来影响对话流。
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事件。
实现分支逻辑
在编写故事时,有时候下一个动作将取决于一个自定义动作的返回值。在这些情况下,重要的是要在返回槽和直接使用自定义动作代码来影响对话机器人下一步做什么之间找到平衡。
当某个值仅用于确定对话机器人的响应的情况时,可以考虑将决策逻辑嵌入自定义动作中,而不是在故事中使用一个特征化的槽。浙江有助于降低整体复杂性并使得故事更易于管理。
例如,你可以将这些故事:
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
通过如下自定义动作代码:
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 []
转换成一个单独的故事:
stories:
- story: check for rain
steps:
- intent: check_for_rain
- action: action_check_for_rain
在某个值用于影响未来的动作流的情况下,则返回一个特征化槽来确定故事。例如,如果你想收集有关新用户的信息,而不是返回用户的信息,则故事可能如下:
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
使用或语句和检查点
或语句和检查点可用于减少必须编写的故事数量。但你应该谨慎使用它们。过度使用或语句和检查点会减慢训练速度,创建过多的检查点会让你的故事难以理解。
或语句
在对话机器人以相同方式处理不同意图或槽事件中,可以使用或语句作为创建新故事的替代方法。
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
用或语句合并为一个故事:
stories:
- story: newsletter signup with or
steps:
- intent: signup_newsletter
- action: utter_ask_confirm_singup
- or:
- intent: affirm
- intent: thanks
- action: action_signup_newsletter
在训练阶段,这个故事将被划分为两个原始故事
检查点
检查点对于将故事模块化成经常重复的单独部分时很有用。例如,如果你希望对话机器人在每个对话流结束时询问用户反馈,可以使用检查点来避免在每个故事结束时包含反馈交互:
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
注意:检查点旨在使不同故事中重复使用某些对话部分变得更加容易。但是强烈反对在现有检查点内部使用检查点,这会显著增加训练时间并使故事难以理解。
在故事中创建逻辑中断
在设计对话流时,经常会创建长的故事来从头到尾补货完整的对话交互。在很多情况下,由于需要考虑分支路径,这会增加需要训练故事的数量。相反,可以考虑将较长的故事分成较小的对话块来处理子任务。
一个用户处理信用卡丢失的预期路径可能如下所示:
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意图进行响应。
我们可以将这个长故事分成几个小故事:
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
处理上下文切换
通常,用户不会用你向他们询问的消息做出回应,而是会用不相关的问题来偏离预期的路径。
使用规则进行上下文切换
在单轮插入语情况下,可以使用规则而非故事来处理上下文切换。
在这个示例中,用户在支付信用卡账单的过程中询问账户余额,之后被引导回信用卡支付表单。因为无论上下文如何询问用户余额都应该始终得到相同的响应,因此你可以在现有流程中创建一个可以自动触发的规则:
rules:
- rule: Check my account balance
steps:
- intent: check_account_balance
- action: action_get_account_balance
默认情况下,表单将持续保持活动状态并重新提示必要的信息,而无需创建额外的训练故事。
使用故事进行上下文切换
当用户的插入语需要多轮对话时,你需要编写额外的故事来处理上下文切换。如果你有两个不同对话流并希望用户能在流之间切换,则需要创建故事来指定切换将如何发生以及如何维护上下文。
例如,如果你想在用户询问时切换上下文,然后在询问完成后返回原始流程:
你需要创建一个故事来描述这种上下文切换交互:
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。
表单
在很多情况下,对话机器人需要从用户收集信息。例如:当一个用户想要订阅时事通讯时,对话机器人必须询问其电子邮箱地址。
slots:
email:
type: text
mappings:
- type: from_text
condition:
- active_loop: newsletter_form
- requested_slot: email
forms:
newsletter_form:
required_slots:
- email
用法
要在开源Rasa中使用表单,你需要确保将规则策略添加到策略配置中。例如:
policies:
- name: RulePolicy
定义表单
通常将表单添加到领域中的forms部分来定义表单。
表单的名称也可以是你在故事或者规则中用于处理表单执行的动作的名称。
你需要为必需的required_slots键指定槽名称列表。
如下示例表单restaurant_form将填充cuisine和num_people槽。
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意图关键字下):
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>(如果未找到前者)的响应来执行此动作。确保在领域文件中为每个必须的槽定义这些响应。
激活表单
要激活表单,你需要添加一个故事或规则,它描述了对话机器人应该何时运行表单。在特定意图触发表单的情况下,可以使用如下示例规则:
rules:
- rule: Activate form
steps:
- intent: request_restaurant
- action: restaurant_form
- active_loop: restaurant_form
停用表单
填写完所有必须的槽后,表单将自动停用。
你可以使用规则或者故事来描述对话机器人在表单结束时的行为。如果不添加适用的故事或规则,对话机器人将在表单完成后自动接收下一条用户消息。
如下示例在表单your_form填满所有必须的槽后立即运行utter_submit和utter_slots_values
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事件作为自定义验证或槽映射的一部分。
要处理可能导致表单执行被拒绝的情况,你可以编写包含预期中断的规则或故事。例如,如果你希望用户与对话机器人闲聊,可以添加一个规则来处理这个问题:
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默认动作可以优雅地处理此类情况。该动作将停用表单并重置请求的槽。此类对话示例故事如下所示:
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部分:
actions:
- validate_restaurant_form
执行表单时,它将在每个用户轮流验证最新填充的槽后运行自定义动作。
此自定义动作可以扩展FormValidationAction类来简化验证提取槽的过程。在这种情况下,你需要为每个提取的槽编写名为validate_<slot_name>函数。
如下示例显示了一个自定义动作的视线,该动作验证名为cuisine的槽是否有效。
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
时,需要三个步骤提取自定义槽:
-
为应该以自定义方式映射的每个槽定义一个方法
extract_<slot_name>
。 -
在领域文件中,对于表单的
required_slots
,列出所有必须的槽,包括预定义和自定义映射。
此外,可以重写 required_slots
方法来添加动态请求的槽,可以在动态表单行为部分获取更多信息。
如下示例显示了一个表单的实现,该表单以自定义方式提取槽 outdoor_seating
,以及使用预定义映射的槽。extract_outdoor_seating
方法根据关键字 outdoor
是否出现在最后一个用户消息中来设置槽 outdoor_seating
。
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>
,如自定义槽映射中所述。如下示例将询问用户是否想坐在阴凉处或阳光下,以防他们想说坐在外面。
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可能会导致意外行为。例如:
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槽
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>
来请求下一个槽。
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 将按照如下顺序排列优先级:
action_ask_<form_name>_<slot_name>
utter_ask_<form_name>_<slot_name>
action_ask_<slot_name>
utter_ask_<slot_name>
规则
规则描述了对话中应该始终遵循的相同路径,无论在之前的对话中说过什么。
我们希望对话机器人能够始终以特定动作来响应一个特定意图,因此我们需要使用规则将动作映射到意图。
在代码块中,我们添加了一条规则,该规则在用户表达“订阅”意图时触发 newsletter_form
。我们还添加了一条规则用于一旦提供了所有必需的信息就触发 utter_subscribed
动作。第二条规则仅在 newsletter_form
开始激活时适用,一单它不再处于激活状态(active_loop: null
)后,表单就完成了。
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
规则简介
规则是一种用于训练对话机器人的对话管理模型的训练数据。规则描述了应该始终遵循的相同路径的简短对话。
不要过度使用规则。规则非常适合处理小型的特定对话模式,但与故事不同,规则没有能力泛化至未见过的对话路径。结合规则和故事,可以使对话机器人变得更加鲁棒并能够处理真实的用户行为。
编写规则
在开始编写规则之前,你必须确保将规则策略添加到模型配置中:
policies:
- name: RulePolicy
可以将规则添加到训练数据的rules部分。
要表明规则可以在对话中的任何点使用,请从启动会话的意图开始,然后添加对话机器人应执行的动作来响应意图。
rules:
- rule: Say `hello` whenever the user sends a message with intent `greet`
steps:
- intent: greet
- action: utter_greet
此示例规则适用于对话开始以及用户决定在正在进行的对话中发送带有greet意图的消息时。
仅作为规则出现在训练数据中而不出现在故事中的对话轮次将在预测时被 TEDPolicy
等仅机器学习策略忽略。
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
rules:
- rule: Say hello when the user starts a conversation with intent greet
conversation_start: true
steps:
- intent: greet
- action: utter_greet
如果用户稍后在对话中发送带有greet的意图的消息,规则将不匹配。
有条件的规则
条件描述了为适用规则而必须满足的要求。为此,请在condition键下添加有关先前对话的任何信息:
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事件。
在规则结束跳过等待用户输入
默认情况下,规则将在完成最后一步后等待下一条用户信息:
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添加到你的规则中:
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入门案例到此为止~