question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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

See original GitHub issue

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/RasaHQ/rasa/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>

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
melindaloubser1commented, Nov 24, 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
1reaction
venushong667commented, Nov 24, 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!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Activeloop - Database for AI
Data infrastructure optimized for computer vision. Deep Lake is the fastest data loader for PyTorch. · The open-source community enabling the future of...
Read more >
What is Computer Vision? - IBM
Computer vision trains machines to perform these functions, but it has to do ... It runs analyses of data over and over until...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found