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?
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)
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!
@rahulmittal79 glad it helped you out! Happy bot-building!
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
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
What do you mean by
the other methods
validate_<slot_name>
andrequired_slots
will not work
Are you trying to extend the behaviour of run
?
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:
- copy the custom_forms.py & customForms_config.yml
- Follow the instructions given in the CustomFormValidationAction docstring.
- use
CustomFormValidationAction
as the base class for your own validation class, like here:class ValidatePayCCForm(CustomFormValidationAction):
- add optional
explain_{slot}
methods, like here:async def explain_credit_card(...)
Oh, awesome! Thanks a lot!