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