FollowUp action disallowed from custom action

Hi all,

I am currently building a chatbot that will fill forms to know how to better help a user and provide solutions. I would like to allow my chatbot to be able to detect when the user intent switches, be able to ask for confirmation, then deactivate the current active loop and start a new one for the newly detected intent.

I know that it is advised to provide unhappy paths on rules or stories to manage this type of behaviour, but I would like my chatbot to manage over 10 intents starting forms, and this would mean writing an unmanageable amount of unhappy path.

Instead, I have built this custom action to try and manage all of those unhappy path at once. It detects intents that are not related to the current form, and I would like it to call utter_pi_outside_form to ask for confirmation and then switch path if an affirm is detected, or keep the current active loop if a deny is detected.


### ACTION_ASK classes
class ActionGetParentIntent(Action):
   def name(self) -> Text:
      return "action_check_parent_intent"

   def run(self,
           dispatcher: CollectingDispatcher,
           tracker: Tracker,
           domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:
        intent = tracker.latest_message['intent'].get('name')
        lambda x: x.replace(' ', '_')
        parent_intents = df['Intentions Parents DialOnce (Super admin)'].unique()
        converter = lambda x: x.replace(' ', '_')
        parent_intents = list(map(converter, parent_intents))
        if intent in parent_intents:
            # If intent slot empty, apply filter
            print(tracker.get_slot('current_intent'))
            if not tracker.get_slot('current_intent'):
                print('Setting filter for intent first one')
                global filt_tot
                filt_tot = (df['Intentions Parents DialOnce (Super admin)'] == intent.replace('_', ' '))
            elif tracker.get_slot('current_intent') != intent:
                print('New intent, different from previous one')
                dispatcher.utter_message(text= f"Nous venons de detecter un \
                changement d'intention de votre part. Pouvez-vous nous confi\
                    rmer qu'il ne s'agit pas d'une erreur, et que vous voulez \
                    bien {intent.replace('_', ' ')}, et non pas \
                    {tracker.get_slot('current_intent').replace('_', ' ')}")
                return [SlotSet("current_intent", intent if intent is not None else "Not applicable"),FollowupAction('utter_pi_outside_form')]

            return [SlotSet("current_intent", intent)]

When I run my chatbot, it is able to detect intent switching successfully, but unfortunately, I get this error.

2022-03-05 14:08:09 INFO     rasa.core.actions.action  - 
Running custom action 'action_check_parent_intent' has resulted in an event of type 'followup'. This is disallowed and the tracker will not be updated with this event.

Could this be because I don’t have rules managing this type of path yet?

I have tried using Followup() events on other custom actions of type action_ask_<slot_name> and they work without an issue.

Thanks for reading, I would really appreciate any help here :slight_smile:

2 Likes

Good Monday Rasa community :slight_smile:

Any ideas on this?

Same problem… Any updates?

Hi,

I have the same problem.

The cause of the problem is that when a form is active it ignores the rules. It will be explained in this section of the documentation Rules

In order to exit the form it is necessary for the ActionExecutionRejected action to happen, which I think happens when a slot is not filled after asking for it.

I’m also trying to exit a form from a custom action using the event FollowupAction but I haven’t figured out how yet.

I have tried to return as events LoopInterrupted and ActionExecutionRejected, but they do not execute and I get the message This is disallowed and the tracker will not be updated with this event.

1 Like

Hi @Belen ,

Thanks for the ideas. I haven’t tried LoopInterrupted nor ActionExecutionRejected but since you are getting the same error, I am going to try something different.

Since the only thing I am able to return from the function detecting a change of intent is a slot, I created a slot ‘change_of_heart’ trying to save the fact that the user is on an unhappy path. I am then trying to write rules that could allow me to do so.


- rule: Change of Heart
  steps:
  - slot_was_set: 
    - change_of_heart: true
  - action: utter_pi_outside_form


- rule: Submit Access Espace Client Form
  condition:
  - active_loop: acces_espace_client_form
  - slot_was_set:
    - change_of_heart: false
  steps: 
  - action: acces_espace_client_form
  - active_loop: null
  - slot_was_set: 
    - requested_slot: null
  - action: action_ask_solution

- rule: Unhappy Access Espace Client Form
  condition:
  - active_loop: acces_espace_client_form
  - slot_was_set:
    - change_of_heart: true
  steps:
  - slot_was_set: 
    - change_of_heart: true
  - action: utter_pi_outside_form


# Connaitre conditions d'application
- rule: Activate conditions d'application
  condition:
  - slot_was_set:
    - change_of_heart: false
  steps:
  - intent: Connaître_les_conditions_d_application
  - action: Connaître_les_conditions_d_application_form
  - active_loop: Connaître_les_conditions_d_application_form
 

As you can see on the rules above, I am trying to allow the user to exit the active loop a change of event is detected, and also force the action: utter_pi_outside_form.

However, I am getting conflicts when I did the following path:

- story: interactive_story_1
  steps:
  - intent: Accéder_à_mon_espace_client
  - slot_was_set:
    - current_intent: Accéder_à_mon_espace_client
  - action: acces_espace_client_form
  - active_loop: acces_espace_client_form
  - slot_was_set:
    - requested_slot: produit
  - intent: Connaître_les_conditions_d_application
  - slot_was_set:
    - current_intent: Connaître_les_conditions_d_application
  - slot_was_set:
    - change_of_heart: true
  - action: utter_pi_outside_form
  - intent: affirm
  - action: action_restart
InvalidRule: 
Contradicting rules or stories found 🚨

- the prediction of the action 'utter_pi_outside_form' in story 'interactive_story_1' is contradicting with rule(s) 'Activate conditions d'application' which predicted action 'Connaître_les_conditions_d_application_form'.
Please update your stories and rules so that they don't contradict each other.
You can find more information about the usage of rules at https://rasa.com/docs/rasa/rules. 

What do you think? Why are my conditions not being picked up by the rules?

In this part of the documentation Forms it says that

To intentionally reject the form execution, you can also return an ActionExecutionRejected event as part of your custom validations or slot mappings.

However, when I try it it doesn’t work and I get the following message:

Running custom action ‘action_extract_question_counter’ has resulted in an event of type ‘action_execution_rejected’. This is disallowed and the tracker will not be updated with this event.

Your rules don’t work because you’re inside a form, and are only taken into account when the ActionExecutionRejected action is detected.

In this forum topic Deactivate a form based on a slot value via story or rule - #2 by JulianGerhard they propose to solve the problem by dividing the form into several smaller forms.

Hi @Belen,

I solved the problem using the solution described in the message attached. I am using a slot set in action_check_parent_intent + conditions in the rules. This will help me code one rule for happy path and one rule for unhappy path for each form. Please find below the example:

action.py


class ActionGetParentIntent(Action):
   def name(self) -> Text:
      return "action_check_parent_intent"

   def run(self,
           dispatcher: CollectingDispatcher,
           tracker: Tracker,
           domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:
        intent = tracker.latest_message['intent'].get('name')
        lambda x: x.replace(' ', '_')
        parent_intents = df['Intentions Parents DialOnce (Super admin)'].unique()
        converter = lambda x: x.replace(' ', '_')
        parent_intents = list(map(converter, parent_intents))
        if intent in parent_intents:
            # If intent slot empty, apply filter
            print(tracker.get_slot('current_intent'))
            if not tracker.get_slot('current_intent'):
                print('Setting filter for intent first one')
                global filt_tot
                filt_tot = (df['Intentions Parents DialOnce (Super admin)'] == intent.replace('_', ' '))

            elif tracker.get_slot('current_intent') != intent:
                print('New intent, different from previous one')
                dispatcher.utter_message(text= f"Nous venons de detecter un \
                changement d'intention de votre part. Pouvez-vous nous confi\
                    rmer qu'il ne s'agit pas d'une erreur, et que vous voulez \
                    bien {intent.replace('_', ' ')}, et non pas \
                    {tracker.get_slot('current_intent').replace('_', ' ')}")
                return [SlotSet("current_intent", intent if intent is not None else "Not applicable"),SlotSet("change_of_heart", True)]
            return [SlotSet("current_intent", intent if intent is not None else "Not applicable")]

rules.yml


- rule: Change of Heart
  steps:
  - slot_was_set: 
    - change_of_heart: true
  - action: utter_pi_outside_form

## Rules for form management
- rule: parent intent outside of form
  steps:
  - action: pi_outside_form
  - intent: affirm
  - active_loop: null
  - action: action_restart
  - action: pi_form_start

## Rules for form management
- rule: parent intent outside of form
  steps:
  - action: pi_outside_form
  - intent: deny
  - active_loop: null
  - action: action_restart
  - action: pi_form_start

# ## Rules for form management
# - rule: parent intent outside of form
#   steps:
#   - action: pi_outside_form
#   - intent: deny
#   - action: pi_form_continue

### Intention forms
# access espace client
- rule: Activate Access Espace Client Form
  condition:
  - slot_was_set:
    - change_of_heart: false
  steps:
  - intent: Accéder_à_mon_espace_client
  - action: acces_espace_client_form
  - active_loop: acces_espace_client_form
 
- rule: Submit Access Espace Client Form
  condition:
  - active_loop: acces_espace_client_form
  - slot_was_set:
    - change_of_heart: false
  steps: 
  - action: acces_espace_client_form
  - active_loop: null
  - slot_was_set: 
    - requested_slot: null
  - action: action_ask_solution

- rule: Unhappy Access Espace Client Form
  condition:
  - active_loop: acces_espace_client_form
  - slot_was_set:
    - change_of_heart: true
  steps:
  - slot_was_set: 
    - change_of_heart: true
  - action: utter_pi_outside_form


# Connaitre conditions d'application
- rule: Activate conditions d'application
  condition:
  - slot_was_set:
    - change_of_heart: false
  steps:
  - intent: Connaître_les_conditions_d_application
  - action: Connaître_les_conditions_d_application_form
  - active_loop: Connaître_les_conditions_d_application_form
 
- rule: Submit conditions d'application
  condition:
  - active_loop: Connaître_les_conditions_d_application_form
  - slot_was_set:
    - change_of_heart: false
  steps: 
  - action: Connaître_les_conditions_d_application_form
  - slot_was_set: 
    - requested_slot: null
  - active_loop: null
  - action: action_ask_solution
  
- rule: Unhappy Connaître_les_conditions_d_application_form
  condition:
  - active_loop: Connaître_les_conditions_d_application_form
  - slot_was_set:
    - change_of_heart: true
  steps: 
  - slot_was_set: 
    - change_of_heart: true
  - action: utter_pi_outside_form