RaSa2.5.x行为之四:表单(Forms)


最常见的对话模式之一是从用户那里收集一些信息,以便做一些事情(预订餐厅、调用API、搜索数据库等)。这也称为 插槽填充

Usage

要将Form与Rasa开源一起使用,需要确保将规则策略添加到策略配置中。例如:

policies:
- name: RulePolicy

定义Form(Defining a Form)

通过将表单添加到domain中的form部分来定义表单。表单的名称同时也是在故事或规则中执行处理表单操作的名称。您还需要为表单应填充的每个插槽定义插槽映射。可以为每个要填充的插槽指定一个或多个插槽映射。
以下示例表单restaurant_form将用提取的实体cuisine填充槽cuisine,实体number填充槽num_people

forms:
  restaurant_form:
    required_slots:
        cuisine:
          - type: from_entity
            entity: cuisine
        num_people:
          - type: from_entity
            entity: number

您可以在ignored_intents键下为整个表单定义要忽略的意图列表。ignored_intents下列出的意图将添加到表单中每个槽映射的not_intent键中。
例如,如果您不希望在聊天时填写表单的任何插槽,则需要定义以下内容(在表单名称之后,在ignored_intents关键字下):

forms:
  restaurant_form:
    ignored_intents: 
    - chitchat
    required_slots:
        cuisine:
          - type: from_entity
            entity: cuisine
        num_people:
          - type: from_entity
            entity: number

一旦表单操作被第一次调用,表单就会被激活,并提示用户输入下一个所需的槽值。它通过查找一个名为utter_ask_<form_name>_<slot_name>utter_ask_<slot_name>响应(如果找不到前者)来实现。请确保在domain文件中为每个所需的插槽定义这些响应。

激活Form(Activating a Form)

要激活表单,您需要添加一个故事规则,描述助理何时运行表单。在特定意图情况下触发表单,例如,可以使用以下规则:

rules:
- rule: Activate form
  steps:
  - intent: request_restaurant
  - action: restaurant_form
  - active_loop: restaurant_form

注意
active_loop: restaurant_form步骤表示表单在运行restaurant_form后被激活。

停用Form(Deactivating a Form)

一旦所有必需的槽位都被填满,表单将自动停用。你可以用一条规则或一个故事来描述你助理在表格结尾的行为。如果您没有添加适用的故事或规则,则助手将在表单完成后自动侦听下一条用户消息。下面的示例在表单your_form填充完所有必需的槽时立即运行语句utter_all_slots_filled

rules:
- rule: Submit form
  condition:
  # Condition that form is active.
  - active_loop: restaurant_form
  steps:
  # Form is deactivated
  - action: restaurant_form
  - active_loop: null
  - slot_was_set:
    - requested_slot: null
  # The actions we want to run when the form is submitted.
  - action: utter_submit
  - action: utter_slots_values

用户可能希望尽早脱离表单。有关如何编写此案例的故事或规则,请参见编写不愉快表单路径的故事/规则

Slot Mappings

Rasa附带了四个预定义的映射,用于根据最新的用户消息填充表单的槽。如果需要自定义函数来提取所需信息,请参阅自定义插槽映射

from_entity

from_entity映射基于提取的实体填充插槽。它将寻找一个名为entity_name的实体来填充一个slot_name槽。如果intent_nameNone,则无论intent_name是什么,槽都将被填充。否则,只有当用户的意图是intent_name时,才会填充槽。

如果提供了role_name和/或group_name,则实体的role/group标签还需要与给定的值匹配。如果消息的意图是excluded_intent,则槽映射将不适用。注意,还可以为参数intentnot_intent定义意图列表。

forms:
  your_form:                              # 表单名称
    required_slots:
        slot_name:                        # 插槽名称
        - type: from_entity               # 插槽填充类型
          entity: entity_name
          role: role_name
          group: group name
          intent: intent_name
          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,具有角色(role)from的实体city唯一地设置槽departure_city,具有角色to的实体city唯一地设置槽arrival_city,因此,即使没有请求这些槽,也可以使用它们来匹配相应的槽。但是,不带角色的实体city可以填充departure_cityarrival_city两个槽位,具体取决于请求的槽位,因此如果在请求槽位arrival_date时提取实体城市,则表单将忽略该实体城市。

from_text

from_text映射将使用下一个用户语句的文本填充槽slot_name。如果intent_nameNone,则无论意图名称是什么,槽都将被填充。否则,只有当用户的意图是intent_name时,才会填充槽。

如果消息的意图是excluded_intent,则槽映射将不适用。请注意,可以为参数intentnot_intent定义意图列表。

forms:
  your_form:
    required_slots:
        slot_name:
        - type: from_text
          intent: intent_name
          not_intent: excluded_intent

from_intent

