Connect a live agent platform with Botpress
This documentation will guide you through setting up and implementing a Human-In-The-Loop (HITL) integration with Botpress. By following these steps, you'll be able to connect your platform to Botpress, enabling seamless interaction between your end-users and live agents.
How HITL Works in Botpress
In Botpress, the Human-In-The-Loop (HITL) functionality is powered by the HITL Agent. This agent consumes integrations that correctly implement the HITL interface, turning agent hand-off on and off.
Here is an overview of the HITL workflow in Botpress:
-
When a conversation is escalated to a live agent:
- A user is created on both Botpress and the third-party live chat platform. The link between the two users can be done on both sides, but we recommend doing it on Botpress's end using user tags.
- A conversation is initiated on Botpress, and a corresponding ticket is created on the agent handoff platform. The link between the two conversations can be done on both sides, but we recommend doing it on Botpress's end using conversation tags.
-
When the end user sends a message to a live agent: The message is forwarded to the agent handoff platform through the "hitl" channel.
-
When the live agent sends a message to the end user: The agent handoff platform calls the integration webhook, which then forwards the message to the end user.
This guide will teach you how to make your integration compatible with the HITL agent.
Configuring the Integration Definition
After completing the steps in our Quickstart Guide and installing the HITL interface, the next step is to enhance your integration definition to support HITL.
The following code snippet accomplishes the following:
- Extends the HITL interface, making this integration compatible with HITL in Botpress.
- Adds a HITL channel, which will be used to route messages between the end user and the live agent.
- Enhances conversation and user tags with additional information, ensuring proper matching of users and conversations between Botpress and your platform.
import { z, IntegrationDefinition, interfaces } from "@botpress/sdk";
import hitl from './bp_modules/hitl'
export default new IntegrationDefinition({
name: "My HITL Integration",
version: "0.0.1",
readme: "hub.md",
icon: "icon.svg",
channels: {
hitl: {
title: "My HITL",
messages: {},
conversation: {
tags: {
ticketId: {
// This is the ticket ID from your platform. It ensures that conversations are correctly matched between Botpress and your platform.
title: "Ticket ID",
},
additionalData: {
title: "You can pass additional data in the tags object.",
},
},
},
},
},
actions: {},
user: {
tags: {
// This is the user ID from the live agent platform. It ensures that users are correctly matched between Botpress and live agent platform.
agentHandoffPlatformUserId: {
title: "Live Agent Platform User ID",
},
},
},
// Extend the HITL interface to implement HITL in your integration.
}).extend(hitl, () => ({}));
Tags
Tags in this integration are customizable fields that can store any information relevant to a conversation or user. You can think of them as metadata. These tags are crucial for maintaining context and linking data between Botpress and your platform. While the examples provided focus on using IDs to match conversations and users, you can customize the tags to include any additional data that suits your needs.
Implementing the HITL Logic
Now, let’s walk through the process of implementing the integration step by step. Start by adding the following code to index.ts
to initialize the integration:
import * as sdk from "@botpress/sdk";
import * as bp from ".botpress";
export default new bp.Integration({});
1. Add Register & Unregister Methods
These methods are part of the integration's lifecycle.
-
register: This method is executed every time the integration configuration is saved. We use it to set a webhook that will be triggered when a live agent sends a message. If you manually set the webhook on the third-party platform, this step can be skipped.
-
unregister: This method is executed when the integration is uninstalled. We use it to remove the Botpress webhook from the third-party platform. If the webhook was set manually, this step can be skipped.
import * as sdk from "@botpress/sdk";
import * as bp from ".botpress";
import AgentHandoffPlatformClient from "./agent-handoff-client"
export default new bp.Integration({
register: async ({ webhookUrl }) => {
// Set the webhook to be triggered when a live agent responds
AgentHandoffPlatformClient.setWebhook(webhookUrl);
},
unregister: async ({ webhookUrl }) => {
// Remove the webhook when the integration is uninstalled
AgentHandoffPlatformClient.deleteWebhook(webhookUrl);
}
});
The webhookUrl provided in the parameters points is unique to an bot integration installation. It's refreshed when an integration is installed/re-installed. There are three webhooks that you should implement in the handler method: handling agent messages, assign a conversation to an agent, and stopping HITL. If you can set up webhooks programmatically, this is the right place to do it.
2. Implement Actions
When extending the HITL interface, you need to implement a set of predefined methods, similar to how you implement methods when working with interfaces in TypeScript. Once these methods are correctly implemented, the integration can be used by the HITL Agent. These actions are automatically called by the HITL Agent and do not need to be triggered manually. You can rely on IntelliSense to guide you on the exact parameters provided to each action.
Here are the actions you need to implement:
-
createUser: Creates a user in both Botpress and the third-party platform and maps them together. Then, the action returns the user ID of the created Botpress user. The mapping between the two users can be done on both sides, but we recommend doing it on Botpress's end using conversation tags.
-
startHitl: Creates a conversation in Botpress and a corresponding conversation (often called a ticket) on the third-party platform and maps them together. This action must return the ID of the created Botpress conversation. The mapping between the two conversations can be done on both sides, but we recommend doing it on Botpress's end using conversation tags.
-
stopHitl: Deletes a conversation on the third-party platform.
Here’s how you can implement these actions:
// import a library to interact with the agent handoff platform
import AgentHandoffPlatformClient from "./agent-handoff-client"
export default new bp.Integration({
//...
// Previously added code
actions: {
//...
// create a user in both platforms
createUser: async ({ client: botpressClient }) => {
// Create a user on the agent handoff platform
const userOnAgentPlatform = AgentHandoffPlatformClient.createUser();
// Create a user on Botpress
const { user: botpressUser } = await botpressClient.getOrCreateUser({
tags: {
// Link the Botpress user with the user on the agent handoff platform
agentHandoffPlatformUserId: userOnAgentPlatform.id,
},
});
return {
userId: botpressUser.id, // always return the newly created botpress user id
};
},
// create a conversation in both platforms
startHitl: async ({ client: botpressClient, input }) => {
const { user } = await botpressClient.getUser({
// Retrieve the user ID created in the createUser action
id: input.userId,
});
// These properties are provided from the "Escalate to a Human" action in the Studio
const { title, description } = input;
// Create a ticket on the agent handoff platform
const ticket = AgentHandoffPlatformClient.createTicket({
ticketTitle: title,
ticketDescription: description,
});
// Create a conversation in Botpress
const { conversation } = await botpressClient.getOrCreateConversation({
channel: "hitl",
tags: {
// Link the ticket with the Botpress conversation
ticketId: ticket.id,
},
});
return {
conversationId: conversation.id, // always return the newly created botpress conversation id
};
},
// close the conversation in the agent handoff platform
stopHitl: async ({ client: botpressClient, input }) => {
const { conversation } = await botpressClient.getConversation({
id: input.conversationId,
});
const ticketId = conversation.tags.ticketId;
// Close the ticket on the agent handoff platform
AgentHandoffPlatformClient.closeTicket({ ticketId });
return {};
},
//...
},
});
3. Send Messages from End User to Live Agent
To enable communication between the end user and the live agent, you'll need to add a special HITL channel that interacts with the third-party API.
// index.ts
//...
export default new bp.Integration({
// Previously implemented code
channels: {
hitl: {
messages: {
// Send a message from the end user to the live agent
text: async ({ client, conversation, ...props }: bp.AnyMessageProps) => {
const { text: userMessage } = props.payload;
const externalUserId = payload.userId ? (await client.getUser({ id: payload.userId })).tags.externalId : "BOT";
// Retrieve the ticket ID that was set in the startHitl action
const ticketId = conversation.tags.ticketId;
// Use the agent handoff platform's API to send the message
AgentHandoffPlatformClient.sendMessage(ticketId, externalUserId, userMessage);
},
//... you can add more message types if you want to support them
},
},
},
});
We suggest starting with the text message type and adding more as needed.
Watch out!
payload.userId
is the user id of the chat-user as seen from the HITL conversation.user.id
is the id of the bot. If you want the agent to know who he's talking to, you must usepayload.userId
.
4. Send Messages from Live Agent to End User
When a live agent responds, the live-agent platform needs to send a request to the integration's webhook URL. Upon receiving this request, a special handler method is triggered. Let's implement this method to forward the message to the end user.
// ...
export default new bp.Integration({
// ...previously implemented logic
handler: async ({ req, client: botpressClient, ctx, logger }) => {
const liveAgentEvent = JSON.parse(req.body);
// Fetch the Botpress user based on the user ID from the third-party platform
const { user } = await botpressClient.getOrCreateUser({
tags: {
agentHandoffPlatformUserId: liveAgentEvent.userId,
},
});
// Fetch a conversation in Botpress associated with the ticket ID
const { conversation } = await botpressClient.getOrCreateConversation({
channel: 'hitl',
tags: {
ticketId: liveAgentEvent.ticketId,
},
});
switch (liveAgentEvent.eventType) {
case 'newMessage':
// Send the received message from the live agent to the end user
await botpressClient.createMessage({
type: 'text',
userId: user.id,
conversationId: conversation.id,
payload: { text: liveAgentEvent.message },
});
break;
}
},
});
In the above example, we don't check the route, or method, but we can do that with the req
parameter of the handler, which returns an express.js-like request object. You can add if
conditions to route different actions you'd like, such as assigning agents, or releasing the user back to the bot.
Why do I keep seeing getOrCreate?
We don't actually want to create users or conversations at this point. Its simply a matter of the
get
methods not supporting finding by tags, while thegetOrCreate
methods do.
5. Inform Bot When HITL Started and Stopped
We need to emit two events to inform the bot about the status of the Human-In-The-Loop (HITL) process:
hitlAssigned: Tells the bot that the conversation has been successfully escalated to a live agent.
hitlStopped: Tells the bot that the conversation with the live agent has ended.
// index.ts
// ...
export default new bp.Integration({
// ...previously implemented logic
handler: async ({ req, client: botpressClient, ctx, logger }) => {
const liveAgentEvent = JSON.parse(req.body);
switch (liveAgentEvent.eventType) {
case 'newMessage':
// ...
break;
case 'ticketAssigned':
// Emit an event to inform the bot that HITL has been assigned
await botpressClient.createEvent({
type: 'hitlAssigned',
payload: {
conversationId: conversation.id,
userId: user.id,
},
});
break;
case 'ticketSolved':
// Emit an event to inform the bot that HITL has been stopped
await botpressClient.createEvent({
type: 'hitlStopped',
payload: {
conversationId: conversation.id,
},
});
break;
}
},
});
Full Example
// index.ts
import * as sdk from "@botpress/sdk";
import * as bp from ".botpress";
// Replace with your actual platform client library
import AgentHandoffPlatformClient from "./agent-handoff-client";
// Initialize the integration
export default new bp.Integration({
// Register method to set the webhook when configuration is saved
register: async ({ webhookUrl }) => {
// Set the webhook to be triggered when a live agent responds
AgentHandoffPlatformClient.setWebhook(webhookUrl);
},
// Unregister method to remove the webhook when integration is uninstalled
unregister: async ({ webhookUrl }) => {
// Remove the webhook when the integration is uninstalled
AgentHandoffPlatformClient.deleteWebhook(webhookUrl);
},
// Define the HITL actions
actions: {
// Create a user on both Botpress and the agent handoff platform
createUser: async ({ client: botpressClient }) => {
// Create a user on the agent handoff platform
const userOnAgentPlatform = AgentHandoffPlatformClient.createUser();
// Create a user on Botpress
const { user: botpressUser } = await botpressClient.getOrCreateUser({
tags: {
// Link the Botpress user with the user on the agent handoff platform
agentHandoffPlatformUserId: userOnAgentPlatform.id,
},
});
return {
userId: botpressUser.id,
};
},
// Start a HITL session by creating a conversation on both platforms
startHitl: async ({ client: botpressClient, input }) => {
const { user } = await botpressClient.getUser({
// Retrieve the user ID created in the createUser action
id: input.userId,
});
// These properties are provided from the "Escalate to a Human" action in the Studio
const { title, description } = input;
// Create a ticket on the agent handoff platform
const ticket = AgentHandoffPlatformClient.createTicket({
ticketTitle: title,
ticketDescription: description,
});
// Create a conversation in Botpress
const { conversation } = await botpressClient.getOrCreateConversation({
channel: "hitl",
tags: {
// Link the ticket with the Botpress conversation
ticketId: ticket.id,
},
});
return {
conversationId: conversation.id,
};
},
// Stop a HITL session by closing the conversation on the agent handoff platform
stopHitl: async ({ client: botpressClient, input }) => {
const { conversation } = await botpressClient.getConversation({
id: input.conversationId,
});
const ticketId = conversation.tags.ticketId;
// Close the ticket on the agent handoff platform
AgentHandoffPlatformClient.closeTicket({ ticketId });
return {};
},
},
// Define the HITL channel to send messages from the end user to the live agent
channels: {
hitl: {
messages: {
// Send a message from the end user to the live agent
text: async ({ client, payload }) => {
const { text: userMessage, conversation, userId } = payload;
// Retrieve the ticket ID that was set in the startHitl action
const ticketId = conversation.tags.ticketId;
// Use the agent handoff platform's API to send the message
AgentHandoffPlatformClient.sendMessage(ticketId, userId, userMessage);
},
},
},
},
// Handler to process messages sent from the live agent to the end user
handler: async ({ req, client: botpressClient }) => {
const liveAgentEvent = JSON.parse(req.body);
// Fetch the Botpress user based on the user ID from the agent handoff platform
const { user } = await botpressClient.getOrCreateUser({
tags: {
agentHandoffPlatformUserId: liveAgentEvent.userId,
},
});
// Retrieve or create a conversation in Botpress associated with the ticket ID
const { conversation } = await botpressClient.getOrCreateConversation({
channel: "hitl",
tags: {
ticketId: liveAgentEvent.ticketId,
},
});
switch (liveAgentEvent.eventType) {
case "newMessage":
// Send the received message from the live agent to the end user
await botpressClient.createMessage({
type: "text",
userId: user.id,
conversationId: conversation.id,
payload: { text: liveAgentEvent.message },
});
break;
case "ticketAssigned":
// Emit an event to inform the bot that HITL has been assigned
await botpressClient.createEvent({
type: "hitlAssigned",
payload: {
conversationId: conversation.id,
userId: user.id,
},
});
break;
case "ticketSolved":
// Emit an event to inform the bot that HITL has been stopped
await botpressClient.createEvent({
type: "hitlStopped",
payload: {
conversationId: conversation.id,
},
});
break;
}
},
});
Production Example
For a production-grade implementation, you can refer to the Zendesk integration maintained by the Botpress team. Check it out here.
Having difficulties with HITL Interfaces?
Contact us at [email protected] with as much details as possible about your company and your use case.
Updated about 13 hours ago