How do I deactivate a form during validation?

I have a RASA form which takes multiple from_text inputs and I need to set a condition to deactivate the form based on a user input. How do I do this in the actions file?

1 Like

You can do this in the slot validation action by returning an ActionExecuted event with the action action_deactivate_form. Read more about this here

Hi, could you show me an example of the implementation?

Actually, Iā€™m realising now that you canā€™t do this with the standard form validation action as it expects a dictionary of slots and their values, not a list of events like some of the other actions. Sorry about the confusion.

I think the better way to do this is handle this with a story:

stories:
- story: User interrupts the form and doesn't want to continue
  steps:
  - intent: request_restaurant
  - action: restaurant_form
  - active_loop: restaurant_form
  - intent: stop
  - action: utter_ask_continue
  - intent: stop
  - action: action_deactivate_loop
  - active_loop: null

My slot handles a from_text input, so using stories didnā€™t seem to work for me

What if you got around this with another slot? It could be a boolean, for example:

slots:
   quit_form:
    type: bool
    influence_conversation: true

Then in your validation action, you could do something like this for your from_text slot.

    def validate_other_slot(
        self,
        value: Text,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> Dict[Text, Any]:
        """Validate num_people value."""
        if "quit" in other_slot:
            return {"quit_form": True}
        else:
            # do other validation here
            return {"other_slot": value}

Add a story:

stories:
- story: User interrupts the form and doesn't want to continue
  steps:
  - intent: request_restaurant
  - action: restaurant_form
  - active_loop: restaurant_form
  - intent: inform
  - slot_was_set:
    - quit: True
  - action: utter_ask_continue
  - intent: deny
  - action: action_deactivate_loop
  - active_loop: null

Note I havenā€™t run this code, so there may be typos or syntax errors. It should just give you a general idea.

For some reason I keep getting the TimeOut error when I implement that? I read somewhere that returning ActiveLoop to None might work

That would look something like this:

class ValidateForm(FormValidationAction):
    """Example of a form validation action."""

    def name(self) -> Text:
        return "validate_your_form"

    async def run(
            self,
            dispatcher: "CollectingDispatcher",
            tracker: "Tracker",
            domain: "DomainDict",
    ) -> List[EventType]:
        if "quit" in tracker.latest_message.get("text"):
            return [ActiveLoop(None), SlotSet(REQUESTED_SLOT, None)]
        await super().run(dispatcher, tracker, domain)
1 Like

Yes, this option seemed to work for me, but for some reason running that code doesnā€™t help me validate my other slots.

for example when I set a slot to {ā€˜Courseā€™: None}, the bot skips validation and asks for the next slot

Well yes, this was assuming you want to quit the form and skip validating the other slots. It returns ActiveLoop(None), SlotSet(REQUESTED_SLOT, None) before validation happens.

You can also return this after validation.

Iā€™m not sure what you mean by this:

when I set a slot to {ā€˜Courseā€™: None}

Are you setting the slot in a validation method? Or in run?

Sorry for being vague, what I meant was that when I run the function

class ValidateForm(FormValidationAction): ā€œā€ā€œExample of a form validation action.ā€""

def name(self) -> Text:
    return "validate_your_form"

async def run(
        self,
        dispatcher: "CollectingDispatcher",
        tracker: "Tracker",
        domain: "DomainDict",
) -> List[EventType]:
    if "quit" in tracker.latest_message.get("text"):
        return [ActiveLoop(None), SlotSet('requested_slot', None)]
    await super().run(dispatcher, tracker, domain)

it works and when there is quit in my message the form exits, but now the problem is that I have a slot called ā€˜Courseā€™, and I have a condition which sets ā€˜Courseā€™ to None based on the users input. So when Iā€™m running the above function and the user inputs the message (not deactivating the form) that should set the ā€˜Courseā€™ slot to None, it takes that in as the input and carries on to the next slot without re-asking for the ā€˜Courseā€™ slot. whereas when I donā€™t run the above code the validation where I set the ā€˜Courseā€™ slot to None works

No worries, just trying to make sure I understand.

That seems weird. Could you share the validation method for Course?

Very helpful advice in this particular post! Itā€™s the little changes that make the largest changes. Thanks for sharing!

1 Like

@rahulmittal79 glad it helped you out! Happy bot-building! :slight_smile:

Hello @fkoerner!

This is exactly what I want. But if I implement run in my FormValidationAction, the other methods validate_<slot_name> and required_slots will not work, and the default actions happen instead like if they were not implemented.

We really need a way to be able to return events in the validate_<slot_name> and required_slots methods :frowning:

Hello Chris, I understand that request! However, the reason we donā€™t allow for this is to keep things somewhat simple (also from the bot-builderā€™s perspective). You can always implement a custom action that does exactly what you want :slight_smile:

What do you mean by

the other methods validate_<slot_name> and required_slots will not work

Are you trying to extend the behaviour of run?

1 Like

Yes I totally understand from your side.

This is my FormValidationAction simplified:

class ValidateFormLogIn(FormValidationAction):
    def name(self):
        return 'validate_form_log_in'

    async def required_slots(self, predefined_slots, dispatcher, tracker, domain):
        # predefined_slots = ['password', 'username']
        # Doing that because Rasa X sorts them alphabetically
        return [predefined_slots[1], predefined_slots[0]]

    async def validate_username(self, value, dispatcher, tracker, domain):
        # Check if username exists in the database
            return {'username': username}
        # else
            return {'username': None}

    async def validate_password(self, value, dispatcher, tracker, domain):
        # Check if password matches with the database
            return {'password': secret, 'loggedin': True, 'password_tries': 0}
        # else
            return {'password': None, 'loggedin': False, 'password_tries': password_tries+1}

I want the form to stop after 3 wrong attempts, so I created a password_tries slot that increases inside the validate_password() method.

I want to exit the form if that happens, so I added:

    async def run(self, dispatcher, tracker, domain):
        if tracker.get_slot('password_tries') >= 3:
            return [ActiveLoop(None), SlotSet('requested_slot', None), SlotSet('username', None), SlotSet('password', None), SlotSet('login_type', None), SlotSet('password_tries', 0)]
        await super().run(dispatcher, tracker, domain)

But after doing that, the FormValidationAction stops working.

It starts asking for the password before the username, and validate_username() and validate_password() do not work/run.

There are print()s inside the methods, and theyā€™re not appearing in the Action Server logs.

Itā€™s as if the ValidateFormLogIn class didnā€™t exist.


You also added ActiveLoop(None) in the return of run(), but this is not an event listed here.

Is it in rasa.core.events?

Hi @ChrisRahme ,

To deactivate a form, you can simply return this from a validate_{slot} method:

return { "requested_slot": None}

That will deactivate the Form.

The dictionary of slots returned from a validate_<slot_name> will be converted into SlotSet events by the caller before it is sent back to Rasa. Note that it is not needed to also return an ActiveLoop(None) event. Just setting the requested_slot to None is sufficient.

If you do nothing else, the bot will just go and listen, which might confuse the user.

We implemented something similar in the financial-demo starter pack, but went a little further, to support this logic:

  • Keep track of the number of repeated validation failures, using a special slot
  • After a certain number of failures:
    • explain the slot to the user
    • reset the counter
    • ask if they want to continue with the form
      • If the user answers yes, keep going
      • If the user answers no, deactivate the form
        • When the form is deactivated, a rule calls a custom action that confirms that the form was stopped

This way, the bot is always trying to help the user, and provides a nice option to exit a form.

This logic was abstracted away in a CustomFormValidationAction. If you want to use this logic in your bot, you can do so with minimal effort by:

7 Likes

Oh, awesome! Thanks a lot!