Rasa Form 表单使用

常见的对话模式: 从用户那里收集一些信息以执行某些操作(预订餐厅、调用 API、搜索数据库等),从用户收集信息的过程,称为"槽填充"(slot filling)。而 form 表单是用来实现此功能的。

用法

要在 Rasa 中使用Form表单,需要确保将 Rule Policy 策略添加到策略配置中。例如:

policies:
- name: RulePolicy

定义表单

需要将表单添加到domain的表单部分来定义表单。表单包括两部分:

  1. 表单的名称:可以在 story 或 rule 中用来处理表单操作的名称
  2. required_slots:表单需要搜集的槽位,是一个 list 列表

以下示例,定义了一个表单:

  1. 表单的名称是:restaurant_form
  2. 定义了两个slot(插槽) , 插槽 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
        - num_people

此处一定要注意 slot 的 type,如果 type 设置为 any,则不影响对话流程,如果 slot 设置为其他类型,并且 influence_conversation 设置为 true,在写story的时候必须显示的写出来 slot_was_set , 会在其他文档详细分析

Form 可以忽略掉一些 intent ,不进行槽位填充,通过 ignored_intents 实现, ignored_intents 下列出的意图将添加到每个插槽映射的 not_intent 键中

Eg: 下面代码实现当 intent 为 chitchat 时,不填充表单的任何槽位

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> 的response 来询问用户填槽。在域文件中需要为每个 slot 定义相对应的 response 。

激活 Form

要激活 Form 表单,在 story 和 rule 里面该如何写?

以 rule 为例,当用户意图满足特定意图就触发表单,可以使用以下规则:

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

以上rule 表示:当 intent 是 request_restaurant 的时候,激活表单

active_loop: restaurant_form 步骤表示在运行 action: restaurant_form 之后应激活表单,可以简单粗暴理解为这两个 step 组合起来是激活表单的操作:

  - action: restaurant_form
  - active_loop: restaurant_form

停用表单

一旦填满所有必需的插槽,表单将自动停用,也可提前终止表单,停用表单的 steps:

如下图所示,表示停用 name 为 restaurant_form 的 Form 表单

  - action: restaurant_form
  - active_loop: null

可以使用 rule 或 story 来描述 bot 在 form 末尾的行为。如果在表单结束之后不添加后续要执行的 action ,bot 将在表单完成后自动监听下一条用户消息。下面的示例表示在表单 restaurant_form 填满所有必需的槽后立即运行 utter_submit 和 utter_slots_values 这两个 action

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

用户不按照预期回答 Writing Stories / Rules for Unhappy Form Paths

用户不会总是按照预期去回应机器人向他们询问的信息,通常,用户会提出其他问题、闲聊、改变主意或以其他方式偏离我们预期的行为。

当表单处于激活状态时,如果没有从用户的输入中提取到对应请求的槽,则表单操作的执行将被拒绝,即表单将自动引发 ActionExecutionRejection。以下是表单将引发 ActionExecutionRejection 的特定场景:

  • 请求了一个 slot ,但用户没有用他们的最后一条消息填充该 slot ,并且您没有定义用于验证插槽或提取插槽的自定义操作。(简而言之,就是没提取到槽,不管什么原因)
  • 已请求槽,但您用于验证槽或提取槽的自定义操作未返回任何 SlotSet 事件。(这种是针对自定义的提取槽和验证槽的操作,不仅要给槽赋值,还要返回 SlotSet 事件,在我测试的Rasa 版本中,目前不需要返回 Slotset 事件了,只需要返回 dict 的 Python 对象, Eg: {slot_name: slot_value})

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

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

rules:
- rule: Example of an unhappy path
  condition:
  # Condition that form is active.
  - 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

在某些情况下,用户可能会在表单操作的中间改变主意并决定不继续他们的初始请求。在这种情况下,机器人应该停止 ask slot。

可以使用默认操作 action_deactivate_loop 处理此类情况,这将停用表单并重置 requested_slot 插槽。此类对话的示例 story 如下所示:

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> 来验证任何提取的插槽。确保将此 action 添加到 domain 的 actions 部分:

actions:
- validate_restaurant_form

当 form 被执行时,在每个用户回合后,它将运行您的自定义操作来验证最新填充的槽位。

这个自定义操作 validate_<form_name> 可以继承并扩展 FormValidationAction 类,以简化验证提取的槽位的过程。在这种情况下,您需要为每个提取的槽位编写名为 validate_<slot_name> 的函数。

以下示例显示了自定义 action 的实现,该操作验证名为 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

如果预定义的 slot mapping 都不能够满足需求, 可以自定义 action : extract_<slot_name> 来写自己的提取 slot 的代码, 当 form 运行的时候, Rasa 会触发这个 action,每次用户输入之后都会触发这个 action,先触发 extract_<slot_name> ,再触发 validate_<form_name>

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

  • 在您的 domain 文件中,列出您 form 表单所需的所有必填槽位

forms:
  form_last_four_digits:
    required_slots:
      - outdoor_seating

  • 槽位的详细信息,包括预定义和自定义映射

slots:
  outdoor_seating:
    type: bool
    # initial_value: false
    influence_conversation: false
    mappings:
      - type: custom

  • 为需要以自定义方式映射的每个槽位定义一个名为 extract_<slot_name> 的方法。在 domain.yml 文件中已使用自定义映射定义的每个槽位都必须有自己独立的 extract_<slot_name> 方法实现。

如果您在 domain 文件的槽位部分中添加了一个具有自定义映射的槽位,并且您只希望在自定义动作(通过扩展 FormValidationAction)的表单上下文中进行验证,请确保此槽位具有自定义类型的映射,并且槽位名称包含在表单的 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 将从 domain 文件 form 表单 列出的 slots, 请求下一个空槽位。如果使用自定义 slot mappings 和 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 插槽

槽位 requested_slot 会自动作为文本类型的槽位添加到Domain中。在对话过程中,requested_slot 的值将被忽略。如果您想改变这种行为,您需要将 requested_slot 添加到您的 Domain 文件中,作为带有 influence_conversation 设置为 true 的分类槽位。如果您想根据当前向用户询问的槽位来处理 unhappy 的路径,您可能想这样做。例如,如果您的用户对机器人的一个问题回复了另一个问题,比如“你为什么要知道这个?”这时候你的回答取决于我们在故事中的位置,可以理解为正在 ask 哪一个 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

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

自定义 ask 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 []

如果插槽有多个询问话术,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>

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值