Pause and Resume conversation

Hi all!

I am struggling to get the conversation going again after pausing it.

Does anyone have a solution for me or does anyone see what I am doing wrong?

The Objective:

What I want to achieve is the following. I have created a bot for Facebook messenger. Here I want to give the user the option to chat with a live agent. Then the agent can indicate that the conversation is finished, then the conversation goes back to the bot.

It comes down to the following. The user talks to the bot and indicates they want to speak to a live agent. The bot hands over control of the conversation to the Facebook Page Inbox, from here a live agent can take over the conversation. When the live agent is finished the live agent hands the conversation back to the bot. The bot is the primary owner of the conversation the live agent is the secondary owner. This approach works well. Sources: Handover Protocol - Messenger Platform - Documentation - Meta for Developers

For example:

User:

I want to speak to a live agent.

Bot:

Okay I’ll connect you through. You can now write down message for the live agent.

User:

What are your opening hours?

Live agent:

We are open from 9:00 to 17:00.

User:

Okay, thank you.

Live agent:

I’m handing the conversation back to the bot.

The Problem:

When the above conversation takes place, the following happens in practice. From the moment the bot has handed over the conversation property to the Facebook Page Inbox (live agent), the bot no longer receives messages. This is controlled on the side of Facebook. Facebook temporarily stops passing on messages from that moment on. From the moment the conversation is given back to the bot, the bot receives all the messages sent during this conversation and the bot tends to answer the messages from itself.

For example:

User:

I want to speak to a live agent.

Bot:

Okay I’ll connect you through. You can now write down message for the live agent.

User:

What are your opening hours?

Live agent:

We are open from 9:00 to 17:00.

User:

Okay, thank you.

Live agent:

I’m handing the conversation back to the bot.

[Problem starts]

Bot:

Our opening hours are from 9:00 am to 5:00 pm.

[Bot response to user message: What are your opening hours?]

Bot:

You’re welcome. Is there anything else I can help you with?

[Bot response to user message: Okay, thank you.]

Problem Analysis:

What happens here is that the bot receives the user’s messages and replies to them as new messages. So user messages are answered twice, first by the live agent and later by the bot. Ideally, the bot ignores these user messages.

Proposed Solution:

The approach that seems most logical to me is inspired by this post: Resume Conversation after Pause - #2 by Arjaan

What I understand from reading through and searching the forum is that the solution is to pause the conversation and then resume it once the bot is the primary owner of the conversation. What I understand is that the bot ignores the users messages during the time the conversation is paused and only responds again when the conversation is resumed. If my understanding is correct, this is the primary function of ConversationPaused() and ConversationResumed().

So in theory all good. What’s the problem, why don’t you implement this?

Well this is where I need some help, I just can’t get it done. I’ve been stuck on this for two weeks now.

I need to implement this on a server where I run my bot on Kubernetes installed with a Helm Chart. But I can’t even get it to work locally.

What did I try?

1. Resume the conversation with a FollowupAction

actions.py

class PauseConversation(Action):
    def name(self) -> Text:
        return "action_pause_conversation"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict,
    ) -> List:

        text = "pause conversation..."
        dispatcher.utter_message(text=text)

        return [
            ConversationPaused(),
            FollowupAction(name="action_resume_conversation")
        ]


class ResumeConversation(Action):
    def name(self) -> Text:
        return "action_resume_conversation"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict,
    ) -> List:

        text = "resume conversation..."
        dispatcher.utter_message(text=text)

        return [ConversationResumed()]

rules.yaml

- rule: Pause and resume with FollowupAction
  steps:
    - intent: pause_conversation
    - action: action_pause_conversation

Result:

Conversation is paused, but the FollowupAction is not executed. Which I don’t understand, because the return value of “action_pause_conversation” = [{'event': 'pause', 'timestamp': None}, {'event': 'followup', 'time

stamp': None, 'name': 'action_resume_conversation'}]

Can someone explain to me why the follow up action is not executed?

2. Pause and Resume the conversation from the same CustomAction.

actions.py

class PauseConversation(Action):
    def name(self) -> Text:
        return "action_pause_conversation"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict,
    ) -> List:

        text = "pause conversation..."
        dispatcher.utter_message(text=text)

        return [
            ConversationPaused(),
            FollowupAction(name="action_resume_conversation")
            ConversationResumed()            
        ]


class ResumeConversation(Action):
    def name(self) -> Text:
        return "action_resume_conversation"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict,
    ) -> List:

        text = "resume conversation..."
        dispatcher.utter_message(text=text)

        return []

rules.yaml

- rule: Pause and resume with FollowupAction
  steps:
    - intent: pause_conversation
    - action: action_pause_conversation    

Result:

At first, this works as expected. But when I take a closer look, I see something unexpected. If I change the method and add this check to “action_resume_conversation”:

paused = tracker.is_paused()
dispatcher.utter_message(text=f"Is tracker paused? {paused}"). 
actions.pyclass PauseConversation(Action):
    def name(self) -> Text:
        return "action_pause_conversation"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict,
    ) -> List:

        text = "pause conversation..."
        dispatcher.utter_message(text=text)

        return [
            ConversationPaused(),
            FollowupAction(name="action_resume_conversation")
            ConversationResumed()            
        ]


class ResumeConversation(Action):
    def name(self) -> Text:
        return "action_resume_conversation"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict,
    ) -> List:
        
        paused = tracker.is_paused()
        dispatcher.utter_message(text=f"Is tracker paused? {paused}"). 

        text = "resume conversation..."
        dispatcher.utter_message(text=text)

        return []

