Skip to content
FacebookYouTubeX (Twitter)

Tutorial 3 - Automatic Human Handoff with Fallback Intent

Add automatic human handoff using fallback intent to your Dialogflow Agent integrated with Pingstreams.

We’ll start from Tutorial 1 - Dialogflow as external chatbot, just adding fallback handling logic to our original endpoint.

You must use the code in Tutorial 1. The code is available on Github here.

Fork the tutorial code using the Fork button. Now you have a copy of the tutorial on your own repo.

This time we’ll give a try to the fallback handoff endpoint already embedded into our Node.js app:

// Tutorial 3 - Automatic human handoff based on fallback intent
var consecutive_fallback_count = {};
const MAX_FALLBACKS = 4;

app.post("/bot-fallback-handoff/:botid", (req, res) => {
  const psClient = new PingstreamsClient({request: req});
  console.log("psClient", psClient);
  
  const botid = req.params.botid;
  const supportRequest = psClient.supportRequest;
  
  // immediately reply back
  res.status(200).send({"success":true});
  
  // reply messages are sent asynchronously
  const dialogflow_session_id = supportRequest.request_id;
  const lang = 'en-EN'; // lang must be the same of the Dialogflow Agent
  const credentials = JSON.parse(process.env[botid]);
  
  runDialogflowQuery(psClient.text, dialogflow_session_id, lang, credentials)
    .then(function(result) {
      if (!consecutive_fallback_count[dialogflow_session_id]) {
        // init consecutive fallback count for this conversation
        consecutive_fallback_count[dialogflow_session_id] = 0;
      }
      
      if (result.intent.isFallback) {
        consecutive_fallback_count[dialogflow_session_id]++;
        console.log("fallback of", dialogflow_session_id, "is", consecutive_fallback_count[dialogflow_session_id]);
      } else {
        // reset fallback on every positive hit.
        // here you can also evaluate result.intentDetectionConfidence
        // and consider it as a fallback if under some threshold value
        consecutive_fallback_count[dialogflow_session_id] = 0;
      }
      
      if (res.statusCode === 200) {
        let msgs = [];
        
        if (consecutive_fallback_count[dialogflow_session_id] == MAX_FALLBACKS) {
          consecutive_fallback_count[dialogflow_session_id] = 0;
          msgs.push({
            "text": "We are putting you in touch with an operator..."
          });
          msgs.push({
            "text": "\\agent",
            "attributes": {subtype: "info"} // this message is hidden in the widget
          });
        } else {
          msgs.push({
            "text": result['fulfillmentText']
          });
        }
        
        msgs.forEach(m => {
          psClient.sendMessage(m, function (err) {
            console.log("Message", m.text, "sent.");
          });
        });
      }
    })
    .catch(function(err) {
      console.log('Error: ', err);
    });
});

As in Tutorial 1 you have to create a Dialogflow agent, train the same Agent following the instructions in this tutorial, then go to Pingstreams and create an external bot and connect it to Routing (or to a Department).

As you can see this new endpoint starts with these two lines of code:

var consecutive_fallback_count = {};
const MAX_FALLBACKS = 4;

The first variable consecutive_fallback_count saves the current number of fallbacks for every conversation. The second variable MAX_FALLBACKS represents the maximum number of consecutive fallbacks in a conversation.

In this tutorial, every time there is a fallback the number of consecutive fallbacks for the same conversation is incremented by one unit.

As soon as the total number of consecutive fallbacks reaches MAX_FALLBACKS a couple of messages is sent back to the client. The first message is shown to the client and is a custom message that you can send to indicate that the conversation is switching to humans. The second message \agent is intercepted by Pingstreams and drives the system to remove the bot and invite an agent to the conversation, following the Department rules.

msgs.push({
  "text": "We are putting you in touch with an operator..."
});
msgs.push({
  "text": "\\agent",
  "attributes": {subtype: "info"} // this message is hidden in the widget
});

To hide a message simply add a sub-property subtype: 'info' to the attributes property of a message. This is useful for system commands like \agent that shouldn’t be visible to the end user.

You can customize the fallback behavior by:

  1. Adjusting the threshold: Change MAX_FALLBACKS to a higher or lower number
  2. Using confidence scores: You can also evaluate result.intentDetectionConfidence and consider low confidence responses as fallbacks
  3. Custom handoff messages: Modify the handoff message to match your brand voice

This automatic handoff system provides several advantages:

  • Prevents user frustration: Users don’t get stuck in endless loops with an unhelpful bot
  • Improves customer experience: Seamless transition to human agents when needed
  • Reduces abandonment: Users are less likely to leave when they know help is available
  • Smart escalation: Only escalates when the bot clearly cannot help

Enjoy Pingstreams!