Form skips utter_ask action

What i am trying to accomplish is create a dynamic form. A user can create a request for a schedule with a teacher.

Form has 2 required slots. Teachers email and date.

When the user inputs the email i use the technique shown in the rasa documentation to update the required_slots by overriding it. This works:

before user inputs email and slot is empty:

[‘scheduled_meeting_teacher_email’, ‘scheduled_meeting_date’]

after user inputs the email and the slot(scheduled_meeting_teacher_email) is filled:

[‘scheduled_meeting_correct_teacher_email’, ‘scheduled_meeting_teacher_email’, ‘scheduled_meeting_date’]

So the next required slot becomes scheduled_meeting_correct_teacher_email

Based on the documentation is now expect utter_ask_scheduled_meeting_correct_teacher_email to be triggered. But that never happens. I can see that the next step becomes extract_scheduled_meeting_correct_teacher_email which i build around the showcase shown here: (18) Customise a Form to Properly Ask for Names | Rasa Tutorials - YouTube which seemed like a pretty good trick.

But since utter_ask_ never got triggered this just

At this point i imagine that i am either missing something or the version difference means that this no longer works or i have to change something more that i cant figure out. I believe that the mappings is the source of this but maybe i am wrong.

So yeah. How do i make this work? Any help will be super appreciated.

Here is the code:

class ValidateScheduledMeetingWithTeacherForm(FormValidationAction):
    def name(self) -> Text:
        return "validate_scheduled_meeting_with_teacher_form"

    async def required_slots(self, domain_slots, dispatcher, tracker, domain):
        updated_slots = domain_slots.copy()
        if tracker.get_slot("scheduled_meeting_teacher_email") and tracker.get_slot("scheduled_meeting_correct_teacher_email")==None:
            updated_slots = ["scheduled_meeting_correct_teacher_email"] + updated_slots
        if tracker.get_slot("scheduled_meeting_date") and tracker.get_slot("scheduled_meeting_correct_date")==None:
            updated_slots = ["scheduled_meeting_correct_date"] + updated_slots
        print(updated_slots)
        return updated_slots

    async def extract_scheduled_meeting_correct_teacher_email(
        self, 
        dispatcher: CollectingDispatcher, 
        tracker: Tracker, 
        domain: Dict
    ) -> Dict[Text, Any]:
        intent = tracker.get_intent_of_latest_message()
        return {"scheduled_meeting_correct_teacher_email": intent == "user_affirm"}
    
    def validate_scheduled_meeting_correct_teacher_email(
        self,
        slot_value: Any,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: DomainDict,
    ) -> Dict[Text, Any]:
        if tracker.get_slot("scheduled_meeting_correct_teacher_email"):
            return {"scheduled_meeting_teacher_email": tracker.get_slot("scheduled_meeting_teacher_email"), "scheduled_meeting_correct_teacher_email": True}
        return {"scheduled_meeting_teacher_email": None, "scheduled_meeting_correct_teacher_email": None}
    

    async def extract_scheduled_meeting_correct_date(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: DomainDict,
    ) -> Dict[Text, Any]:
        intent = tracker.get_intent_of_latest_message()
        return {"scheduled_meeting_correct_date": intent == "user_affirm"}

    def validate_scheduled_meeting_correct_date(
        self,
        slot_value: Any,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: DomainDict,
    ) -> Dict[Text, Any]:
        if tracker.get_slot("scheduled_meeting_correct_date"):
            return {"scheduled_meeting_date": tracker.get_slot("scheduled_meeting_teacher_email"), "scheduled_meeting_correct_date": True}
        return {"scheduled_meeting_date": None, "scheduled_meeting_correct_date": None}

    def validate_scheduled_meeting_teacher_email(
        self,
        slot_value: Any,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: DomainDict,
    ) -> Dict[Text, Any]:
        try:
            if slot_value=="test@uni.com":
                return {"scheduled_meeting_teacher_email": slot_value}
            else:
                return {"scheduled_meeting_teacher_email": None}
        except ValueError:
            return {"scheduled_meeting_teacher_email": None}

    def validate_scheduled_meeting_date(
        self,
        slot_value: Any,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: DomainDict,
    ) -> Dict[Text, Any]:
        try:
            # Parse the date with the expected format
            date_obj = datetime.strptime(slot_value, "%d/%m/%Y")
            
            today = datetime.today()
            
            min_date = today + 1
            max_date = today + timedelta(days=30)
            
            # Check if the date is within the range
            if date_obj <= min_date <= max_date:
                return {"scheduled_meeting_date": slot_value}
            else:
                return {"scheduled_meeting_date": None }
        except ValueError:
            return {"scheduled_meeting_date": None }

My slots:

  scheduled_meeting_teacher_email:
    type: text
    influence_conversation: true
    mappings:
      - type: from_entity
        entity: university_email
        conditions:
        - active_loop: scheduled_meeting_with_teacher_form
          requested_slot: scheduled_meeting_teacher_email

  scheduled_meeting_date:
    type: text
    influence_conversation: true
    mappings:
      - type: from_entity
        entity: date
        conditions:
        - active_loop: scheduled_meeting_with_teacher_form
          requested_slot: scheduled_meeting_date

  scheduled_meeting_correct_teacher_email:
    type: bool
    initial_value: null
    influence_conversation: true
    mappings:
      - type: custom

  scheduled_meeting_correct_date:
    type: bool
    initial_value: null
    influence_conversation: true
    mappings:
      - type: custom 

And my responses:

  utter_ask_scheduled_meeting_teacher_email:
    - text: "Ποιο είναι το email του/της καθηγητή/καθηγήτριας που θέλεις να συναντήσεις;"

  utter_ask_scheduled_meeting_date:
    - text: "Ποια ημερομηνία θα σε ενδιέφερε;"

  utter_ask_scheduled_meeting_correct_teacher_email:
    - text: "To email που δήλωσες είναι: . Συμφωνείς;"
      buttons:
        - title: "Ναι"
          payload: '/user_affirm'
        - title: "Οχι"
          payload: '/user_deny'

  utter_ask_scheduled_meeting_correct_date:
    - text: "Η ημερομηνίας που δήλωσες είναι: . Συμφωνείς;"
      buttons:
        - title: "Ναι"
          payload: '/user_affirm'
        - title: "Οχι"
          payload: '/user_deny'

If still facing this issue, then i suggest to put a condition:

async def extract_scheduled_meeting_correct_teacher_email(
        self, 
        dispatcher: CollectingDispatcher, 
        tracker: Tracker, 
        domain: Dict
    ) -> Dict[Text, Any]:
        if tracker.get_slot("requested_slot") == "scheduled_meeting_correct_teacher_email":
                intent = tracker.get_intent_of_latest_message()
                return {"scheduled_meeting_correct_teacher_email": intent == "user_affirm"}
        return {"scheduled_meeting_correct_teacher_email": tracker.get_slot("scheduled_meeting_correct_teacher_email"}

Not sure whats the issue in your case, but the calls to extract, validate etc in dynamic form often behaves weirdly. This guard ensures that scheduled_meeting_correct_teacher_email is extracted to be filled only when its requested and the slot is not pre-filled. Logic being that utter_ask/action_ask are not called when slot is already filled.