Custom action not working - invoking wrong function

I’m running into a problem with custom actions in the final slot of a form. The bot doesn’t run the final action, even when directed to with rasa interactive, and instead invokes a ‘hello world action’. I can’t figure out why.

In the domain and action pasted below, the asana_form is the one I’m having trouble with. The last two slots should trigger custom actions, since they have the prefix ‘action_ask’. The second-to-last slot is triggered correctly. But, when the bot asks to log information for the last slot, that’s when I run into a problem.

I’ll type something in for the prompt like ‘this is a note’, and in Rasa interactive, i force it to interpret it as an “inform” intent. But, then it just doesn’t work and instead invokes hello world, and it does that even if i also select “asana_form” from the rasa interactive menu.

thanks for any feedback and let me know if I can clarify this question

DOMAIN

   version: '2.0'
session_config:
  session_expiration_time: 60
  carry_over_slots_to_new_session: false

intents:
- sleep_log
- affirm
- inform
- greet
- goodbye
- deny
- mood_great
- mood_unhappy
- bot_challenge
- question_test
- trigger_hello_world
- flush_slots
- check_asana


entities:
- exercise
- sleep
- stress
- task_name


slots:
  confirm_log:
    type: text
    influence_conversation: true
  sleep_confirm:
    type: text
    influence_conversation: true
  sleep:
    type: text
    influence_conversation: true
  sleep_description:
    type: text
    influence_conversation: true
  goal:
    type: text
    influence_conversation: true
  asana_task_name:
    type: text
    influence_conversation: true
  asana_number_select:
    type: text
    influence_conversation: true
  asana_list_tasks:
    type: text
    influence_conversation: true
  notes_from_asana_task_selected:
    type: text
    influence_conversation: true
  asana_select_options_slot:
    type: text
    influence_conversation: false
  asana_after_note_logged:
    type: text
    influence_conversation: false



responses:
  utter_confirm_sleep:
  - text: Do you want to talk about how you slept?
  utter_greet:
  - text: Hi! It's time for your daily sleep check-in. I'm excited to learn about your experience. Shall we get started?
  utter_goodbye:
  - text: Thanks for chatting. Bye!
  utter_iamabot:
  - text: I am a bot, powered by Rasa.
  utter_ask_confirm_log:
  - text: Did you exercise yesterday? Don't sweat it if you didn't run a marathon - walks count!
  utter_ask_exercise:
  - text: What kind of exercise did you do 💪 ?
  utter_ask_sleep:
  - text: How much sleep did you get last night?
  utter_ask_sleep_description:
  - text: How would you describe your sleep?
  utter_ask_diet:
  - text: Did you stick to a healthy diet 🥦  yesterday?
  utter_ask_stress:
  - text: What is your stress level right now 🧘 ?
    buttons:
    - title: low
      payload: '/inform{"stress": "low"}'
    - title: medium
      payload: '/inform{"stress": "medium"}'
    - title: high
      payload: '/inform{"stress": "high"}'
  utter_ask_goal:
  - text: Setting goals - even small ones - is a great way to focus your day. What do you want to accomplish today 🥇 ?
  utter_slots_values:
  - text: |-
      You've submitted the following answers:
       - Exercised?: {confirm_log}
       - Sleep: {sleep}
       - Goal: {goal}
  utter_no_worries:
  - text: No problem :)
  utter_ask_continue:
  - text: Sorry, I don't quite understand. Do you want to continue?
  utter_cheer_up:
  - text: 'Here is something to cheer you up:'
    image: https://i.imgur.com/nGF1K8f.jpg
  utter_did_that_help:
  - text: Did that help you?
  utter_start_over:
  - text: Ok, let's start over.
  utter_happy:
  - text: Great, carry on!
  utter_question_test:
  - text: What's your question?
  utter_question_color_test:
  - text: my favorite color is blue
  utter_ask_asana_task_name:
  - text: What's the task you'd like to check?


actions:
- action_flush_slots
- action_hello_world
- action_submit_results
- confirm_log
- utter_confirm_sleep
- utter_exercise_info
- validate_health_form
- validate_sleep_form
- validate_sleep_log
- check_asana_action
- select_asana_action
- action_ask_asana_list_tasks
- action_ask_notes_from_asana_task_selected
- action_ask_asana_after_note_logged