The bot response:

pause conversation…

Is tracker paused? False

resume conversation…

I expect the bot here would say:

pause conversation…

Is tracker paused? True

resume conversation…

Can someone explain to me why the tracker is not paused?

3. Pause and Resume the conversation from rules.yaml.

actions.py

class PauseConversation(Action):
    def name(self) -> Text:
        return "action_pause_conversation"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict,
    ) -> List:

        text = "pause conversation..."
        dispatcher.utter_message(text=text)

        return [ConversationPaused()]


class ResumeConversation(Action):
    def name(self) -> Text:
        return "action_resume_conversation"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict,
    ) -> List:

        text = "resume conversation..."
        dispatcher.utter_message(text=text)

        return [ConversationResumed()]

rules.yaml

- rule: Pause and resume with FollowupAction
  steps:
    - intent: pause_conversation
    - action: action_pause_conversation
    - action: action_resume_conversation    

Result:

The conversation is paused, but “action_resume_conversation” is not executed. Which I don’t understand, because it is implemented as a rule.

Can someone explain to me why “action_resume_conversation” is not executed even though it is a rule?

4. Resume the conversation with POST request.

Finally, when the conversation is paused I try to resume via a post request, but again I make little progress. This is my code which I run locally:

def resume_conversation(sender_id):
    url = f"http://localhost:5005/conversations/{sender_id}/tracker/events"
    headers = {"Content-Type": "application/json"}
    data = '[{"event": "resume"}, {"event": "followup", "name": "action_resume_conversation"}]'

    return requests.post(url=url, data=data, headers=headers)

Can someone explain to me what I do wrong here?

@Juste can you help me?

Hi @Johan1us , Were you able to implement human handoff successfully. I am trying exactly the same you did but my channel of communication is WhatsApp. How did you manage to pause and resume the conversation?

I have a bot installed on Google Cloud with the Helm Chart installation. I can’t get it to work there. So if anyone is reading this and has installed a bot with the Helm Chart and knows how “pause” and “resume” works there. Please help!

Locally, I can get it to work.

I am sharing my code with you.

First start your rasa core server on port 5005 with the --enable-api flag, e.g. rasa run --enable-api I mention this on purpose because it took me a while to figure this out.

import requests
import json

def pause_conversation(sender_id):
    url = f"http://localhost:5005/conversations/{sender_id}/tracker/events"
    headers = {"Content-Type": "application/json"}
    data = [{"event": "pause"}]

    return requests.post(url=url, data=json.dumps(data), headers=headers)


def resume_conversation(sender_id):
    url = f"http://localhost:5005/conversations/{sender_id}/tracker/events"
    headers = {"Content-Type": "application/json"}
    data = [{"event": "resume"}]

    return requests.post(url=url, data=json.dumps(data), headers=headers)

Hi @Johan1us, What i am trying to do is, when the intent human handoff is predicted, the human handoff custom action will trigger. This action will pause the conversation and bot will go out of picture and the user will be redirected to human agent. Please let me know if i am doing anything wrong. Sharing the code

class ActionTalkToHuman(Action):
    def name(self) -> Text:
        return "action_talk_to_human"

    def run(self, dispatcher, tracker, domain):
        response = "Reaching out to a human agent... "#[{}]...".format(tracker.sender_id)
        events=[]
        message="Reaching out to a human agent... "
        ConversationPaused()
        #events.extend(self.redirect_url(message,tracker,dispatcher))
        mes=self.redirect_url(message,tracker,dispatcher)
        print(mes)
        #if mes != "unpause":
        #    dispatcher.utter_message("Human agent: {}".format(message))
        #    self.redirect_url(message,tracker,dispatcher)
        #else:
        #    return [ConversationResumed()]
    
    @staticmethod
    def redirect_url(message,tracker: Tracker,dispatcher):
        dispatcher.utter_message("Human agent: {}".format(message))
        while message != "unpause":
            url = "http://127.0.0.1:5000/handoff/{}".format(tracker.sender_id)
            req = requests.get(url)
            resp = json.loads(req.text)
            print(resp)
            message = req.json()["message"]
            if "error" in req:
                raise Exception("Error fetching message: " + repr(resp["error"]))
            if message != "unpaused":
                dispatcher.utter_message("Human agent: {}".format(message))
                #message="pause"
                return message
            else:
                return [ConversationResumed()]

What I cannot manage is to resume the conversation from a custom action. So ConversationResumed() I can’t get it to work. I have discovered a sort of exception and that is if you use ConversationPaused() and ConversationResume() from the same custom action. For example
return [ConversationPaused(), FollowupAction(name=action_greet), ConversationResumed()], but this has limited functionality, so I wouldn’t focus too much on this.

What I understand so far is that once the conversation is paused, you cannot continue the conversation from a different custom action. For this you need to use the api which gives the command directly to rasa core to resume. Rasa core ignores in paused state the messages that come in the normal way.

With the code I gave you, hopefully you can get it working locally, then you can see the effect. And for a handoff this is what you want I think. Because you want to have a way that the messages the user sends to the live agent are not then answered again by the bot. This is where pause and resume come in handy. The messages are ignored if the primary recipient is the live agent and picked up again if the primary recipient is the bot.

@erohmensing can you maybe help us in our quest?

Hi @Juste, can you please help me with this. I have been stuck on this topic for a long time and I don’t know how to proceed.

hi @Johan1us if you have done it working please let me know

I’m also stuck here for a long time

thank you