not in submit
though, then using trigger_intent
in tracker, it’ll be possible to change output of required_slots
so that correct next slot will be asked
I’m not sure I understand exactly what you mean. Having thought about it, I think a combination of both a self.trigger_intent
and a from_trigger_intent
might be best. Validate could then use self.trigger_intent
to populate a slot and the user can specify how to name the slot.
For example with a FormAction like this:
class FormAction(Action):
@staticmethod
def required_slots(tracker):
return ['order_type', 'action', ...]
def slot_mappings(self) -> Dict[Text, Union[Dict, List[Dict]]]:
return {
"order_type": [
self.from_text(intent='inform'),
self.from_trigger_intent(intent='add_drink', value='drink'),
self.from_intent(intent='add_drink', value='drink'),
...
],
"action": [
self.from_text(intent='inform'),
self.from_trigger_intent(intent='add_drink', value='add'),
self.from_intent(intent='add_drink', value='add'),
...
],
"other_slots": ...
}
def validate(...):
# would require to now also handle .from_trigger_intent
def submit(self, dispatcher: CollectingDispatcher, tracker: Tracker,
domain: Dict[Text, Any]) -> List[Dict]:
action = tracker.get_slot('action')
order_type = tracker.get_slot('order_type')
# Some logic to trigger correct task
tasks = {
"add": {
"drink": add_drink, # or some other function call
...
},
...
}
task = tasks.get(action, {}).get(order_type)
if task:
# execute task
res = task(dispatcher, tracker, domain)
else:
# handle error
return []
… one could handle all kinds of cases like this
* order
- cart_form
- slot{"requested_slot": "order_type"}
* form: drink
- cart_form
- slot{"requested_slot": "action"}
* form: add
- cart_form # adds a drink
* add_drink
- cart_form # directly adds drink
Then you could even fill multiple slots with the trigger_intent
and have the form fill them for you if the request is not specific enough.
What do you think?
we cannot have self.trigger_intent
because the Action should be stateless
submit
is called only after all required_slots are populated
Ah. Ok I get it. Isn’t tracker.latest_message['intent']
working though?
I suggest doing similar to Slot Filling
tracker.latest_message[‘intent’] is the latest message, so on the second question it is not the message that triggered the form
You’re right, but if we have a .from_trigger_intent()
it could populate the slots right on the first .validate()
call. And afterwards the slots will be filled by the .from_intent()
slot mappings.
But of course we’d lose the self.trigger_intent functionality. But the user could add a:
"trigger_intent": [self.from_trigger_intent(...)]
If he still needs access to trigger intent
I’m not sure what you mean
following my suggestion, it would be always available in tracker.active_form.get('trigger_message', {}).get("intent", {}).get("name")
Yes, I’m fine with that. What I meant is that we can use your suggestion to fill slots with some value based on the trigger intent, e.g. if we modified the intent_is_desired
and extract_requested_slot
functions like this
@staticmethod
def intent_is_desired(requested_slot_mapping, tracker):
# **add this**
mapping_type = requested_slot_mapping.get("type")
if mapping_type == 'from_trigger_intent':
# **your suggestion**
intent = tracker.active_form.get(
'trigger_message', {}).get("intent", {}).get("name")
else:
intent = tracker.latest_message.get("intent",
{}).get("name")
return ((not mapping_intents and intent not in mapping_not_intents) or
intent in mapping_intents)
def extract_requested_slot(self,
dispatcher, # type: CollectingDispatcher
tracker, # type: Tracker
domain # type: Dict[Text, Any]
):
...
elif mapping_type == "from_intent":
value = requested_slot_mapping.get("value")
# **add this**
elif mapping_type == "from_trigger_intent":
value = requested_slot_mapping.get("value")
....
return {}
one could just turn on / turn off whether he wants slots to be filled from the trigger intent as well. Enabling stories like the one above:
I see, yes, this looks fine, but we need this trigger_intent
first
Do you mind creating PRs to both rasa_core
and rasa_core_sdk
to enable this capability?
Sure, I’ll try to submit one by the end of today. Just to avoid any upfront issues:
- for core, I’ll just update the
change_form_to
method here - for sdk: If
rasa_core
is updated, shouldn’t SDK’s tracker be initialized with the new active form dictionary already? Isn’t therasa_core_sdk
'stracker.active_form
just initialized with whatever is received as JSON here
And then of course I’ll add the changes from above.
in core
you also need to update Form event to take trigger_message
together with form name
indeed for sdk
, active_form
should be init correctly.
but in sdk
Form event should be updated according to core
changes in this event
and forms in sdk
should be updated to pass message
to Form
event during activation
If I do this, wouldn’t this require that the story format for forms changes as well? E.g.
* order_drinks
- form_order # Should now return [Form(self.name(), trigger_message)]
- form{"name": "form_order", "trigger_message": ...} # Isn't this necessary then?
- form{"name": null}
Or how would the Form(Event)
be initialized? And in Form.__init__()
we also don’t have access to the tracker to get the latest message.
If so, what would be the issue with modifying just the change_form_to
method? E.g.
# rasa_core.trackers.py
def change_form_to(self, form_name: Text) -> None:
"""Activate or deactivate a form"""
if form_name is not None:
self.active_form = {'name': form_name,
'validate': True,
'rejected': False,
'trigger_message': self.latest_message}
else:
self.active_form = {}
This method is only called on activation or deactivation of a form, right? So when a form gets activated, we’d set the trigger_message in the tracker.active_form attribute to tracker.latest_message (which now should be the last user message before the form got activated). Isn’t it sufficient that only the tracker sets the trigger_message
but the Form(Event) handling stays the same?
yes but in the forms.py, so that the stories will be the same
def _activate_if_required(self, tracker):
# type: (Tracker) -> List[Dict]
"""Return `Form` event with the name of the form
if the form was called for the first time"""
if tracker.active_form.get('name') is not None:
logger.debug("The form '{}' is active"
"".format(tracker.active_form))
else:
logger.debug("There is no active form")
if tracker.active_form.get('name') == self.name():
return []
else:
logger.debug("Activated the form '{}'".format(self.name()))
return [Form(self.name(), tracker.latest_message)]
rasa_core.trackers.py
def change_form_to(self, form_name: Text, message) -> None:
"""Activate or deactivate a form"""
if form_name is not None:
self.active_form = {'name': form_name,
'validate': True,
'rejected': False,
'trigger_message': message}
else:
self.active_form = {}
@Ghostvv added two PRs:
-
rasa_core
: Fill form slots from form trigger message intent by smn-snkl · Pull Request #1679 · RasaHQ/rasa_core · GitHub -
rasa_core_sdk
: Trigger message by smn-snkl · Pull Request #44 · RasaHQ/rasa_core_sdk · GitHub
I believe PR is not final yet, but feel free to review general idea already.
Thanks, I’ll take a look