Fill form slot with intent of initial message

I was wondering if it’s possible to fill a form slot with the intent of the initial message that triggers the FormAction.

E.g. if I had something like this:

* order_drinks
  - form_order
  - form{"name": "form_order"}
  - form{"name": null}

And I have a slot called subject that can be ['drinks', 'food', ...] that I want to be filled in order for the submit method to act on it. E.g. my form has these methods:

def slot_mappings(self):
    return {
        "subject": [
            self.from_intent(
                intent='order_drinks',
                value='drinks'
            ),
            self.from_intent(
                intent='order_food',
                value='food'
            ),
        ]
    }

def submit(self, dispatcher, tracker, domain):
    subject = tracker.get_slot('subject')

    if subject == 'drinks':
        order_drinks()
    elif ...:
        order_something_else()
    else:
        dispatcher.utter_message("Unkown subject")

    return []

Which works for a story like this:

* order
  - form_order
  - form{"name": "form_order"}
  - form{"name": null}

Because then the bot would ask the user “What would you like to order?”, then the user could say “I want to drinks” or whatever, and then the submit action would work.

However, for the initial story from above, the user already says “I want to order drinks”, but the bot then asks “What would you like to order?” and the user has to say “Drinks” again.

So overall, the question could be summarized to "can I fill a form slot using the intent that triggered the form so that forms can be shared across different intents?"

I know I could do it like that:

* order_drinks
      - form_order
      - form{"name": "form_order"}
      - form{"name": null}
      - action_order_drinks

However, even though this probably would never be an issue, using e.g. the KerasPolicy it doesn’t guarantee that action_order_drinks will be executed. It’s left up to the core’s policies to make that call. However, I need to guarantee execution of action_order_drinks after form_order has requested all other necessary slots.

The only other way I can imagine as of now is to have a separate FormAction for every initial intent.

Any solutions for this?

1 Like

@akelad @Ghostvv is this possible at the moment? Any reason why slots are directly extracted, but intents are not?

1 Like

one possible solution to create common intent order with entity order_type that could contain drinks, then order_type slot should be populated automatically

or you would need to override validate function and check if tracker.active_form.get('name') != self.name() which means it is the first call to this form

Thanks @Ghostvv.

  • Option 1 would require an entity to be extracted though right? Or how would the slot be set to drinks or food in these cases:
    • Drinks: “I’m thirsty”, “I need a drink”, “I want some beer”
    • Food: “I’m hungry”, “I want something to eat”, …
  • Option 2 was my workaround for now, added these few lines to “extract initial intent like the form ones”:
# Set subject
if self._should_request_slot(tracker, 'subject'):
  initial_intent = tracker.latest_message['intent']['name']
  for mapping in self.slot_mappings()['subject']:
    if initial_intent in mapping['intent']:
      evts.append(SlotSet('subject', mapping['value']))

This was based on the assumption that the tracker.latest_message is always containing the last user message from before the form was activated, which seems to be the case (as all form events have type form and last_user_message requires type user). Is that future proof?

@Ghostvv: Do you think there’s a real use case for this? Should I file an issue / PR? Maybe it’s just me, but I just recently discovered that using one “generic form” for similar tasks makes training a lot easier and increases accuracy. For example, having one ManageCartFormAction rather than having separate AddProductToCart, RemoveProductFromCart, ModifyOrder, … forms which all require similar information, but pretty much only differ in the function being executed afterwards (on submit).

yes, for the option one, entity should be extracted by nlu

for option 2, I’m not sure how future proof it is. I would go with checking whether active form is not the same, in this case you can be sure that it is the first call and tracker.latest_message is the one that triggered it

Regarding the PR, I’m not sure what you mean

What I mean is that it may be helpful for others as well to be able to use forms like this:

* add_product
 - cart_form  # instead of add_product_form

* remove_product
 - cart_form  # instead of remove_product_form

* change_order
 - cart_form  # instead of change_order_form

And the cart_form knows how to deal with a add_product, remove_product or change_order request.

Reason being that there’s fewer actions that need to be trained, it’s more DRY, and you don’t need to retrain in case you add / remove / change functionality from the “cart form”.

Do I understand correctly, that you recommend to store the intent that triggered the form? In this case, I agree it is helpful functionality. I would create class variable like self.trigger_intent and populate it here: rasa_core_sdk/forms.py at 7f91132aecda9c4ea6f1c97a58a52ed5a7178efe · RasaHQ/rasa_core_sdk · GitHub

Yes, exactly, that’s what I meant. Sorry for the confusion. Because then you can implement your business logic in the FormAction.submit() method

makes sense to me. Also we could remove @staticmethod for required_slots and condition required slots on this intent. Do you mind creating a PR?

Oh, wait. I think just having a self.trigger_intent won’t work though. It would be nice if it’s still part of the slot extraction process.

Because then it would even be possible to have a story like:

* cart  # Some generic cart intent
 - cart_form
 - slot{"requested_slot": "action"}  # Bot asks "What do you want to do? Add to cart? Change order?" etc. Which would be filled with trigger intent if trigger_intent is specific
* form: add_product
 - cart_form  # now on .submit() bot could use `action` slot to execute correct task

yes, you’re right, sorry, we cannot have class properties

it should be part of rasa_core/trackers.py at 674ff4bfb03c04e6da165edd3f4e05cf325dd319 · RasaHQ/rasa_core · GitHub

and Form event

What about having a new slot mapping type from_trigger(intent=Optional[Text], value=Text) that can be used like this?

def slot_mappings(self) -> Dict[Text, Union[Dict, List[Dict]]]:
    return {
        "trigger_intent": [. # Or whatever this should be named
            self.from_trigger(intent=None, value='some_task_name'),
        ]
    }

then you could read it in any place inside the form

should work, but then a user should add such slot to domain

You’re right. That’s not handy.

I think making in tracker

            self.active_form = {'name': form_name,
                                'trigger_message': trigger_message,
                                'validate': True,
                                'rejected': False}

is a good solution but this change requires a bit of work

even better to store trigger_message there, that would contain all the information including entities

Would this be possible then though?

Even though this would just be the cherry on top as you could also handle the generic case with a separate story like this:

* cart
 - action_ask_action
* add_product
 - cart_form