To intentionally reject the form execution, you can also return an ActionExecutionRejected event as part of your custom validations or slot mappings.
To handle situations that might cause a form’s execution to be rejected, you can write rules or stories that include the expected interruptions. For example, if you expect your users to chitchat with your bot, you could add a rule to handle this:
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
In some situations, users may change their mind in the middle of the form action and decide not to go forward with their initial request. In cases like this, the assistant should stop asking for the requested slots.
You can handle such situations gracefully using a default action action_deactivate_loop which will deactivate the form and reset the requested slot. An example story of such conversation could look as follows:
stories:
story: User interrupts the form and doesn’t want to continue
steps:
I have a form, in which few slots are filled by predefined slot mapping and some by custom slot mapping, when a prompt for custom slot mapping is asked and if the user respond with FAQ/chitchat , my bot doesn’t respond back with FAQ/chitchat, Instead it will ask the prompt for that slot. This happens only during custom slot mapping prompts.
I think the below rule will work, but i have 3 forms, so i would have to write 6 rules (faq and chitchat)
The rules above will kick in if the form action is rejected. This might happen if a slot was requested, but then not filled, for example during a validation action. However, if you want the form loop to be picked up again, you will have to write those 6 rules you describe. This is also how we do this in our demo
In the above example it asks for slot name and slot check in, name is custom mapped slot and checkin in predefined mapping. During the prompt " what’s your name" (custom mapping), if a user respond with some faq question, the bot doesn’t reply with answer for that faq question, But during the prompt " what are the checkin and check-out time?" bot gives relevant faq reply.
I checked this demo, I have 3 forms so I should write 3 rules for each form , right? Or is it possible to write one single rule for any active loop?
"what are the check -in and check-out time " is an faq
This is how my custom slot mapping looks like
def name_change( dispatcher, tracker):
extractor_list = tracker.latest_message['entities']
old_name = tracker.get_slot("person")
person_name = None
if len(extractor_list)>0:
confid = 0
for elements in extractor_list[::-1]:
if elements['entity']== 'PERSON':
if elements['extractor'] == 'SpacyEntityExtractor':
person_name = elements['value']
break
if elements['extractor'] == 'DIETClassifier':
if elements['confidence_entity'] > confid:
person_name = elements['value']
if not old_name:
return person_name
if person_name:
dispatcher.utter_message(text=f"Hey!! Your name have been changed to {person_name}")
else:
dispatcher.utter_message(text=f"Yes you can change your name")
return person_name
class ValidateHotelBookingForm(FormValidationAction):
def name(self) -> Text:
return "validate_hotel_booking_form"
async def required_slots(
self,
slots_mapped_in_domain: List[Text],
dispatcher: "CollectingDispatcher",
tracker: "Tracker",
domain: "DomainDict",
) -> Optional[List[Text]]:
if tracker.get_slot('state_inter') is False:
return['state_final']
if tracker.get_slot('state_final') is not None:
return[]
custom_slot_list = ["hotelname","person"]
for i in custom_slot_list:
if not tracker.get_slot(i) :
return [i] + slots_mapped_in_domain
if tracker.get_slot("checkin") and tracker.get_slot("checkout") and not tracker.get_slot("count"):
return ["count"]
if room_details_filled(tracker):
return ['state_final']
return slots_mapped_in_domain
def extract_person(self, dispatcher, tracker, domain):
person_name = name_change(dispatcher, tracker)
return{"person":person_name}
async def validate(self, dispatcher, tracker, domain):
if intent == "name_negative":
person_name = name_change(dispatcher, tracker)
return [SlotSet('person', person_name)]
name_negative is an intent that means user wants to change his name