Forms形式
最常见的对话模式之一是从用户那里收集一些信息以便做某事(预订餐厅、调用 API、搜索数据库等)。这也称为**槽填充**。
用法#
要在 Rasa Open Source 中使用表单,您需要确保将 规则策略添加到您的策略配置中。例如:
policies: - name: RulePolicy
定义表单#
通过将表单添加到域中的forms
部分来定义表单。表单的名称也是您可以在 故事或规则中用于处理表单执行的操作的名称。您还需要为表单应填写的每个插槽定义插槽映射。您可以为要填充的每个插槽指定一个或多个插槽映射。
下面的示例形式restaurant_form
将填充槽 cuisine
从所提取的实体cuisine
和槽num_people
从实体number
。
1 forms: 2 restaurant_form: 3 required_slots: 4 cuisine: 5 - type: from_entity 6 entity: cuisine 7 num_people: 8 - type: from_entity 9 entity: number
一旦第一次调用表单操作,表单就会被激活并提示用户输入下一个所需的槽值。它通过查找调用 的响应utter_ask_<form_name>_<slot_name>
或未utter_ask_<slot_name>
找到前者来实现此目的。确保在您的域文件中为每个必需的槽定义这些响应。
激活表格#
要激活表单,您需要添加故事或规则,其中描述了助手应何时运行该表单。在特定意图触发表单的情况下,您可以例如使用以下规则:
1 rules: 2 - rule: Activate form 3 steps: 4 - intent: request_restaurant 5 - action: restaurant_form 6 - active_loop: restaurant_form
该active_loop: restaurant_form
步骤表示应在restaurant_form
运行后激活该表单 。
停用表格#
填满所有必需的空位后,表单将自动停用。您可以使用规则或故事来描述助手在表单结尾处的行为。如果您不添加适用的故事或规则,则助手将在表单完成后自动收听下一条用户消息。以下示例utter_all_slots_filled
在表单your_form
填满所有必需的插槽后立即 运行话语。
1 rules: 2 - rule: Submit form 3 condition: 4 # Condition that form is active. 5 - active_loop: restaurant_form 6 steps: 7 # Form is deactivated 8 - action: restaurant_form 9 - active_loop: null 10 - slot_was_set: 11 - requested_slot: null 12 # The actions we want to run when the form is submitted. 13 - action: utter_submit 14 - action: utter_slots_values
用户可能希望尽早退出表单。有关如何为这种情况编写故事或规则的信息,请参阅 编写不愉快形式路径的故事/规则。
插槽映射#
Rasa 开源带有四个预定义的映射,用于根据最新的用户消息填充表单的插槽。如果您需要自定义函数来提取所需信息,请参阅 自定义插槽映射。
来自_实体#
的from_entity
映射填充基于提取的实体槽。它将寻找一个被称为entity_name
填充 slot的实体slot_name
。如果intent_name
是None
,则无论意图名称如何,都将填充插槽。否则,仅当用户的意图为 时才会填充该插槽intent_name
。
如果role_name
和/或group_name
提供,实体的角色/组标签也需要匹配给定的值。如果消息的意图是 ,则槽映射将不适用excluded_intent
。请注意,您还可以为参数intent
和定义意图列表not_intent
。
1 forms: 2 your_form: 3 required_slots: 4 slot_name: 5 - type: from_entity 6 entity: entity_name 7 role: role_name 8 group: group name 9 intent: intent_name 10 not_intent: excluded_intent
在from_entity
映射中,当提取的实体唯一地映射到插槽时,即使表单没有请求该插槽,该插槽也会被填充。如果映射不是唯一的,则提取的实体将被忽略。
forms: your_form: required_slots: departure_city: - type: from_entity entity: city role: from - type: from_entity entity: city arrival_city: - type: from_entity entity: city role: to - type: from_entity entity: city arrival_date: - type: from_entity entity: date
在上面的例子中,实体date
唯一地设置槽arrival_date
,一个实体city
与角色from
唯一地设置狭槽departure_city
和一个实体city
与角色to
唯一地设置狭槽arrival_city
,因此它们可被用于拟合即使未要求这些时隙对应的狭槽。但是,city
没有角色的实体可以同时填充departure_city
和arrival_city
插槽,具体取决于请求的是哪个,因此如果city
在arrival_date
请求插槽时提取了实体,则表单将忽略它。
from_text #
该from_text
映射将使用下一个使用者说话的文本,以填补插槽 slot_name
。如果intent_name
是None
,则无论意图名称如何,都将填充插槽。否则,仅当用户的意图为 时才会填充该插槽intent_name
。
如果消息的意图是 ,则槽映射将不适用excluded_intent
。请注意,您可以为参数intent
和定义意图列表not_intent
。
1 forms: 2 your_form: 3 required_slots: 4 slot_name: 5 - type: from_text 6 intent: intent_name 7 not_intent: excluded_intent
from_intent #
该from_intent
映射将填充槽slot_name
用值my_value
如果用户意图是intent_name
或None
。如果消息的意图是 ,则槽映射将不适用excluded_intent
。请注意,您还可以为参数intent
和定义意图列表not_intent
。
的from_intent
形式的初始激活期间插槽映射将不适用。要根据激活表单的意图填充插槽,请使用from_trigger_intent
映射。
1 forms: 2 your_form: 3 required_slots: 4 slot_name: 5 - type: from_intent 6 value: my_value 7 intent: intent_name 8 not_intent: excluded_intent
from_trigger_intent #
该from_trigger_intent
映射将填充槽slot_name
用值my_value
如果窗体通过用意图用户消息激活intent_name
。如果消息的意图是 ,则槽映射将不适用 excluded_intent
。请注意,您还可以为参数intent
和定义意图列表not_intent
。
1 forms: 2 your_form: 3 required_slots: 4 slot_name: 5 - type: from_trigger_intent 6 value: my_value 7 intent: intent_name 8 not_intent: excluded_intent
为不愉快的表单路径写故事/规则#
您的用户不会总是回复您询问他们的信息。通常,用户会提出问题、闲聊、改变主意或以其他方式偏离快乐的道路。
当表单处于活动状态时,如果用户的输入未填充请求的插槽,则表单操作的执行将被拒绝,即表单将自动引发ActionExecutionRejection
. 这些是表单将引发的特定场景ActionExecutionRejection
:
- 请求了一个插槽,但用户没有用他们的最后一条消息填充该插槽,并且您没有定义用于验证插槽或 提取插槽的自定义操作 。
- 请求了一个插槽,但您用于验证插槽或 提取插槽的自定义操作 未返回任何
SlotSet
事件。
要故意拒绝表单执行,您还可以返回一个ActionExecutionRejected
事件作为自定义验证或槽映射的一部分。
要处理可能导致表单执行被拒绝的情况,您可以编写包含预期中断的规则或故事。例如,如果您希望您的用户与您的机器人聊天,您可以添加一个规则来处理这个问题:
1 rules: 2 - rule: Example of an unhappy path 3 condition: 4 # Condition that form is active. 5 - active_loop: restaurant_form 6 steps: 7 # This unhappy path handles the case of an intent `chitchat`. 8 - intent: chitchat 9 - action: utter_chitchat 10 # Return to form after handling the `chitchat` intent 11 - action: restaurant_form 12 - active_loop: restaurant_form
在某些情况下,用户可能会在表单操作的中间改变主意,并决定不继续他们的初始请求。在这种情况下,助手应该停止请求请求的插槽。
您可以使用默认操作优雅地处理此类情况,该操作action_deactivate_loop
将停用表单并重置请求的插槽。此类对话的示例故事如下所示:
1 stories: 2 - story: User interrupts the form and doesn't want to continue 3 steps: 4 - intent: request_restaurant 5 - action: restaurant_form 6 - active_loop: restaurant_form 7 - intent: stop 8 - action: utter_ask_continue 9 - intent: stop 10 - action: action_deactivate_loop 11 - active_loop: null
这是强烈建议你建立一个使用这些规则或故事, 互动学习。如果您手写这些规则/故事,您可能会错过重要的事情。
高级用法#
验证表单输入#
从用户输入中提取槽值后,您可以验证提取的槽。默认情况下,Rasa Open Source 仅验证在请求插槽后是否填充了任何插槽。
您可以实施自定义操作 validate_<form_name>
来验证任何提取的插槽。确保将此操作添加到actions
域的部分:
actions: - validate_restaurant_form
执行表单时,它将运行您的自定义操作。
此自定义操作可以扩展FormValidationAction
类以简化验证提取槽的过程。在这种情况下,您需要编写validate_<slot_name>
为每个提取的插槽命名的函数。
以下示例显示了自定义操作的实现,该操作验证指定的插槽cuisine
是否有效。
1 from typing import Text, List, Any, Dict 2 3 from rasa_sdk import Tracker, FormValidationAction 4 from rasa_sdk.executor import CollectingDispatcher 5 from rasa_sdk.types import DomainDict 6 7 8 class ValidateRestaurantForm(FormValidationAction): 9 def name(self) -> Text: 10 return "validate_restaurant_form" 11 12 @staticmethod 13 def cuisine_db() -> List[Text]: 14 """Database of supported cuisines""" 15 16 return ["caribbean", "chinese", "french"] 17 18 def validate_cuisine( 19 self, 20 slot_value: Any, 21 dispatcher: CollectingDispatcher, 22 tracker: Tracker, 23 domain: DomainDict, 24 ) -> Dict[Text, Any]: 25 """Validate cuisine value.""" 26 27 if slot_value.lower() in self.cuisine_db(): 28 # validation succeeded, set the value of the "cuisine" slot to value 29 return {"cuisine": slot_value} 30 else: 31 # validation failed, set this slot to None so that the 32 # user will be asked for the slot again 33 return {"cuisine": None}
您还可以扩展Action
类并检索提取的插槽tracker.slots_to_validate
以完全自定义验证过程。
自定义插槽映射#
如果没有预定义的插槽映射适合您的用例,您可以使用 自定义操作 validate_<form_name>
来编写您自己的提取代码。Rasa Open Source 会在表单运行时触发这个动作。
如果您使用的是 Rasa SDK,我们建议您扩展提供的 FormValidationAction
. 使用FormValidationAction
时,提取海关槽位需要三个步骤:
-
extract_<slot_name>
为每个应该以自定义方式映射的插槽定义一个方法。 - 确保在域文件中为表单只列出那些使用预定义映射的插槽 。
- 覆盖
required_slots
以将具有自定义映射的所有插槽添加到表单应请求的插槽列表中。
下面的示例展示了一个表单的实现outdoor_seating
,除了使用预定义映射的插槽之外,它还以自定义方式 提取插槽 。该方法根据关键字是否出现在最后一条用户消息中来extract_outdoor_seating
设置槽。outdoor_seating
outdoor
1 from typing import Dict, Text, List, Optional, Any 2 3 from rasa_sdk import Tracker 4 from rasa_sdk.executor import CollectingDispatcher 5 from rasa_sdk.forms import FormValidationAction 6 7 8 class ValidateRestaurantForm(FormValidationAction): 9 def name(self) -> Text: 10 return "validate_restaurant_form" 11 12 async def required_slots( 13 self, 14 slots_mapped_in_domain: List[Text], 15 dispatcher: "CollectingDispatcher", 16 tracker: "Tracker", 17 domain: "DomainDict", 18 ) -> Optional[List[Text]]: 19 required_slots = slots_mapped_in_domain + ["outdoor_seating"] 20 return required_slots 21 22 async def extract_outdoor_seating( 23 self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict 24 ) -> Dict[Text, Any]: 25 text_of_last_user_message = tracker.latest_message.get("text") 26 sit_outside = "outdoor" in text_of_last_user_message 27 28 return {"outdoor_seating": sit_outside}
默认情况下,FormValidationAction
将自动将 设置为未填充requested_slot
的第一个插槽required_slots
。
动态表单行为#
默认情况下,Rasa Open Source 会从域文件中为您的表单列出的插槽中请求下一个空插槽。如果您使用 自定义插槽映射和FormValidationAction
,它将要求该required_slots
方法返回的第一个空插槽。如果required_slots
填写了所有插槽,则该表格将被停用。
如果需要,您可以动态更新表单所需的插槽。例如,当您需要基于前一个槽的填充方式的更多详细信息时,或者您想更改请求槽的顺序时,这很有用。
如果您使用的是 Rasa SDK,我们建议您使用FormValidationAction
和 覆盖required_slots
来适应您的动态行为。您应该extract_<slot name>
为每个不使用预定义映射的插槽实现一个方法,如自定义插槽映射 中所述。下面的示例将询问用户是否想坐在阴凉处或阳光下,以防他们说他们想坐在外面。
1 from typing import Text, List, Optional 2 3 from rasa_sdk.forms import FormValidationAction 4 5 class ValidateRestaurantForm(FormValidationAction): 6 def name(self) -> Text: 7 return "validate_restaurant_form" 8 9 async def required_slots( 10 self, 11 slots_mapped_in_domain: List[Text], 12 dispatcher: "CollectingDispatcher", 13 tracker: "Tracker", 14 domain: "DomainDict", 15 ) -> Optional[List[Text]]: 16 additional_slots = ["outdoor_seating"] 17 if tracker.slots.get("outdoor_seating") is True: 18 # If the user wants to sit outside, ask 19 # if they want to sit in the shade or in the sun. 20 additional_slots.append("shade_or_sun") 21 22 return additional_slots + slots_mapped_in_domain
request_slot 插槽#
该插槽requested_slot
将作为类型为 的插槽自动添加到域中text
。的值requested_slot
将在对话期间被忽略。如果你想改变这个行为,你需要将 加入requested_slot
到你的域文件中作为一个分类槽, influence_conversation
设置为true
。如果您想以不同的方式处理不愉快的路径,您可能想要这样做,具体取决于用户当前询问的插槽。例如,如果您的用户用另一个问题来回答机器人的一个问题,比如您为什么需要知道这一点? 对此explain
意图的反应取决于我们在故事中的位置。在餐厅案例中,您的故事将如下所示:
1 stories: 2 - story: explain cuisine slot 3 steps: 4 - intent: request_restaurant 5 - action: restaurant_form 6 - active_loop: restaurant 7 - slot_was_set: 8 - requested_slot: cuisine 9 - intent: explain 10 - action: utter_explain_cuisine 11 - action: restaurant_form 12 - active_loop: null 13 14 - story: explain num_people slot 15 steps: 16 - intent: request_restaurant 17 - action: restaurant_form 18 - active_loop: restaurant 19 - slot_was_set: 20 - requested_slot: cuisine 21 - slot_was_set: 22 - requested_slot: num_people 23 - intent: explain 24 - action: utter_explain_num_people 25 - action: restaurant_form 26 - active_loop: null
同样,强烈建议您使用 交互式学习来构建这些故事。
使用自定义操作请求下一个插槽#
一旦表单确定用户接下来必须填充哪个位置,它就会执行操作utter_ask_<form_name>_<slot_name>
或utter_ask_<slot_name>
要求用户提供必要的信息。如果常规话语还不够,您还可以使用自定义操作action_ask_<form_name>_<slot_name>
或 action_ask_<slot_name>
要求下一个槽。
1 from typing import Dict, Text, List 2 3 from rasa_sdk import Tracker 4 from rasa_sdk.events import EventType 5 from rasa_sdk.executor import CollectingDispatcher 6 from rasa_sdk import Action 7 8 9 class AskForSlotAction(Action): 10 def name(self) -> Text: 11 return "action_ask_cuisine" 12 13 def run( 14 self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict 15 ) -> List[EventType]: 16 dispatcher.utter_message(text="What cuisine?") 17 return []