Hi everyone,
I have two questions regarding the title of this thread. Imagine the following story:
## status_form
* trigger_status_form
- status_form
- form{"name": "status_form"}
- slot{"requested_slot": "permission"}
* iaachat
- respond_iaachat
- status_form
- slot{"requested_slot": "fullname"}
- form{"name": null}
- action_restart
## status_form
* trigger_status_form
- status_form
- form{"name": "status_form"}
- slot{"requested_slot": "permission"}
- slot{"requested_slot": "fullname"}
* iaachat
- respond_iaachat
- status_form
- slot{"requested_slot": "phonenumber"}
- form{"name": null}
- action_restart
The form guides the user through some questions regarding the status of an order. In between, I want the user to be able to ask any question he wants to ask and continue the form processing afterwards. Because I won’t know what the question of the user might be, I want to use the new ResponseSelector for “iaachat”. I have implemented everything, tested it and most of the time it works fine, except in two conflicting moments.
A sample dialogue could look like:
User : Hey I want to ask about the status of my order.
Bot : Okay, sure. I need to process personal data – are you okay with that?
Now the user currently would have four options:
-
“Yes” -> The form would proceed to the next step (due to slot mapping –> intent confirm –> True)
-
“No” -> The form would utter a template and return self.deactivate() (due to slot mapping –> intent Deny –> False)
-
“ChitChat” (Iaachat) ->
- ResponseSelector wins intent ranking for Iaachat and triggers an utterance that utters the predicted response.
- Form is still active, proceeds and asks for the slot again as a second utterance in the same response (utter_ask_slotname)
-
“ChitChat” (No known intent triggered) ->
- Since the form is not able to extract the required slot, the fallback is triggered by the bot
To make point 3 work – to let the bot utter the ResponseSelector-prediction AND to let the user know that there is the slot yet to fill, I need to throw “ActionExecutionRejection”, which is done in the validate-method and looks like:
# extract requested slot
if slot_to_fill:
slot_values.update(self.extract_requested_slot(dispatcher, tracker, domain))
if not slot_values:
# reject to execute the form action
# if some slot was requested but nothing was extracted
# it will allow other policies to predict another action
raise ActionExecutionRejection(
self.name(),
"Failed to extract slot {0} "
"with action {1}"
"".format(slot_to_fill, self.name()),
)
return []
If I use this implementation, the bot works as expected in this situation. Imagine, that the user responds:
User : Where is your booth?
Bot: Our booth is in Hall 3. (Utterance by response selector due to ExecutionRejection)
Bot : I need to process personal data – are you okay with that? (Utterance by still active form slot)
User : Hey you dirty freak!!!
This would cause again an ActionExecutionRejection because the slot can’t be extracted but it will trigger the fallback-action since there is:
- No ResponseSelector-intentthat is valid
- The requested slot could not be extracted
My expectation is, that the bot instead sais:
User : Hey you dirty freak!!!
Bot : Sorry, I couldn’t extract any confirmation or rejection. Please repeat. (Utterance by template utter_wrong_slotname)
Bot : I need to process personal data – are you okay with that! (Utterance by still active form)
To make this work, I need to implement the validate method like this:
# extract requested slot
if slot_to_fill:
slot_values.update(self.extract_requested_slot(dispatcher, tracker, domain))
if not slot_values:
# reject to execute the form action
# if some slot was requested but nothing was extracted
# it will allow other policies to predict another action
dispatcher.utter_template("utter_wrong_" + slot_to_fill, tracker)
return []
return self.validate_slots(slot_values, dispatcher, tracker, domain)
If I use this implementation, the bot would utter that it wasn’t able to set the slot and ask for it again. Why does he do that? Because I don’t throw the ActionExecutionRejection. Which is okay, because I don’t really want to reject it.
Currently, I solve this whole problem by using this implementation:
# extract requested slot
if slot_to_fill:
slot_values.update(self.extract_requested_slot(dispatcher, tracker, domain))
if not slot_values:
# reject to execute the form action
# if some slot was requested but nothing was extracted
# it will allow other policies to predict another action
dispatcher.utter_template("utter_wrong_" + slot_to_fill, tracker)
current_intent = tracker.latest_message['intent'].get('name')
if current_intent == 'iaachat':
raise ActionExecutionRejection(
self.name(),
"Failed to extract slot {0} "
"with action {1}"
"".format(slot_to_fill, self.name()),
)
return []
return []
return self.validate_slots(slot_values, dispatcher, tracker, domain)
Basically this checks if we got an ResponseSelector-Intent triggered and if so, we reject the form execution. If not, we utter to the user that we weren’t able to extract the wanted slot.
Question 1 : Is there a better solution to this?
Question 2 : Let’s say I want to give the user the opportunity to ask the Response-ChitChat(IaaChat) question during the whole FormAction . Do I really need to permutate the stories to reinsert * iaachat after every slot asked which means that I would end up with n storylines where n is the number of slots to ask for. Wouldn’t it be great to provide the opportunity to consolidate ChitChat-Response-Selector questions during FormProcessing with only one statement?
Regards Julian