如果用户意图为intent_nameNone,则from_intent映射将用值my_value填充槽slot_name。如果消息的意图是excluded_intent,则槽映射将不适用。注意,还可以为参数intentnot_intent定义意图列表。

注意
在窗体的初始激活期间,from_intent槽映射将不适用。要根据激活窗体的意图填充槽,请使用from_trigger_intent mapping

forms:
  your_form:
    required_slots:
        slot_name:
        - 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,则槽映射将不适用。注意,还可以为参数intentnot_intent定义意图列表。

forms:
  your_form:
    required_slots:
        slot_name:
        - type: from_trigger_intent
          value: my_value
          intent: intent_name
          not_intent: excluded_intent

为不愉快的表单路径编写故事/规则(Writing Stories / Rules for Unhappy Form Paths)

用户不会总是以您对他们的要求作出响应。通常,用户会问问题,聊天,改变主意,或者偏离快乐的道路。
当表单处于活跃状态时,如果用户的输入未填满请求的槽,则表单操作的执行将被拒绝,即表单将自动引发ActionExecutionRejection。以下是表单将引发ActionExecutionRejection的特定场景:

  • 已请求插槽,但用户最新一条消息没有填充该插槽,并且您没有定义用于验证插槽提取插槽的自定义操作。
  • 已请求插槽,但用于验证插槽提取插槽的自定义操作未返回任何SlotSet事件。

要有意拒绝表单执行,还可以将ActionExecutionRejected事件作为自定义验证或槽映射的一部分返回。
要处理可能导致表单执行被拒绝的情况,可以编写包含预期中断的规则或故事。例如,如果希望用户与bot聊天,可以添加一个规则来处理此问题:

rules:
- rule: Example of an unhappy path
  condition:
  # 条件是:表单处于活跃状态
  - active_loop: restaurant_form
  steps:
  # This unhappy path handles the case of an intent `chitchat`.
  - intent: chitchat
  - action: utter_chitchat
  # Return to form after handling the `chitchat` intent
  - 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

强烈建议您使用交互式学习构建这些规则或故事。如果你手写这些规则/故事,你很可能会错过重要的事情。

高级用法(Advanced Usage)

表单可以使用自定义操作(Custom Actions)完全自定义。

验证表单输入(Validating Form Input)

从用户输入中提取插槽值后,可以验证提取的插槽。默认情况下,Rasa只验证在请求插槽之后是否所有插槽被填满。
您可以实现一个自定义操作validate_<form_name>来验证任何提取的插槽。请确保将此操作添加到domainactions部分:

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检索提取的插槽,以完全自定义验证过程。

自定义插槽映射(Custom Slot Mappings)

如果预定义的槽映射都不适合您的用例,那么您可以使用自定义操作validate_<form_name>来编写您自己的提取代码。Rasa开源将在窗体运行时触发此操作。
如果您使用的是Rasa SDK,我们建议您扩展提供的FormValidationAction。使用FormValidationAction时,需要三个步骤来提取自定义槽:

  1. 为每个以自定义方式映射的插槽定义方法extract_<slot_name>
  2. 确定在domain文件中为表单只列出那些使用预定义映射的插槽
  3. 重写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 required_slots(
        self,
        slots_mapped_in_domain: List[Text],
        dispatcher: "CollectingDispatcher",
        tracker: "Tracker",
        domain: "DomainDict",
    ) -> Optional[List[Text]]:
        required_slots = slots_mapped_in_domain + ["outdoor_seating"]
        return required_slots

    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中指定的第一个未填充的插槽。

动态表单行为(Dynamic Form Behavior)

默认情况下,Rasa将从domain文件的表单下列出的插槽中请求下一个空插槽。如果使用自定义槽映射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,
        slots_mapped_in_domain: List[Text],
        dispatcher: "CollectingDispatcher",
        tracker: "Tracker",
        domain: "DomainDict",
    ) -> Optional[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 + slots_mapped_in_domain

The requested_slot slot

requested_slot将作为文本类型的槽自动添加到域中。对话期间将忽略requested_slot的值。如果要更改此行为,需要将requested_slot添加到domain文件中,并将其influence_conversation属性设置为true。如果希望以不同的方式处理不愉快的路径,则可能需要这样做,具体取决于用户当前请求的插槽。例如,如果你的用户用另一个问题回答机器人的一个问题,比如你为什么需要知道这个问题?对这种explain意图的反应取决于我们在故事中的位置。在餐馆的案例中,你的故事看起来是这样的:

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

同样,强烈建议您使用交互式学习来构建这些故事。

使用自定义操作请求下一个插槽(Using a Custom Action to Ask For the Next Slot)

一旦表单确定用户下一步要填充哪个插槽,它将执行操作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 []

参考

  1. 官方文档
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值