Forwarding messages to Slack and vice versa for human hand-off

This thread was very helpful to get started with a human hand-off in RASA, but there’s still some way to go before we have a working example.

In this particular thread, code is provided that will allow us to send a message to a Slack channel when a user requests to talk to an agent.

It is not discussed how the agent can forward messages back to the user and how the user can continue to engage with the agent until it is indicated that the bot can take over again.

The main issue to solve is the following: how can we extend the code in order to send user messages to Slack and Slack messages to the user so long as the hand-off is required?

The custom action provided in the thread looks as follows:

import os
from slack import WebClient
from slack.errors import SlackApiError
 
client_slack = WebClient(token=os.environ["SLACK_API_TOKEN"], run_async=True)
SLACK_SUPPORT_CHANNEL = os.environ["SLACK_SUPPORT_CHANNEL"]
 
 
class ActionHumanHandoff(Action):
   def name(self):
       return "action_human_handoff"
 
   async def run(
       self, dispatcher, tracker, domain, reason: Text = None,
   ):
       events = []
       text = f"Help needed for user {tracker.sender_id}"
       try:
           response = await client_slack.chat_postMessage(
               channel=SLACK_SUPPORT_CHANNEL, text=text
           )
           if response.get("ok"):
               dispatcher.utter_message(template="utter_handover_to_support")
               events.append(ConversationPaused())
       except SlackApiError as e:
           dispatcher.utter_message(
               template="utter_technical_issue", extra_message=e.response["error"],
           )
       return events
1 Like

Hi, one possible way to accomplish that would be the following:

So this means you

  • have one action action_human_handoff like you suggest above. In addition to what it does already, have it set a slot like handoff_active.
  • add a policy like described in the post I linked

And then for sending messages back and forth between the user and the human support, you could implement another custom action like action_talk_to_human which your policy always predicts while the handoff_active slot is set. The action action_talk_to_human could e.g. use the Slack API for sending messages to send the last message from the user as a reply to the thread that was started by action_human_handoff, use the Slack API for retrieving messages to get the answer that is sent by the human support, and then forward this answer to the user.

Once the hand-off is no longer required, the human support should send a specific message, e.g. /handback. The action action_talk_to_human needs to recognise this and unset the slot handoff_active.

Hope that helps, let me know if you have more questions.

6 Likes

Awesome stuff!

I’ll work in this for a while and provide feeback when I get it working :slight_smile:

Thanks for the detailed instructions, this is the best approach to handle the human hand-off so far. I was exploring the looped FollowupAction path and quickly realized it would be overridden with new user input.

I will work on this too and will share my implementation once done.

My case: I have connected my bot with MS Teams and when the user request for human agents, the chat will be queued up in service now chat dashboard for the agents to pick it up.

For future readers, this is the path I have taken so far:

  1. User types ‘talk to live agent’ or something similar.
  2. The custom action action_live_agent creates a live support chat in service now and sets the handoff_active to true. It also sets slots like live_chat_id (live chat id), live_chat_user (user_id to differentiate messages between agent and user), and live_chat_last_updated (timestamp of the last sent message to compare and only get the new messages later).
  3. The custom handoff policy HandoffPolicy with the highest priority checks handoff_active slot and predicts action action_live_agent_chat until handoff_active slot is set to false.
  4. action_live_agent_chat will post user messages to the live chat using live_chat_id. It will then do the following in order:
  • Check user message if message == "/restart" or message == '/end_chat'. If true, it will end the live chat by calling leave chat API and set slots handoff_active, live_chat_id, and live_chat_last_updated to None. HandoffPolicy will then stop predicting the custom action and bot will start handling responses. Ref. Screenshot 1.
  • If user message is not ‘/restart’ or ‘/end_chat’, the custom action will post the latest message to live agent chat thread and will retrieve the message thread.
  • It will loop through the message thread. If the message creator is not live_chat_user and message timestamp is greater than live_chat_last_updated, it will utter the messages to the end-user. After uttering the messages to the end-user, it will set live_chat_last_updated to the timestamp of the latest message in the thread. Ref. Screenshot 2.
  • It will then check if the received message contains the text has closed the support session (this is a part of the message that is sent once the live agent ends the chat session). If true, it will end the live chat by calling leave chat API and set slots handoff_active, live_chat_id, and live_chat_last_updated to None. Ref. Screenshot 3.
  • If any of the API calls (posting/retrieving messages) throws an error, it will end the live chat by calling leave chat API and set slots handoff_active, live_chat_id, and live_chat_last_updated to None. It will utter message with details like (chat summary, chat id, last updated). If there is an error in calling leave chat API, it will set slots handoff_active, live_chat_id, and live_chat_last_updated to None and utter generic message like “The chat session is closed/could not be located. Please try reconnecting to live agent if you still need help!”

