Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an action that runs on form start/initialization, before active_loop is set yet #10913

Closed
indam23 opened this issue Feb 17, 2022 · 6 comments
Labels
area:rasa-oss 🎡 Anything related to the open source Rasa framework cse-issues type:enhancement ✨ Additions of new features or changes to existing ones, should be doable in a single PR

Comments

@indam23
Copy link
Contributor

indam23 commented Feb 17, 2022

h3. What problem are you trying to solve?

I want to add form-entry logic that should only run once when a form is activated. In 2.x this was possible by modifying the run method of a FormValidationAction to run certain logic only when requested_slot was None, a heuristic for the form beginning. In 3.x [this was cleaned up| https://github.com//pull/10295/files#r807244104] so that the validation action doesn't run unless the loop is active, which makes sense, but has the side effect of not allowing form initial logic.

h3. What's your suggested solution?

Add a new default action type, FormInitializationAction that can be subclassed as FormValidationAction is and called by name rule the same way e.g. initialize_<form_name> . Other options that came up: * Add a method to FormValidationAction that runs only in the case that the form action has just been predicted but the loop is not active yet i.e. in the same place as the validation action used to run in 2.x. e.g. run_on_entry(). Problem with this: This would require changes in Rasa SDK as well, since the action server only offers a way to call the run method of an action, not any other method. The principle has been to keep form running logic (i.e. when what happens) entirely in Rasa Open Source to avoid any dependency of Rasa SDK for those who run action servers not using Rasa SDK. h3. Examples (if relevant) This used to work in 2.8.x, in 3.x the entry logic will never run: class CustomFormValidationAction(FormValidationAction, ABC): def name(self): return async def run( self, dispatcher: "CollectingDispatcher", tracker: "Tracker", domain: "DomainDict", ) -> List[EventType]: events = await super().run(dispatcher, tracker, domain) if tracker.get_slot("requested_slot") is None: # CONDITION NEVER MET IN 3.x dispatcher.utter_message(text="I am the form entry logic!") return events h3. Is anything blocking this from being implemented? (if relevant) No response h3. Definition of Done * [ ] default action added/called from Rasa OSS * [ ] default action template added in Rasa SDK * [ ] documentation updated
</form_name>

@indam23 indam23 added type:enhancement ✨ Additions of new features or changes to existing ones, should be doable in a single PR area:rasa-oss 🎡 Anything related to the open source Rasa framework labels Feb 17, 2022
@indam23 indam23 changed the title Add a method to FormValidationAction that runs on form start, before active_loop is set yet Add an action or a method to FormValidationAction that runs on form start, before active_loop is set yet Feb 21, 2022
@indam23 indam23 changed the title Add an action or a method to FormValidationAction that runs on form start, before active_loop is set yet Add an action that runs on form start, before active_loop is set yet Feb 21, 2022
@venushong667
Copy link

venushong667 commented Feb 25, 2022

Exalate commented: venushong667 commented:

Exalate commented:

venushong667 commented:

I will give an upvote to this request as I'm facing the same issue, form initialization functions are not working anymore in Rasa 3.0 broke all of our applications.

For my opinion, changing FormValidation to be triggered only after form is activated or filled slots is totally make sense, but the behavior changes of active_loop is kind of not acceptable. I think that form action is a loop and when a form is activated, it should means loop is active as well.

I tried to implement alternative way using slot extraction to do the same job but turns out we are unable to get active_loop of form on the activation unless something is pre-filled into it. This issue is quite critical for us, please consider this enhancement.

@indam23
Copy link
Contributor Author

indam23 commented Mar 1, 2022

Exalate commented: melindaloubser1 commented:

Exalate commented:

melindaloubser1 commented:

@venushong667 For any other users looking for this, can you explain how you tried to use slot extraction to achieve this? It may be that for some use cases your workaround would suffice.

@venushong667
Copy link

venushong667 commented Mar 2, 2022

Exalate commented: venushong667 commented:

Exalate commented:

venushong667 commented:

@venushong667 For any other users looking for this, can you explain how you tried to use slot extraction to achieve this? It may be that for some use cases your workaround would suffice.

@melindaloubser1 In my previous use case, I implemented continue_form and confirm_form slots in formValidationAction to keep track of the interruption of form filling, both slots are set True when form is initialized and set None/False when user trigger certain intents ex. stop_form. Thus it can trigger utter_ask_continue_form and utter_ask_confirm_form to question about the interruption.

Using slot extraction, I had tried to create a active_loop slot and extraction function to track the active form, below is the simplified code that I used to initialize slots

slot active_loop: type: rasa.shared.core.slots.AnySlot initial_value: null influence_conversation: false mappings:

  • type: custom

actions.py class ValidatePredefinedSlots(ValidationAction):

async def extract_active_loop( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict ) -> Dict[Text, Any]: active_loop = tracker.active_loop_name return

{'active_loop': active_loop}

def validate_active_loop( self, slot_value: Any, dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict, ) -> Dict[Text, Any]: required_slots = domain.get('forms', {}).get(slot_value, {}).get('required_slots', []) if 'AA_continue_form' not in required_slots: return {} return

{'AA_continue_form': True, 'zz_confirm_form': True}

form general_form: ignored_intents: [] required_slots:

  • AA_continue_form
  • fullname
  • email
  • zz_confirm_form

The AA_continue_form and zz_confirm_form are both True to influence_conversaton hence I can implement certain rules with conditions of slot_was_set: True/False for both slots to decide my response

However, the active_loop is not able to get on the form activation, continue and confirm slots cannot be initialized causing the action: general_form always trigger utter_ask_continue_form at the first response due to None value The key initial_value couldn't help as well since it only initialize the value on startup

@indam23
Copy link
Contributor Author

indam23 commented Mar 2, 2022

Exalate commented: melindaloubser1 commented:

Exalate commented:

melindaloubser1 commented:

This is the workaround I've come up with. Please let me know if it covers your use case too - you can clone the form_intial_logic_3.x branch here: [https://github.com/melindaloubser1/moodbot/tree/form_initial_logic_3.x| https://github.com/melindaloubser1/moodbot/tree/form_initial_logic_3.x] to try it out. For this case I was looking for an utterance to be returned on form initialization and nothing more i.e.

Your input -> /intro I am the extra run logic and I should run at the beginning of a form! what is your name? Your input -> melinda submitted melinda

Add a slot form_initialized with a custom type mapping as the first required slot for every form that needs initialization logic.

Add a custom slot extraction method for form_initialized. This will run on every user turn. It will set the slot to False unless the slot is both currently set (it's set by the form below) and the form is still active. This takes care of resetting the slot once a form is closed.

Create a form validation base class that runs extra logic when form_initialized is False, and includes a validation method for form_initialized that just sets the slot to True. The validation method will only run after the form was initialized.

Subclass the custom validation action for any forms requiring initialization logic.

This is what the actions end up looking like:

class ValidatePredefinedSlots(ValidationAction):
    def extract_form_initialized(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: DomainDict,
    ):
        if not tracker.active_loop or not tracker.get_slot("form_initialized"):
            return {"form_initialized": False}
        else:
            return {"form_initialized": True}

class CustomFormValidationAction(FormValidationAction, ABC):
    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: DomainDict,
    ) -> List[EventType]:
        events = []
        if not tracker.get_slot("form_initialized"):
            events.extend(await self.extra_run_logic(dispatcher, tracker, domain))
        events.extend(await super().run(dispatcher, tracker, domain))
        return events

    def validate_form_initialized(
        self,
        slot_value: Any,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: DomainDict,
    ):
        return {"form_initialized": True}

    async def extra_run_logic(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: DomainDict,
    ):
        return []

class ValidateIntroForm(CustomFormValidationAction):

    def name(self) -> Text:
        return "validate_intro_form"

    async def extra_run_logic(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: DomainDict,
    ):
        events = await super().extra_run_logic()
        dispatcher.utter_message(text="I am the extra run logic and I should run at the beginning of a form!")
        events.append(SlotSet("example_slot_to_set_on_form_entry", "value"))
        return events

And the domain:

version: "3.0"

intents:
- greet
- intro

responses:
  utter_submit_intro_form:
   - text: "submitted {name}"

  utter_ask_name:
  - text: "what is your name?"

  utter_greet:
  - text: "Hey! How are you?"

slots:
 name:
   type: text
   influence_conversation: false
   mappings:
   - type: from_text
      not_intent: intro
      conditions:
       - active_loop: intro_form
         requested_slot: name
  form_initialized:
     type: any
     mappings:
      - type: custom

forms:
 intro_form:
   required_slots:
   - form_initialized
   - name

actions:
- validate_intro_form
- action_validate_slot_mappings

session_config:
 session_expiration_time: 60
 carry_over_slots_to_new_session: true

@venushong667
Copy link

venushong667 commented Mar 9, 2022

Exalate commented: venushong667 commented:

Exalate commented:

venushong667 commented:

This is the workaround I've come up with. Please let me know if it covers your use case too - you can clone the form_intial_logic_3.x branch here: [https://github.com/melindaloubser1/moodbot/tree/form_initial_logic_3.x| https://github.com/melindaloubser1/moodbot/tree/form_initial_logic_3.x] to try it out. For this case I was looking for an utterance to be returned on form initialization and nothing more i.e.

Your input -> /intro I am the extra run logic and I should run at the beginning of a form! what is your name? Your input -> melinda submitted melinda

h1. Add a slot form_initialized with a custom type mapping as the first required slot for every form that needs initialization logic.

h1. Add a custom slot extraction method for form_initialized. This will run on every user turn. It will set the slot to False unless the slot is both currently set (it's set by the form below) and the form is still active. This takes care of resetting the slot once a form is closed.

h1. Create a form validation base class that runs extra logic when form_initialized is False, and includes a validation method for form_initialized that just sets the slot to True. The validation method will only run after the form was initialized.

h1. Subclass the custom validation action for any forms requiring initialization logic.

This is what the actions end up looking like:

from typing import Text, List, Any import logging

from abc import ABC

from rasa_sdk import FormValidationAction, ValidationAction, Tracker from rasa_sdk.events import EventType, SlotSet from rasa_sdk.executor import CollectingDispatcher from rasa_sdk.types import DomainDict

logger = logging.getLogger(name)

class ValidatePredefinedSlots(ValidationAction): def extract_form_initialized( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict, ): if not tracker.active_loop or not tracker.get_slot("form_initialized"): return

{"form_initialized": False}

else: return

{"form_initialized": True}

class CustomFormValidationAction(FormValidationAction, ABC): async def run( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict, ) -> List[EventType]: events = [] if not tracker.get_slot("form_initialized"): events.extend(await self.extra_run_logic(dispatcher, tracker, domain)) events.extend(await super().run(dispatcher, tracker, domain)) return events

def validate_form_initialized( self, slot_value: Any, dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict, ): return

{"form_initialized": True}

async def extra_run_logic( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict, ): return []

class ValidateIntroForm(CustomFormValidationAction):

def name(self) -> Text: return "validate_intro_form"

async def extra_run_logic( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict, ): events = await super().extra_run_logic() dispatcher.utter_message(text="I am the extra run logic and I should run at the beginning of a form!") events.append(SlotSet("example_slot_to_set_on_form_entry", "value")) return events

And the domain:

version: "3.0"
> 
> intents:
> - greet
> - intro
> 
> responses:
> utter_submit_intro_form:
> - text: "submitted 
> 

{name}

"

utter_ask_name:

  • text: "what is your name?"

utter_greet:

  • text: "Hey! How are you?"

slots: name: type: text influence_conversation: false mappings:

  • type: from_text not_intent: intro conditions:
  • active_loop: intro_form requested_slot: name form_initialized: type: any mappings:
  • type: custom

forms: intro_form: required_slots:

  • form_initialized
  • name

actions:

  • validate_intro_form
  • action_validate_slot_mappings

session_config: session_expiration_time: 60 carry_over_slots_to_new_session: true

Sorry for the late reply. Yes, your solution did the job very well as the form_initialized slot is being extracted in every turn of conversation, it is able to trigger the FormValidation to run and so the extra_run_logic where initialize function can be implemented. Thanks for the solution!

@rasabot-exalate rasabot-exalate added area:rasa-oss and removed type:enhancement ✨ Additions of new features or changes to existing ones, should be doable in a single PR area:rasa-oss 🎡 Anything related to the open source Rasa framework labels Mar 15, 2022 — with Exalate Issue Sync
@m-vdb m-vdb added area:rasa-oss 🎡 Anything related to the open source Rasa framework type:enhancement ✨ Additions of new features or changes to existing ones, should be doable in a single PR area:rasa-oss and removed area:rasa-oss area:rasa-oss 🎡 Anything related to the open source Rasa framework labels Mar 16, 2022
@rasabot-exalate rasabot-exalate added area:rasa-oss and removed type:enhancement ✨ Additions of new features or changes to existing ones, should be doable in a single PR area:rasa-oss 🎡 Anything related to the open source Rasa framework area:rasa-oss labels Mar 17, 2022 — with Exalate Issue Sync
@rasabot-exalate rasabot-exalate added type:enhancement ✨ Additions of new features or changes to existing ones, should be doable in a single PR area:rasa-oss 🎡 Anything related to the open source Rasa framework type:enhancement_:sparkles: and removed type:enhancement ✨ Additions of new features or changes to existing ones, should be doable in a single PR area:rasa-oss :ferris wheel: labels Mar 17, 2022 — with Exalate Issue Sync
@indam23 indam23 changed the title Add an action that runs on form start, before active_loop is set yet Add an action that runs on form start/initialization, before active_loop is set yet Apr 8, 2022
@indam23
Copy link
Contributor Author

indam23 commented Jul 20, 2022

The change leading to this feature request was reverted in #11326; as such it is now obsolete.

@indam23 indam23 closed this as completed Jul 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:rasa-oss 🎡 Anything related to the open source Rasa framework cse-issues type:enhancement ✨ Additions of new features or changes to existing ones, should be doable in a single PR
Projects
None yet
Development

No branches or pull requests

5 participants