I am having trouble handling user deviations while inside a form. I have uncooperative paths where any chitchat is followed by the appropriate chitchat action, in the training data. But the bot seems to always predict form_action, even though the intent is identified well as chitchat.
I tried many things but it seems the bot doesn’t follow the path in the training data once form action is started.
So I got it working in a way that was fine for my requirements. But I am not sure this is best practice.
The problem was the form policy was always predicting the form_action as the next action with a confidence of 1 even if unhappy paths were in the training data.
My solution was to throw an error that would exit the form like this:
print(value + “IS THE WRONG TYPE FOR BIRTHDATE”)
dispatcher.utter_template(‘utter_default’, tracker)
# validation failed, set slot to None
slot_values[slot] = None
And then Keras policy came into play, and this policy would predict the next action correctly following what happens in the training data (chitchat). I have a chitchat_action that handles all chitchats and returns a followup action. This followup action would be the same action that happened in the event that lead to action_chitchat.
class SymptomForm(FormAction):
"""Example of a custom form action"""
def name(self):
return "symptom_form"
@staticmethod
def required_slots(tracker):
"""
A list of required slots that the form has to fill
:param tracker:
:return:
"""
positive = tracker.get_slot('positive')
cirrhosis = tracker.get_slot('cirrhosis')
if positive is False:
return ['positive']
elif cirrhosis is True:
return ['positive', 'cirrhosis']
else:
return ['positive', 'cirrhosis', 'age', 'GPT', 'GOT', 'DNA']
def slot_mappings(self):
return dict(
positive=[self.from_entity(entity='logic'),
self.from_intent(intent='symptom_info', value="no additional preferences")],
cirrhosis=[self.from_entity(entity='logic'),
self.from_intent(intent='symptom_info', value="no additional preferences")],
age=self.from_entity(entity='number'),
GPT=self.from_entity(entity='number'),
GOT=self.from_entity(entity='number'),
DNA=self.from_entity(entity='number')
)
def submit(self, dispatcher, tracker, domain):
"""
Define what the form has to do
after all required slots are filled
:param dispatcher:
:param tracker:
:param domain:
:return:
"""
Instruct().run(dispatcher, tracker, domain)
return []
def validate(self,
dispatcher,
tracker,
domain):
"""Validate extracted requested slot
else reject the execution of the form action
"""
slot_values = self.extract_other_slots(dispatcher, tracker, domain)
slot_to_fill = tracker.get_slot(REQUESTED_SLOT)
if slot_to_fill:
slot_values.update(self.extract_requested_slot(dispatcher,
tracker, domain))
if slot_to_fill == 'positive' or slot_to_fill == 'cirrhosis':
value = tracker.get_slot('logic')
if not value or value not in ['yes', 'no']:
dispatcher.utter_template('utter_wrong_bool', tracker)
# validation failed, set slot to None
slot_values[slot_to_fill] = None
else:
isTrue = True if value == "yes" else False
slot_values[slot_to_fill] = isTrue
slot_values['logic'] = None
# validation succeed, set the slots values to the extracted values
else:
value = tracker.get_slot('number')
if not value or not value.isdigit():
dispatcher.utter_template('utter_wrong_number', tracker)
slot_values[slot_to_fill] = None
slot_values['number'] = None
return [SlotSet(slot, value) for slot, value in slot_values.items()]