Screenshot 1: User initiated live chat end:

Screenshot 2: Live agent conversation:

Screenshot 3: Live agent closed the chat session:

5 Likes

Thank you so much for your detailed instruction, I have posted my implementation so far.

I have a question in the quoted part. I have a service now API to get the message thread, should I be polling the API to get the updated messages or is there a better implementation?

Really good stuff man. I’ve yet to start on my implementation, just got back from leave. Will post solution soon :slight_smile:

Hi Niraj!

First, I just want to say thank you for posting a detailed description of your implementation. It was a great reference for the development of my own solution.

As far as your API Polling question. I’m currently using Slack for my implementation, which offers an Events API with a configurable Callback URL.

I set that endpoint up in the Rasa Server, which is essentially a modified version of the execute_action() function associated with the “/conversations/<conversation_id>/execute” endpoint.

It checks to make sure that the message isn’t from the bot, then parses the sender_id (associated with the parent message of the Slack thread), and fetches the tracker. It then calls for a custom action to execute, that I setup to retrieve the most recent message and emit it to the tracker’s output channel.

This way, there is no polling. The endpoint receives a request when a new message is posted in the Slack channel, and then the action executes.

I’m not familiar with the Teams APIs, but it looks like this might offer some similar functionality:

https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-outgoing-webhook

1 Like

Thanks Patrick! Your explanation seems to be the right way of doing it. I will work on the implementation in couple of days. I will share it for future implementors of servicenow and MS Teams API.

I think it’s not a good approach to use Rasa as the centralized messaging router. It’s not designed to play this role. Even if you implement the suggested solution, your tracker store will contain conversations that are unrelated to the domain of the bot.

Instead, there should be a client UI that switches between Rasa and other services (Slack, another Rasa bot, etc). The action would send a message that the client UI recognizes as a transfer (via slot metadata) and pauses the conversation. The UI then connects to Slack or some other service.

3 Likes

@nchauras Hey Niraj, thanks for this post. It’s very helpful. Could you share the code for your policy? I’ve been trying to implement a policy that predicts an action when a slot is set but haven’t been having much luck

In your custom policy __init__ method, set the priority to highest int value (depends on your config and the priority for other policies). e.g. priority: int = <set to highest int value> and handoff_action_name: Text = "action_live_agent_chat".

In predict_action_probabilities method, predict the handoff_action if tracker.get_slot('handoff_active') is True.

Hope this helps!

@nchauras I put the priority in config.yml like so:

...
  - name: custom_policies.testpolicy
    priority: 6

the __init__.py file in the directory housing my custom action is empty.

For some reason it seems that the policy is not actually running the action, though. Here’s my code in predict_action_probabilities:

 is_triggered = tracker.get_slot('my_slot')

if is_triggered:
    logger.info('if statement ' + str(is_triggered) + ' ' + str(type(is_triggered)))
    return confidence_scores_for("action_my_action", 1.0, domain)
else:
    logger.info('in else statement ' + str(is_triggered) + ' ' + str(type(is_triggered)))
    return self._default_predictions(domain)

I also made sure to mount the file in docker-compose.override.yml so that shouldn’t be an issue

@stephens @nchauras @pcoutu Hi!

Here we are trying to do human-handoff with Rasa bot. The hard part is retrieve the message from the human agent and send it to the user, but I do not want to do polling whitin an action to do that.

Maybe something like this can be implemented:

Can somebody help me with how to implement this? I’m thinking about rabbit or something like this but I’m not sure.

We recently added an enhancement to the chatroom Rasa widget here and we documented it’s use in the example helpdesk-assistant here.

It is an example of what you show above without the use of a Router or Handoff DB. When the user requests a transfer, the Rasa response can include slots or metadata that is passed to the UI component. In the example implementation, it passes the URL to another Rasa chatbot but you could add your own metadata.

2 Likes

Thank you, but I’m using whatsapp not a personal UI

I ended up creating Webhook to push human messages to the bot using MS Teams API directly (as MS Teams is the front end for me). The messages from the user are pushed out to the human agent using rasa custom action & handoff policy and the human agent reply triggers webhook response directly to the front end.

I followed this route to record the user messages as to why the human agent was requested and we do a weekly review to include those use-cases in the bot itself. This has helped us with the CDD tremendously.

1 Like

Interesting! Does that mean that you review conversations containing both human chats and bot chats mixed together?

Hi @nchauras,

Could you share the implementation code or repo to go through?

Thanks.

Hi, were you able to implement this? Can you please share the steps you took