Is it possible to execute utter_X with a different message if it has already been said by the assistant?

Hi,

I have an utter_X with different responses like:

> utter_X:
>   - text: bye!
>   - text: hope to see you soon.
>   - text: have a nice day.

> rule: say bye
>   steps:
>   - intent: intent_X
>   - action: utter_X

Lets say I have triggered intent_X, and bot said 'have a nice day'. At some point I trigger intent_X again and now I want the bot to reply with a different message this time. How can I do it?

You can do it using custom actions and slots, and saving which response the bot already uttered.

Is it really worth it?

you mean: save in a slot what was the bot utterance, and if that slot value is the same is the message, change the variable message? not sure I got what you meant. It is worth it because this intent is triggered many times and the bot always says the same although I have included multiple ‘text: …’ in utter_X

The way you described it is not the most optimized (why save the whole sentence, just save its ID), but that’s basically the idea, yes.

thanks @ChrisRahme for clarifying. how do you save the ID of a bot utter_X? any better way than defining a dictionary ?

Something like that

from random import randrange


class ActionGoodbye(Action):
    def name(self):
        return 'action_goodbye'

    def run(self, dispatcher, tracker, domain):
        goodbyes = [
            'bye!', # 0
            'hope to see you soon.' # 1
            'have a nice day.' # 2
        ]

        already_uttered = tracker.get_slot('uttered_goobye') # For example [0, 2] means the first and third sentences were already uttered

        if len(already_uttered) >= len(goodbyes): # If all the sentences were already uttered, reset
            already_uttered = [] # Reset the slot

        random_index = None
        
        while (random_index is None) or (random_index not in already_uttered): # Do this until we get a random_index that was not uttered
            random_index = randrange(len(goodbyes))

        utterance = goodbyes[random_index]
        already_uttered.append(random_index)

        return [SlotSet('uttered_goobye', already_uttered)]

Hi @ChrisRahme just facing this problem again. Would it be possible to vary the utter_X which we define in domain.yml, instead of using Actions?

The best you can do without actions is add a lot of responses under utter_X instead of just 3 so that the probability of repetition becomes lower.

PS: I made a mistake in the condition for the while loop, I fixed it now.

thanks for the reply, and is there a way of using actions but to avoid repetition from what is in domain.yml (utter_X)

Can you clarify please?

You want to use the action but take the responses as defined in the domain instead of defining them in the code?

@ChrisRahme it would be very helpful if you provide the code of other files(domain.yml,rules.yml,stories.yml,nlu.yml) as well , because only looking at action.py my I am not getting how to implement for my project

@ChrisRahme what I mean is: I have utter_X with text in my domain.yml:

> - Hello
> - Hi there
> - Hey again
> - Good morning

Now I am using actions.py in Form validation to return

> dispatcher.utter_message(response = "utter_X")

But I want to make sure that if “Hello” has been already said, I say something else from the examples I defined in my domain.yml.

Best you can do is use the solution above, but if you don’t want to define the sentences in the actions but in the domain instead, you can get the domain as a JSON (dictionary) in a custom action using the domain variable.

So to get the responses for utter_goodbye, you can do this:

goodbyes = domain['responses']['utter_goodbye']
goodbyes = filter(lambda x: 'text' in x and len(x['text']) > 0, goodbyes)
goodbyes = map(lambda x: x['text'], goodbyes)

The first line will get you the responses for utter_goodbye as a dict, like in the domain. For example:

responses:
  utter_greet:
  - text: "Hey! How are you?"
    buttons:
    - title: "great"
      payload: "/mood_great"
    - title: "super sad"
      payload: "/mood_sad"
  - text: "Hello! What's up?"
    channel: "slack"
  - image: "https://i.imgur.com/nGF1K8f.jpg"
  - text: "Hey! Nice to see you again! How are you?"
    condition:
    - type: slot
      name: logged_in
      value: true
{
  "responses": {
    "utter_greet": [
      {
        "text": "Hey! How are you?",
        "buttons": [
          {
            "title": "great",
            "payload": "/mood_great"
          },
          {
            "title": "super sad",
            "payload": "/mood_sad"
          }
        ]
      },
      {
        "text": "Hello! What's up?",
        "channel": "slack"
      },
      {
        "image": "https://i.imgur.com/nGF1K8f.jpg"
      },
      {
        "text": "Hey! Nice to see you again! How are you?",
        "condition": [
          {
            "type": "slot",
            "name": "logged_in",
            "value": true
          }
        ]
      }
    ]
  }
}

What the first line is retrieving is the list inside utter_greet.

The second line will remove any element that does not have a “text” key, so the image-only response will be gone and the list will be:

[
  {
    "text": "Hey! How are you?",
    "buttons": [
      {
        "title": "great",
        "payload": "/mood_great"
      },
      {
        "title": "super sad",
        "payload": "/mood_sad"
      }
    ]
  },
  {
    "text": "Hello! What's up?",
    "channel": "slack"
  },
  {
    "text": "Hey! Nice to see you again! How are you?",
    "condition": [
      {
        "type": "slot",
        "name": "logged_in",
        "value": true
      }
    ]
  }
]

Finally, the third line will only keep the “text” element of each object:

[
  "Hey! How are you?",
  "Hello! What's up?",
  "Hey! Nice to see you again! How are you?"
]

Of course, you can choose how to filter and map the array, but you get the idea.

They are exactly the same as in a regular project. Just instead of utter_ in action names, use action_.