forms:
  asana_form:
    asana_task_name:
    - type: from_intent
      intent: inform
      value: true
    asana_list_tasks:
    - type: from_intent
      intent: inform
      value: true
    notes_from_asana_task_selected:
    - type: from_text
      intent: None
      not_intent: inform
    asana_after_note_logged:
    - type: from_text
      intent: None
      not_intent: inform
  sleep_form:
    sleep_confirm:
    - type: from_intent
      intent: affirm
      value: true
    - type: from_intent
      intent: deny
      value: false
    - type: from_intent
      intent: inform
      value: true
    sleep_description:
    - type: from_text
      intent: None
      not_intent: inform
  health_form:
    confirm_log:
    - type: from_intent
      intent: affirm
      value: true
    - type: from_intent
      intent: deny
      value: false
    - type: from_intent
      intent: inform
      value: true
    sleep:
    - type: from_entity
      entity: sleep
    - type: from_intent
      intent: deny
      value: None
    sleep_description:
    - type: from_text
      intent: None
      not_intent: inform
    goal:
    - type: from_intent
      intent: affirm
      value: true
    - type: from_intent
      intent: deny
      value: false
  experience_log:
    verify_desire_log:
    - type: from_intent
      intent: affirm
      value: true
    substance:
    - type: from_intent
      intent: inform
      value: true
    dose:
    - type: from_text
      intent: None
      not_intent: inform
    note:
    - type: from_text
      intent: None
      not_intent: inform
    timing:
    - type: from_text
      intent: None
      not_intent: inform
    key_takeaway:
    - type: from_text
      intent: None
      not_intent: inform
    explanation_of_mechanism:
    - type: from_text
      intent: None
      not_intent: inform
    behavior_change:
    - type: from_text
      intent: None
      not_intent: inform
    feeling:
    - type: from_text
      intent: None
      not_intent: inform

ACTION FILE

# This files contains your custom actions which can be used to run
# custom Python code.
#
# See this guide on how to implement these action:
# https://rasa.com/docs/rasa/custom-actions


# This is a simple example for a custom action which utters "Hello World!"

from typing import Any, Text, Dict, List, Optional

from rasa_sdk import Action, Tracker
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk.forms import FormValidationAction
#from rasa_core_sdk import Action
from rasa_sdk.forms import FormAction
from datetime import datetime
from rasa_sdk.events import SlotSet
from rasa_sdk.types import DomainDict
from rasa_sdk import Tracker, FormValidationAction
import re






now = datetime.now()
 
print("now =", now)
dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
print("date and time =", dt_string)




##asana creds

import pandas as pd
import asana
import json
import re
from Levenshtein import distance
from dialogasanalink import list_tasks_all, get_task_details, list_tasks_numbered_names, update_task_note

# pip3 install python-Levenshtein



# Construct an Asana client
client = asana.Client.access_token(personal_access_token)
# Set things up to send the name of this script to us to show that you succeeded! This is optional.
client.options['client_name'] = "hello_world_python"

# Get your user info
me = client.users.me()
client.options['page_size'] = 100

print ('this is testing whether asana can be called', me)


class ActionHelloWorld(Action):

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

