How to Build a “Human” Handoff Feature with Rasa

“How can I do human handoff?”

It’s a pretty common question on the Rasa Forum. No matter how well a conversational assistant is designed, sometimes the best action it can take is to hand the user off to a human agent. We decided this question was common and important enough to warrant building a demo for it as part of our starter packs.

The first challenge we faced was an obvious one: usually an assistant hands off to a human chat or phone agent, but our starter packs are standalone chatbots—they don’t have a human to hand off to. We wanted to build something that users could try out without recruiting friends or colleagues to act as “human agents,” so we got creative: we decided to configure two starter pack bots to hand off to each other. From a technical standpoint, the handoff process is the same whether the bot hands off to a human or another bot. The user’s experience after handoff will be different of course, but for demonstration purposes, bot-to-bot handoff was the most shareable way to do it.

General Approach

With any kind of handoff, the first matter to settle is whether the bot will still be involved in the conversation (even if silently) after handoff. We concluded it’s almost always better for the bot to be completely disconnected from the conversation once handoff has happened.

Why? Once the bot has handed off, the messages that come in through the channel could be completely outside the bot’s domain, with either no labels (human handoff) or incompatible labels (bot handoff). You might want to look at conversations with a human agent later to see if there’s something you can use to improve your bot, but the handed-off parts of the conversation should be kept completely separate from the bot’s own conversations. That makes it easier to review each conversation according to who the user was talking to.

For this reason, we decided it was best to configure handoff from the front-end. The flow would work like this: Bot #1 would ask the user if they wanted to be handed off to Bot #2, and the messaging channel would actually switch which chat endpoint it was listening to. After the chat endpoint was switched, Bot #1 would no longer receive any messages, unless the user asked to be handed back to Bot #1, in which case the conversation would continue where it left off.

The bot-to-bot handoff workflow

Implementation

Once we’d decided on an approach, we could dive into the details of building out the feature. Here are a few of the considerations we made along the way.

Choosing a Channel

Since we wanted to do handoff from the front-end, we needed to choose a messaging channel. We settled on Chatroom because it is a straightforward REST channel that’s easy to get started with, without setting up authentication or additional accounts. Chatroom is also open source, so we could configure it as much as we needed to.  

While we chose to use Chatroom, handoff is possible on many channels. Some channels like Facebook Messenger have a built in protocol for switching which bot or app is in charge of a conversation. If you’re looking to implement handoff in your own bot, check out your messaging channel’s documentation to see if it has similar capabilities.

Configuring the Channel

To configure handoff from the front-end, we first needed to set up Chatroom to support switching chat endpoints, which Chatroom calls the “host”. We forked the project and made some code changes that allow Chatroom to switch which host it listens to based on a bot message with a specific payload. The payload we configured includes the URL for the chat endpoint to hand off to, meaning the handoff URL could change depending on which bot was in charge of the conversation.

Configuring the Bots

With a handoff-capable channel at hand, we just needed to set up our bots. We decided to use the Financial-Demo and Helpdesk-Assistant, which would hand off to each other.

You’ll recall that the Chatroom channel is set up to switch chat endpoints when it receives a specific payload, so each bot needs to be able to send that payload. To do this, we needed to write a story for sending the handoff payload and define the handoff payload itself. When the user tells one of the bots they want to speak to a human, the bot offers the user a list of possible bots to hand off to based on a configuration file. When the user chooses a bot, another custom action sends the correct payload:

## handoff
* human_handoff
 - utter_ask_handoff
 - action_handoff_options
* trigger_handoff
 - action_handoff
class ActionHandoff(Action):
   def name(self) -> Text:
       return "action_handoff"
 
   async def run(
       self,
       dispatcher: CollectingDispatcher,
       tracker: Tracker,
       domain: Dict[Text, Any],
   ) -> List[EventType]:
 
       dispatcher.utter_message(template="utter_handoff")
       handoff_to = tracker.get_slot("handoff_to")
 
       handoff_bot = handoff_config.get(handoff_to, {})
       url = handoff_bot.get("url")
 
       if url:
           if tracker.get_latest_input_channel() == "rest":
               dispatcher.utter_message(
                   json_message={
                       "handoff_host": url,
                       "title": handoff_bot.get("title"),
                   }
               )
           else:
               ...
 
       return []

The bot that accepts the handoff should recognize the handoff payload and greet the user appropriately. We settled on simple welcome messages telling the user which bot they were talking to, and mapped it to the expected `handoff` intent:

intents:
...
- handoff:
   triggers: utter_greet
 
responses:
...
 utter_greet:
 - text: Hi! I'm your Financial Assistant!

In a  human-handoff scenario, you might want to forward the human agent the entire chat transcript or send them a message to tell them where the user is coming from so the human agent has the necessary context.

Try it out

If you want to try it out, you can clone the forked version of Chatroom and the Helpdesk-Assistant and Financial-Demo starter packs and follow the instructions in their READMEs.

There are a few things you can customize to adapt the demo to your use case:

Changing the handoff hosts

You can change which handoff options are offered to the user by modifying handoff_config.yml. The starter packs are configured to hand off to each other, but if you’d rather hand off to some other bot, or add more bots to hand off to, you can add your bot’s chat endpoint and title to the config file. Of course, a bot will need to be running at the address you provide. Here we’ve added Moodbot to our bots:

handoff_hosts:
   helpdesk_assistant:
     title: "Helpdesk Assistant"
     url: "http://localhost:5005"
   moodbot:
     title: "MoodBot"
     url: "http://localhost:5007"

Changing the handoff intent

The default intent that Chatroom will send to the bot being handed off to is handoff. You can change this by editing the properties in chatroom.js:

   var chatroom = new window.Chatroom({
...
     handoffIntent: "handoff_other",
...
   });

Changing what happens after handoff

Each starter pack is configured to utter a welcome message when it receives a handoff payload. You can change this behaviour by changing the triggers action mapping for the /handoff intent, or by writing a story with multiple actions after handoff. In the example below, Financial-Demo would send both a greeting and a help message after handoff:

## greet and help after handoff
* handoff
 - utter_greet
 - utter_help

Takeaways

Sometimes the best thing a bot can do for a user is point them to the right person or resource. Handoff lets it do so seamlessly, without asking the user to change interfaces. This demo is only one simplified, shareable implementation of “human” handoff; there are many alternative approaches out there. But no matter which approach you take, we recommend completely disconnecting the bot from the conversation after handoff.

If you have an idea for improving this demo, we’d love to hear it! Both starter packs and Chatroom are open-source projects, so please submit an issue or PR if you’d like to contribute.