Intercom lets businesses communicate with customers or prospective customers in-app or on-website. Intercom developers can initiate support conversations, as well as sales conversations. On the business side, Intercom is generally manned by an employee, although it lets users (businesses) create simple.
It’s a great technology. I think it can be made greater yet if one could automate the customer conversations that happen through it at a human level.
How Intercom does bots today
Dasha AI
Challenges to overcome
Integration
- Be notified of newly-initiated conversations that have yet to be responded to by a human agent
- Read the content of the customer’s communication.
- Notify the customer that their message(s) have been read.
- Decipher the customer’s intent and select an appropriate response.
- Respond to the customer in the conversation.
- Search conversation by parameters;
- Reply to a specific conversation;
- Create/open/close/reopen conversations;
- Read messages from any given conversation.
Step one. Getting the access token for Intercom
Step two. Apply integration to your code
import { IntercomClient } from "./src/intercomClient";
const application = await dasha.deploy("./graph", { groupName: "Default" }); const intercom = await IntercomClient.create("https://api.intercom.io", { accessToken: process.env.INTERCOM_APIKEY!, });
try { application.connectionProvider = async (conv) => dasha.chat.connect(await dasha.chat.createConsoleChat()); application.setExternal("send_report_on_manager", (args, conv) => { console.log({ set_args: args, conversation: conv }); return "ok"; });
await intercom.simpleConnectToDashaApp(application);
intercom.setLogger(console);
Details of Intercom integration library
Function for connecting Dasha application to Intercom
public async simpleConnectToDashaApp( application: dasha.Application< Record<string, unknown>, Record<string, unknown> > ): Promise<void>
if (this.isSimpleUsed) { return; } this.isSimpleUsed = true; const closeTrigger = false; const conversations = new Map<string, IntercomConversation>();
const dashaChat = await dasha.chat.createChat(); const chat = await this.runChatInConversation( conversations.get(conv.input.conversationId as string)!.id, conversations.get(conv.input.conversationId as string)?.open ?? false, { closeAfter: { messageOnClose: "Conversation was closed because dialog was finished", }, periodReading: 100, startOptions: { skipLastMessage: !conversations.get( conv.input.conversationId as string )?.open, }, } );
chat.run(dashaChat).catch(this._logger?.error); return dasha.chat.connect(dashaChat); }; application.queue.on("ready", async (id, conv) => { this._logger?.info(`Got new starting conversation ('${id}').`); // Checking that the conversation belongs to this intercom client instance if (!conversations.has(id)) { this._logger?.info( `Try starting conversation from not this instance of intercom client new conversation ('${id}'). ` + `Conversation was been rejected` ); return; } this._logger?.info(`Prepare intercom conversation for chat ('${id}').`); this._logger?.info( `Connecting to dasha sdk chat protocol for conversation ('${id}').` ); conv.input = { conversationId: id, continueConversation: conversations.get(id)?.open, };
conv.on("transcription", async (transcription) => { if (this.onTransctiption) { await this.onTransctiption(conversations.get(id)!, transcription); } }); this._logger?.info(`Conversation was been accepted ('${id}').`);
const result = await conv.execute(); try { this._logger?.info(`Conversation finish ('${id}').`); if (this.onCompletedJob) { await this.onCompletedJob(conversations.get(id)!, result); } } finally { conversations.delete(id); } }
await this.watchConversations( async (conv) => { try { // Checking that a conversation is already in progress if (conversations.has(conv.id)) { return; } this._logger?.info(`Got new conversation ('${conv.id}')`); conversations.set(conv.id, conv); const date = Date.now(); const notBefore = new Date(date); const notAfter = new Date(date + 3600 * 1000); await application.queue.push(conv.id); this._logger?.info( `Conversation ('${conv.id}') was enqueued into dasha sdk` ); } catch { this._logger?.error( `Could not enqueue conversation ('${conv.id}') into dasha sdk` ); } }, () => { return closeTrigger; } ); this.isSimpleUsed = false; }
The function that lets us search ouyt a new conversation
public async watchConversations( // We need callback to start work with conversation callback: (conv: IntercomConversation) => Promise<void>, // Hook, for end watching conversations stopTrigger: () => boolean ): Promise<void> { // Search over all conversation and find only those which have not replied message while (!stopTrigger()) { // take first 20 conversation from first page in listing method let conversation = await this.getConversation(20, 1); // skip all conversation which was been replied by admin for (const conv of conversation.conversations.filter( (x) => x.statistics.last_contact_reply_at > x.statistics.last_admin_reply_at )) { // send new potential conversation for working callback(conv); } // now we go over all other conversation and do same action like above while (conversation.pages.total_pages >= conversation.pages.page) { conversation = await this.getConversation( 20, conversation.pages.page + 1 ); for (const conv of conversation.conversations.filter( (x) => x.statistics.last_contact_reply_at > x.statistics.last_admin_reply_at )) { callback(conv); } } // timeout in order not to spam requests await new Promise((resolve, reject) => setTimeout(resolve, 1000)); } }
Function for start chatting
public async runChatInConversation( conversationId: string, // is the conversation open at the moment conversationWasOpen: boolean, options: { // if define than conversation after and need close // else conversation does not closing closeAfter?: { messageOnClose: string; }; // if nod defined than used first available user message in conversation // else use last available user message in conversation startOptions?: { // ignore last message, and awaiting new message skipLastMessage: boolean; }; // period of receiving new messages from the api intercom periodReading: number; } ): Promise<IIntercomChat> { // Create client for each conversation const intercomConversationClient = new IntercomConversationClient( this, conversationId, options.startOptions === undefined ? 0 : await this.getLastTime( conversationId, options.startOptions.skipLastMessage ), options.periodReading );
return { // hook which was triggered when conversation was started on the dasha platform. run: async (chatChannel: dasha.chat.Chat): Promise<void> => { try { // open conversation if it`s needed if (!conversationWasOpen) { await this.openConversation(conversationId); } // run conversation chat await intercomConversationClient.run(chatChannel); } finally { // close conversation if it`s need if (options.closeAfter !== undefined) { try { await this.closeConversation( conversationId, options.closeAfter.messageOnClose ); } catch (e) { this._logger?.error(JSON.stringify(e)); } } } }, }; }
Implementing a class to chat within a single conversation.
export class IntercomConversationClient implements IIntercomChat { // Id of conversation on Intercom. public readonly conversationId: string; // Used Intercom client. private readonly client: IIntercomClient; // Delay for reading new messages from Intercom conversation. private readonly delayReading: number; // This property indicate that this client in a close state. private close = false; //Creation time of the last message we read from Intercom conversation. private lastTime = 0; public constructor(client: IIntercomClient, conversationId: string, lastTime: number, delayReading: number) { this.client = client; this.conversationId = conversationId; this.lastTime = lastTime; this.delayReading = delayReading; }
public async run(chatChannel: dasha.chat.Chat): Promise<void> { // listen message from dasha sdk application and write into intercom chatChannel.on("text",async(text)=>{ await this.client.reply(this.conversationId, text); }); chatChannel.on("close",()=>{ this.close = true; }); chatChannel.on("error",(error)=>{ this.close = true; console.warn("chat error:", error); }); // listen message from intercom and write into dasha sdk chat channel await this.readConversation(chatChannel); }
private async readConversation(chatChannel: dasha.chat.Chat): Promise<void> { while (!this.close) { await new Promise((resolve, _) => setTimeout(resolve, this.delayReading)); // read new messages in intercom conversation const obj = await this.client.readMessages(this.conversationId, this.lastTime); // mark conversation as read. This see user in intercom await this.client.markAsRead(this.conversationId); this.lastTime = obj.lastTime; // send new message from intercom into dasha sdk application for (const msg of obj.messages) { await chatChannel.sendText(msg); } } } }
The Dasha conversational AI app
- .graph/main.dsl is the DashaScript file which you use to construct the conversational pathway that Dasha is to take. Here you can make your conversation as absolutely complex or simple as you wish. For the purposes of this tutorial we will opt for simple.
- data.json is the file where you store data that is used to train the neural networks to properly understand the customer’s/user’s intent.
- phrasemap.json is where you store all the phrases to be used by Dasha AI in the context of the conversation. (tip: you can list phrases directly in main.dsl by using the #sayText() function. Using the phrase map for it is, however, the better way for scalable apps.)
- Node is literally just that - a node in the conversation. It’s a point of interaction between the user and the AI. A node must always have another node or digression leading into it.
- Digression is, in essence, a node that does not have to have anything lead into it. A digression can be called up by the user at any point in the conversation. Digressions are used to imitate the way a human might adapt when the person they are talking to suddenly throws them off on a tangent. Read this for a deeper dive on digressions.
- Intent refers to the meaning behind the user/customer’s phrases. When you are speaking to someone you figure out the meaning of their words and phrases and sentences based on context, a variety of clues and previous experience. Right now Dasha AI interprets meaning solely from text (we are working on emotional analysis). Intents refer to the . The Dasha Cloud Platform has neural networks dedicated to training for user intents and to analysing said intents in the course of a conversation. Take a look here for more on intents.
- Named entities are fields referring to names, place names, objects, etc. that you need Dasha AI to identify within the customer/user’s responses. Named entities are also processed by neural networks and you provide training data using data.json in a format similar to intent training data. For more on named entities you can read this piece from the leader of the team which constructed the neural networks.
import "commonReactions/all.dsl";
context { input conversationId: string; input continueConversation: boolean; last_message: string = ""; can_say: boolean = false; output status: string?; output serviceStatus: string?; }
external function send_report_on_manager(report: string): unknown;
start node root { do { #connectSafe(""); if(!$continueConversation) { #say("greeting", repeatMode: "ignore"); #say("how_can_i_help_you"); } goto hub; } transitions { hub: goto hub; } }
"problem_with_phone": { "includes": [ "I can't seem to make a phone call", "I have a problem with my phone", "I'm afraid I broke my phone", "Phone problem" ],
digression problem_dig { conditions { on #messageHasAnyIntent(["problem_with_phone", "problem_with_manager", "have_problem"]); } do { digression disable problem_dig; goto problem; } transitions { problem: goto problem; } }