How can I quit from a Active Form


(Kevin Faveri) #1

Hello everyone…

I am in doubt about how I can exit an active form using the user input (“fromText” method in slot type).

I tested the “/restart” command and saw that it takes precedence over all other methods, then closing the active form and clearing the slots.

This is exactly what I want, but the problem is that I would like to use another trigger word instead of “/restart”, such as “/cancel” or “/quit”.

Is there any way I can create custom actions that have a trigger that is a priority to all the others, in order to achieve this purpose, which is to close an active form from the user’s text input (using the “fromText” user text input method for the slot)?


(Vladimir Vlasov) #2

there is default action action_deactivate_form that if predicted (you need to have corresponding stories) deactivates current active form.

another way is in your custom validate method return self._deactivate() if certain intent


(Kevin Faveri) #3

Well, I tried returning self._deactivate() in my custom validation, and it deactivate but there is a problem: if there is a required field not filled yet in the form it will concatenate the self._deactivate() + set required_slot action to the next required field. So I don’t like this approach since, lets imagine this scenario:

  • I have a FormAction with 3 required fields:
  • TICKET_TYPE (using slot_mapping = self.from_text())
  • TICKET_DETAILS (using slot_mapping = self.from_text())
  • TICKET_QUANTITY (using slot_mapping = self.from_text())

So, when I enter in this Form it will ask for TICKET_TYPE, which expect any text from the user.

Ok then, now let’s suppose, when the bot asks me about TICKET_DETAILS (utter_ask_TICKET_DETAILS) I want to cancel the form with “/cancel”. If I cancel the form using the command “/cancel” (catching this string in the custom validation method) and then return self._deactivate(), it appears to concatenate the form deactivation command + setting a required slot, like:

[‘Form(None)’, ‘SlotSet(key: requested_slot, value: None)’, ‘SlotSet(key: requested_slot, value: TICKET_DETAILS)’]

When it should be:

[‘Form(None)’, ‘SlotSet(key: requested_slot, value: None)’]

So, what is the problem with canceling from inside the custom validation method? Basically, when it appends the “SlotSet(key: requested_slot, value: TICKET_DETAILS)”, it will call utter_ask_TICKET_DETAILS, which doesn’t make any sense in this context since the form is already canceled (and yet the bot will ask for the new field).

So the problem with this approach is the bot uttering ‘utter_ask_TICKET_DETAILS’ even after deactivating the form, which doesn’t make sense since the user is already out of the form (now deactivated).

This scenario and behavior don’t occur, however, when typing “/restart” in the user input.


(Vladimir Vlasov) #4

I see your point, so self._deactivate() should be returned in request_next_slot(...) then, instead of asking the next question


(Kevin Faveri) #5

Okay, but I will need to do some validation, no?

Because if I haven’t canceled it should request the next slot and not deactivate the form…

I thought in the “run” function, check if there is the deactivate form command, and if it exists, do not concatenate the command to request the next slot command, like (check TODO on what I was planning to do):

def run(self, dispatcher, tracker, domain):

    events = self._activate_if_required(tracker)
    events.extend(self._validate_if_required(dispatcher, tracker, domain))

    temp_tracker = tracker.copy()
    for e in events:
        if e['event'] == 'slot':
            temp_tracker.slots[e["name"]] = e["value"]

    next_slot_events = self.request_next_slot(dispatcher, temp_tracker,
                                              domain)
    #TODO: Develop an condition (if) that if the deactivate command combination exists ([‘Form(None)’, ‘SlotSet(key: requested_slot, value: None)’]) do not extend the events list with events.extend(next_slot_events)
    if next_slot_events is not None:
        events.extend(next_slot_events)
    else:
        events.extend(self.submit(dispatcher, temp_tracker, domain))
        events.extend(self._deactivate())

    return events

I’m not, however, very familiar with Python yet, and I do not know what would be the ideal and more performative way of doing this condition. It would be of great help if you could help me with the writing of this condition in the best practices.

Also, is there any way to dispatch an event? Like, from inside the custom validation method, dispatch some event which is immediately and synchronously executed and only after that return the events of the custom validation method?


(Vladimir Vlasov) #6

there is no the best way. You can do validation, as you do now, then add condition in request_next_slot, smth along the line:

def request_next_slot(self,
                      dispatcher,  # type: CollectingDispatcher
                      tracker,  # type: Tracker
                      domain  # type: Dict[Text, Any]
                      ):
    # type: (...) -> Optional[List[Dict]]
    """Request the next slot and utter template if needed,
        else return None"""

    intent = tracker.latest_message.get("intent", {}).get("name")
    if intent == 'cancel':
        return self._deactivate()
    else:
        for slot in self.required_slots(tracker):
            if self._should_request_slot(tracker, slot):
                logger.debug("Request next slot '{}'".format(slot))
                dispatcher.utter_template("utter_ask_{}".format(slot),
                                          tracker,
                                          silent_fail=False,
                                          **tracker.slots)
                return [SlotSet(REQUESTED_SLOT, slot)]

        logger.debug("No slots left to request")
        return None

(Vladimir Vlasov) #7

run method of a custom action returns a list of event that are executed afterwards


(Kevin Faveri) #8

Yes, I tried along the lines of what I’ve written (with a custom run method) but it is executed afterward so it doesn’t serve for solving this scenario.

I’ve now rewritten the custom action using a custom ‘request_next_slot’ along the lines of what you have written and I consider this a satisfactory solution for my scenario.

Thank you so much for the help!