Hello everyone ,
How can i return FollowupAction and list of Slotset in a custom Action ?
Thank you,
Hello everyone ,
How can i return FollowupAction and list of Slotset in a custom Action ?
Thank you,
Hi @Ahmed. Can you be more specific with what you mean by āreturnā? If you want to include Slotset to your custom actions, you can add the following at the end of your custom action class:
return [SlotSet("matches", result if result is not None else [])]
You can find a full example here: Actions
Hi @Ahmed
in addition to @Juste 's post, returning a FollowUp Action works like this:
from rasa_sdk.events import FollowupAction
def your_custom_action(...):
<code>
return [FollowupAction(name='your_action_name')]
as documented here:
https://rasa.com/docs/rasa/api/events/#force-a-followup-action
If you provide more information, more help is awaiting you!
Regards
But is there a way to do both at the same time?
E.G. Iād like to do return [FollowupAction(name='action_name'), SlotSet('slot1', value)]
As a workaround, I tried this approach, but then slot1 is never setā¦
class CustomAction(Action):
def run(...):
SlotSet('slot1', value) ***
return [FollowupAction(name='action_name')]
Would be nice to be able to set the slot outside of the return statement
I actually have the same question, albeit slightly different. Theoretically (AFAIK), this is valid (and is what I tried):
return [SlotSet('slot_name', None), FollowupAction(name='a_new_form')]
The idea I have is to reset some specific slot before redirecting. However, none of them was removed; and more strangely with that, some slots are asked again.
Also I wonder if that construct above implies order (SlotSet
before FollowupAction
), or the order is undetermined (and we have to use something like await
).
any solution? Still canāt set a slot and trigger a followup action, whether or not both are in the return statements.
Hi @liaeh,
can you elaborate where you actually want to return:
[FollowupAction(name='action_name'), SlotSet('slot1', value)]
I am asking because depending on the caller of the function that should return, the SDKs behaviour differs.
Kind regards
Julian
@JulianGerhard Hereās an overview -
I want to find recipe suggestions. I have various slots, one of which is called disliked_ingredients
. A separate action ActionFetchRecipes
gets recipe suggestions using an API and stores the list of hits
in a slot called found_recipes
. Then, my custom action ActionSuggestRecipe
is called, where I try to take an element from found_recipes
. If I canāt find any recipes, then I want to relax the constraints of the search.
So, if searching for a recipe initially with disliked_ingredients = ['olives', 'cilantro']
, then I want to remove one of the constraints, disliked_ingredients = ['olives']
.
class ActionSuggestRecipe(Action):
def run(self, dispatcher, tracker: Tracker, domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:
hits = tracker.get_slot("found_recipes")
try:
suggest = hits.pop()
.... carry on
except (IndexError, AttributeError): # there are no more hits to suggest
# remove something from disliked ingredients and search again
constraints = tracker.get_slot('disliked_ingredients')
if len(constraints ) > 1:
constraints.pop()
return [SlotSet('disliked_ingredients', constraints), FollowupAction("action_fetch_recipe")]
Hi @liaeh,
at the risk of overlooking it, but your UseCase seems like the perfect match for a FormAction itsself. Why donāt you actually use one? If you have to do it via ānormalā Actions, we will figure it out, but the way the SlotFilling behaviour is managed inside a FormAction fits your needs quite wellā¦
If you would be open to use a FormAction, I can come up with a simple implementation if you want me to. If not, give me a sign and Iāll take a look at the CustomAction thing.
Kind regards
Julian
Hi @JulianGerhard - I guess I shouldāve mentioned that I am using a FormAction already to fill the slots incl disliked_ingredients from the user input Hereās a broader overview of how things flow with a story:
## happy path recipe search
* greet
- utter_greet
* affirm
- utter_ask_number_users
* inform{"num_ppl" : "1"}
- action_decide_which_form
- recipe_form
- form{"name": "recipe_form"}
- form{"name": null}
- action_fetch_recipes
- action_suggest_recipe
* affirm
- action_share_recipe
* affirm
- utter_goodbye
* goodbye
- action_slot_reset
So my FormAction Recipe_Form
delegates fetching and suggesting a recipe once the form is filled. From how I saw it, the purpose of the form is to gather all necessary information. Do you have another suggestion?
Thanks
Hi @liaeh,
ok - good that you mentioned it now!
I assume that fetching the recipes is an asynchronous process. Can you tell me how long the request lasts?
Kind regards
Julian
@JulianGerhard It is actually executed synchronously - the request is really quick.
Hi @liaeh,
ok - then we would have several options to actually achieve your goal. Let me propose one of them:
fetch_recipes
method inside the FormActionfetch_recipes
based on the trackers slots filledfetch_recipes
again or b. setting the value of the last slot to None
(or even more), resulting in the Form not being able to be submitted. This way you would be able to ask for certain slots again, modify your way of asking for them and lots of more.I really like holding a logic inside a FormAction because the behaviour is controlled and you can influence it at almost every point.
Keep in mind: If your FormAction is designed such that every slot can be filled at any time, then there wonāt be a ālast slotā to be filled. If so, you can simply call fetch_recipes
inside every validator and in your method, you can check if every necessary is actually filled before executing the API call itsself.
Did you get the idea? If you are unsure how to implement it, you can contact me via Slack if you want to.
Kind regards
Julian
Thanks for the suggestion @JulianGerhard!
I see itās possible this way - but it sounds like itās a workaround because you cannot modify the state of the tracker. I guess this doesnāt answer the original question of why
return [FollowupAction(name='action_name'), SlotSet('slot1', value)]
doesnāt work in any custom action, in general. I can think of a few more use cases where being able to modify the tracker would also be helpful, so I want to find a way to modify a slot and return a followup action anytime, or at least be able to change slot values outside of a form/outside of a return statement like here:
def run(...):
SlotSet('slot1', value) ***
return [FollowupAction(name='action_name')]
Any ideas? thanks again!
Hi @liaeh,
I donāt think itās a workaround but I get your point.
Letās say we have to do it via a normal CustomAction in which we simply want to set one slot and force the next action - correct? I can reproduce that without your usecase if you are okay with that!?
Let me check this in detail and get back to you asap. I need to take the dog for a walk, but will respond straight afterwards if that would be ok!?
Kind regards
Julian
Yes, totally fine. Thanks - looking forward to seeing what you find
Hi @liaeh,
so here is what I did. I have a story:
## repeat_last_utterance
* repeat_last_question
- action_repeat_last_utterance
- action_restart
The action repeat_last_utterance
is implemented as follows:
class RepeatLastUtterance(Action):
def name(self) -> Text:
return "action_repeat_last_utterance"
def run(
self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any],
) -> List[Dict[Text, Any]]:
return [SlotSet('test_slot', 'RepeatLastUtterance'), FollowupAction('test_action')]
The action test_action
is implemented as follows:
class TestAction(Action):
def name(self) -> Text:
return "test_action"
def run(
self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any],
) -> List[Dict[Text, Any]]:
x = tracker.get_slot('test_slot')
dispatcher.utter_message(text='SLOT VALUE IS: {}'.format(x))
return []
So actually, if that what you want to achieve, would work, the bot would have to utter:
[
{
"recipient_id": "default",
"text": "SLOT VALUE IS: RepeatLastUtterance"
}
]
Which, you may have guessed it, he did. Here is the traceback:
2020-04-01 17:26:38 DEBUG rasa.core.actions.action - Calling action endpoint to run action 'action_repeat_last_utterance'.
2020-04-01 17:26:39 DEBUG rasa.core.processor - Action 'action_repeat_last_utterance' ended with events '[<rasa.core.events.SlotSet object at 0x000001CB999BC438>, <rasa.core.events.FollowupAction object at
0x000001CB999BC470>]'.
2020-04-01 17:26:39 DEBUG rasa.core.processor - Current slot values:
test_slot: RepeatLastUtterance
2020-04-01 17:26:39 DEBUG rasa.core.processor - Predicted next action 'test_action' with confidence 1.00.
2020-04-01 17:26:39 DEBUG rasa.core.actions.action - Calling action endpoint to run action 'test_action'.
2020-04-01 17:26:40 DEBUG rasa.core.processor - Action 'test_action' ended with events '[BotUttered('SLOT VALUE IS: RepeatLastUtterance', {"elements": null, "quick_replies": null, "buttons": null, "attachme
nt": null, "image": null, "custom": null}, {}, 1585754800.156991)]'.
2020-04-01 17:26:40 DEBUG rasa.core.processor - Current slot values:
test_slot: RepeatLastUtterance
2020-04-01 17:26:40 DEBUG rasa.core.policies.fallback - NLU confidence threshold met, confidence of fallback action set to core threshold (0.3).
2020-04-01 17:26:40 DEBUG rasa.core.policies.memoization - Current tracker state [None, {}, {'prev_action_listen': 1.0, 'intent_repeat_last_question': 1.0}, {'intent_repeat_last_question': 1.0, 'prev_action
_repeat_last_utterance': 1.0}, {'prev_test_action': 1.0, 'intent_repeat_last_question': 1.0}]
2020-04-01 17:26:40 DEBUG rasa.core.policies.memoization - There is no memorised next action
2020-04-01 17:26:40 DEBUG rasa.core.policies.form_policy - There is no active form
2020-04-01 17:26:40 DEBUG rasa.core.policies.mapping_policy - There is no mapped action for the predicted intent, 'repeat_last_question'.
2020-04-01 17:26:40 DEBUG rasa.core.policies.ensemble - Predicted next action using policy_0_TEDPolicy
2020-04-01 17:26:40 DEBUG rasa.core.processor - Predicted next action 'action_listen' with confidence 0.96.
2020-04-01 17:26:40 DEBUG rasa.core.processor - Action 'action_listen' ended with events '[]'.
2020-04-01 17:26:40 DEBUG rasa.core.lock_store - Deleted lock for conversation 'default'.
So this seems fine for me. Does it for you? If not, where is the misunderstanding?
Regarding the tracker: The Tracker is a āsimpleā dictionary. Without returning it, how would the caller know about its changes? The actions should be designed stateless, so actually you would need a bit of engineering to write an update-method that works systemwide.
Kind regards
Julian
Hi Julian, oh I see - so the way the tracker is designed, setting a slot outside the return statement would cause side effect changesā¦ Makes sense now why changes are made as they are!!
What version of rasa are you using? your repeat_last_utterance
action is exactly what I tried but didnāt work! Curious why.
Hi @liaeh,
actually I am using the latest Rasa version which is 1.9.5
. I wondered from the beginning why you had the impression that these things wonāt work so I assume that the problem resides in your code.
Can you post your exact custom action the Rasa version you are using?
Kind regards
Julian
I have the same issue here. I want to run followupaction and setslot at the same time, but it never worked. Has anyone solved this issue here? Thanks.