def run(self, dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:

    
    
    print ('action hello triggered')
    #try:
    #    sheet.append_row(
    #    ["from test hello action", dt_string], 
    #    value_input_option='RAW', 
    #    insert_data_option=None, 
    #    table_range=None)
    #except:
    #    pass
    
    
    
    dispatcher.utter_message("Hello World!")

    return [SlotSet('asana_select_options_slot', "raw text")]

class ValidateHealthForm(FormValidationAction):
print ('validation triggered')
def name(self) -> Text:
    print ('validation run health')
    return "validate_health_form"

async def validate_confirm_log(
    self,
    value: Text,
    dispatcher: CollectingDispatcher,
    tracker: Tracker,
    domain: Dict[Text, Any],
) -> Dict[Text, Any]:
    if value:
        print ('health log validated true')
        return {"confirm_log": True}
    else:
        return {"exercise": "None", "confirm_log": False }
    

async def required_slots(
    self,
    slots_mapped_in_domain: List[Text],
    dispatcher: "CollectingDispatcher",
    tracker: "Tracker",
    domain: "DomainDict",
) -> Optional[List[Text]]:
    required_slots = slots_mapped_in_domain + ["sleep_description"]
    return required_slots

async def extract_sleep_description(
    self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
) -> Dict[Text, Any]:
    text_of_last_user_message = tracker.latest_message.get("text")
    sit_outside = "slept" in text_of_last_user_message

    dispatcher.utter_message(text = "sleep description noted")
    
    return {"sleep_description": sit_outside}    
    
    
class ActionSubmitResults(Action):
print ('submit triggered')

def name(self) -> Text:
    return "action_submit_results"
def run(
    self,
    dispatcher: CollectingDispatcher,
    tracker: Tracker,
    domain: Dict[Text, Any],
) -> List[Dict]:

    confirm_log = tracker.get_slot("confirm_log")
    exercise = tracker.get_slot("exercise")
    sleep = tracker.get_slot("sleep")
    stress = tracker.get_slot("stress")
    diet = tracker.get_slot("diet")
    goal = tracker.get_slot("goal")
    sleep_description = tracker.get_slot("sleep_description")                        
    response = "this is the response"
    #create_health_log(
    #        confirm_exercise=confirm_exercise,
    #        exercise=exercise,
    #        sleep=sleep,
    #        stress=stress,
    #        diet=diet,
    #        goal=goal
    #    )


    x = []
    print ('x before', x)
    x.append(goal)
    print ('x after', x)
    print ('sleep variable', sleep)
    
    print ("sleep des variable type", type(sleep_description))
    sheet.append_row(
        ["from test doc", dt_string, type(sleep_description)], 
        value_input_option='RAW', 
        insert_data_option=None, 
        table_range=None)

    dispatcher.utter_message("Thanks, your answers have been recorded!")
    slots_to_reset = ["confirm_log", "sleep", "sleep_description", "goal", "requested_slot"]
    return [SlotSet(slot, None) for slot in slots_to_reset]
    
    
    
# https://forum.rasa.com/t/form-deactivation-inside-validate-function/14435/16



class ValidateSleep(FormValidationAction):
print ("sleep form triggered")
def name(self) -> Text:
    print ('validation run sleep')
    return "validate_sleep_form"


async def validate_sleep_confirm(
    self,
    value: Text,
    dispatcher: CollectingDispatcher,
    tracker: Tracker,
    domain: Dict[Text, Any],
) -> Dict[Text, Any]:
    if value:
        print ('true value of sleep confirmed')
        return {"sleep_confirm": True}
    else:
        print ('false value sleep confirmed')
        return {"sleep_confirm": False }

async def required_slots(
    self,
    slots_mapped_in_domain: List[Text],
    dispatcher: "CollectingDispatcher",
    tracker: "Tracker",
    domain: "DomainDict",
) -> Optional[List[Text]]:
    required_slots = slots_mapped_in_domain + ["sleep_description"]
    print ('required slot triggered')
    return required_slots

async def extract_sleep_description(
    self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
) -> Dict[Text, Any]:
    text_of_last_user_message = tracker.latest_message.get("text")
    sleep_description = text_of_last_user_message
    print ('sleep extraction triggered')
    dispatcher.utter_message(template="utter_ask_sleep_description")
    print ('this is the sleep description from tracker- ', text_of_last_user_message)
    for key in tracker.slots:
        print ('these are the slots', key, '->', tracker.slots[key])
    
    try:
        sheet.append_row(
        [text_of_last_user_message, dt_string], 
        value_input_option='RAW', 
        insert_data_option=None, 
        table_range=None)
    except:
        pass

    return {"sleep_description": sleep_description}


class ActionFlushSlots(Action):

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

def run(self, dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:

    slots = []
    for key, value in tracker.current_slot_values().items():
        if value is not None:
            slots.append(SlotSet(key=key, value=None))

    return slots


class AskForAsanaTasks(Action):

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


def run(self, dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:
    
    text_of_last_user_message = tracker.latest_message.get("text")
    
    tasklist = list_tasks_numbered_names(text_of_last_user_message)

    tasklist = '"' + tasklist + '"'
    
    print ('DEBUG: this is the task list', tasklist)
    
    
    
    dispatcher.utter_message('here is a list of tasks', tasklist)
    return [SlotSet('asana_select_options_slot', tasklist)]


class AsansSelect(Action):

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


def run(self, dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:

    text_of_last_user_message = tracker.latest_message.get("text")
    
    
    
    listed_tasks_from_slot = tracker.get_slot("asana_select_options_slot")
    my_regex = re.escape(text_of_last_user_message) + r"(.*)(?:(id:) (\d*)(?:;))"

    capture = re.search(my_regex, listed_tasks_from_slot)
    
    print ('this is captured', capture)
    
    task_name_selected = capture.group(3)

    print ('DEBUG: this is the selected task', task_name_selected)
    dispatcher.utter_message('What would you like to log about' + task_name_selected + '?')
    
    
    
    #print ('DEBUG: captured notes', notes)
    
    
    


    
    print ('DEBUG STATUS: user response before return', tracker.latest_message.get("text"))
    return [SlotSet('asana_select_options_slot', task_name_selected)]

class AsanaNoteLogged(Action):

def name(self) -> Text:
    print ('check asana invoked')
    return "action_ask_asana_after_note_logged"


def run(self, dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:
    
    asana_notes_from_last_user_message = tracker.latest_message.get("text")
    
    extracted_gid = tracker.get_slot("asana_select_options_slot")
    
    print ('DEBUG EXTRACTED GID', extracted_gid)
    print ('DEBUG user note', extracted_gid)
    
    update_task_note(extracted_gid, asana_notes_from_last_user_message)
    
    
    print ('DEBUG: this is the task notes', asana_notes_from_last_user_message)
    
    try:
        sheet.append_row(
        [asana_notes_from_last_user_message, dt_string, extracted_gid], 
        value_input_option='RAW', 
        insert_data_option=None, 
        table_range=None)
    except:
        pass
    
    dispatcher.utter_message('you updated the task')
    return

Hi @travelmail26, for the notes_from_asana_task_selected and asana_after_note_logged slots in your asana_form you have not_intent: inform so those slots will not be filled with values that map to the inform intent. In your question you say you are forcing phrases to the inform intent so I’m wondering if removing not_intent: inform from your form slot mappings will help.

@m.vielkind I’m still running into the same problem even after getting rid of the not_intent linke

notes_from_asana_task_selected:
- type: from_text
  intent: None
asana_after_note_logged:
- type: from_text
  intent: None

I appreciate the help! What am i doing wrong? after i type a note, which I think is for the asana_after_note_logged slot, it doesn’t complete the previous action and just loops through other actions or intents. thanks again!

Can you share your stories and any rules as well?

Sure! pasted below. thanks again for your help!

RULES

version: "2.0"

rules:

#- rule: Say goodbye anytime the user says goodbye
#  steps:
#  - intent: goodbye
#  - action: utter_goodbye

#- rule: Say 'I am a bot' anytime the user challenges
#  steps:
#  - intent: bot_challenge
#  - action: utter_iamabot

- rule: Submit form
  condition:
# Condition that form is active.
- active_loop: health_form
  steps:
  - action: health_form
  - active_loop: null
# The action we want to run when the form is submitted.
  - action: action_submit_results
  - action: utter_slots_values


- rule: Ask exercise question
  steps:
  - intent: ask_exercise
  - action: utter_exercise_info

- rule: Thanks
  steps:
  - intent: thankyou
  - action: utter_no_worries
  - action: utter_goodbye



- rule: ask sleep
  steps:
  - intent: sleep_log
  - action: utter_confirm_sleep

- rule: activate sleep
  steps:
  - action: utter_confirm_sleep
  - intent: affirm
  - action: sleep_form
  - active_loop: sleep_form

# submit form (activation is appended to each form)

- rule: submit sleep
  condition:
  - active_loop: sleep_form
  steps:
  - action: sleep_form
  - active_loop: null
  - action: utter_goodbye

# check asana

- rule: check asana rule 1
  steps:
  - intent: check_asana
  - action: asana_form


- rule: check asana rule 2
  condition:
  - active_loop: asana_form
  steps:
  - action: utter_goodbye

STORIES

version: "2.0"

stories:

- story: happy path
  steps:
  - intent: greet
  - action: utter_greet
  - intent: mood_great
  - action: utter_happy

- story: sad path 1
  steps:
  - intent: greet
  - action: utter_greet
  - intent: mood_unhappy
  - action: utter_cheer_up
  - action: utter_did_that_help
  - intent: affirm
  - action: utter_happy

- story: sad path 2
  steps:
  - intent: greet
  - action: utter_greet
  - intent: mood_unhappy
  - action: utter_cheer_up
  - action: utter_did_that_help
  - intent: deny
  - action: utter_goodbye

- story: survey happy path
  steps:
  - intent: greet
  - action: utter_greet
  - intent: affirm
  - action: health_form
  - active_loop: health_form

- story: color question test
  steps:
  - intent: question_test
  - action: utter_question_test
  - intent: question_color_test
  - action: utter_question_color_test
  - action: utter_goodbye

- story: hello world path
  steps:
  - intent: trigger_hello_world
  - action: action_hello_world

- story: User stops health_form
  steps:
  - active_loop: health_form
  - intent: flush_slots
  - action: action_flush_slots
  - action: utter_start_over
  - action: utter_greet
  - action: action_deactivate_loop
  - active_loop: null

I’m running into more problems (when i tried to fix things). Now when I try to run the form, it triggers other actions, and when I correct it in rasa interactive, it keeps wanting to run another action.

I’m pretty confused about how Rasa chooses to fill forms in sequence and how i can debug why it isn’t going in the order i want (or why it rejects a particular slot).

cc @SamS for help (who has been fantastic at helping me along so far!)

thanks for the help all!