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_nameNone,則無論意圖名稱如何,都將填充插槽。否則,僅當用戶的意圖為 時才會填充該插槽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_cityarrival_city 插槽,具體取決於請求的是哪個,因此如果cityarrival_date請求插槽時提取了實體,則表單將忽略它。

from_text #

from_text對映將使用下一個使用者說話的文字,以填補插槽 slot_name。如果intent_nameNone,則無論意圖名稱如何,都將填充插槽。否則,僅當用戶的意圖為 時才會填充該插槽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_nameNone。如果訊息的意圖是 ,則槽對映將不適用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時,提取海關槽位需要三個步驟:

  1. extract_<slot_name>為每個應該以自定義方式對映的插槽定義一個方法。
  2. 確保在域檔案中為表單只列出那些使用預定義對映的插槽 。
  3. 覆蓋required_slots以將具有自定義對映的所有插槽新增到表單應請求的插槽列表中。

下面的示例展示了一個表單的實現outdoor_seating,除了使用預定義對映的插槽之外,它還以自定義方式 提取插槽 。該方法根據關鍵字是否出現在最後一條使用者訊息中來extract_outdoor_seating設定槽。outdoor_seatingoutdoor

 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 []