Most of the time, when interfacing with a bot, the bot will send you a "welcome message" or introduce itself before you type anything. This is considered good bot etiquette.

Recently, I was involved with integrating a bot with LivePerson using a Direct Line Channel for a client that wanted to put a bot as a proxy between the chat window users and their live support agents. If the bot could not help the user, it would escalate to a human agent.

LivePerson provides AI-powered messaging and chatbots to companies that want to put a chat interface on their support process. They have their own bot framework but also can integrate with 3rd Party Bots across a variety of Channels.

Our bot worked perfectly, until I ran it in the Direct Line channel, and then it didn't work... at all. The problem was the the bot's first message, which was a welcome message, and a second message, which was a question to the user were not being sent. This was a surprise as the messages worked fine when running locally via the Bot Emulator and when using Web Chat in Azure

This blog post is how I solved the case of the missing welcome message.

Bot Emulator and Azure Web Chat

Like I mentioned, the bot worked perfectly in the Bot Emulator
BotWorkingInEmulator_resized

And in Azure Web Chat
BotWorkingInAzureWebChat_resized

In both of the screengrabs above, you can see the bot sending the welcome message and the first question to the user, without the user having to type anything.

Direct Line

A bot just sitting in Azure isn't going to provide much business value without users being able to get to and interface with the bot. Because I eventually had to tie the bot back to LivePerson, I was going to have to use a Direct Line Channel to allow the bot to be used.

Connecting a bot to Direct Line is really simple, and there are great docs here that show how to do it. I won't be diving into how to connect a bot to Direct Line in the this blog.

After I had my bot configured for Direct Line, I copied the Secret Key and headed over to LivePerson to integrate the bot
ConfigureDirectLine_resized

Connecting the Bot to LivePerson

LivePerson has docs that show you how to hook up a "3rd Party Bot" using a Direct Line Channel.

In step 2, choose "Chat" as the Conversation Type
ChatConversationType-1

IMPORTANT: LivePerson no longer recommends using the Chat conversation type for 3rd party bot configuration (even though it's still offered as a choice). The recommended configuration is to use Messaging. The Chat conversation type puts contrains on the integrated bot like the inability to correctly render adaptive dialogs like ConfirmInput() and hero cards

In step 4, enter the Direct Line Channel secret key so LivePerson can forward all conversations from its chat window through to your bot running in Azure and vice-versa.
LPConnectToAI_resized_SecretRemoved

For the connection from LivePerson to your bot in Azure to work correctly, you have to use v3 of Direct Line. Make sure you have the v3 checkbox checked in Configure Direct Line in Azure as well as "v3" in the URL for the Direct Line Endpoint in LivePerson.

On the fourth step of "Connect to A.I.", I clicked on the "Test Connection" button and walla, everything succeeded!
TestConnecgtionWorked_resized

With the bot connected to LivePerson via Direct Line, it was time to test the bot.

Testing the Bot with LivePerson Test Chat

With the bot connected, I found a "test chat" feature in LivePerson that would let me take the bot for a spin right away.
LivePersonTestChat_resized

I clicked on orange "Talk to" button in the lower right hand corner, a chat window opened and I waited... and waited... and waited... but nothing happened.
EmptyLPTestChatWindow_resized

The chat screen remained completely empty. Where was the welcome message and first question?

When I finally typed "Hello?" into the chat window, the bot interpreted it as the answer to the first question that was not being shown.

After checking Direct Line and LivePerson docs, I could not find an answer. Because the connection test had worked, I decided to cut my losses and try the chat window hosted in the website instead of the test chat in LivePerson.

NOTE: I didn't learn until much later that you should not test your bot in LivePerson's test chat. It would have been nice to know this, because I did waste a good deal of time trying to get something working that never was going to work.

Testing the Bot on a Web Site

A chat window had already been embedded in a website by another developer using a LivePerson tag. The "tag" is nothing more than a link to a LivePerson script that does a bunch of DOM manipulation to "embed" a chat window into an HTML page.

This embedded code was working fine as the chat window used to be routed directly to a live agent (human) in customer support so they could chat with the user. The goal was to use bot as a proxy between the user and a human and only escalate to a human when the bot could not help the user.

With sensitive data removed, this is what the LivePerson "tag" looks like:

<!-- BEGIN LivePerson Monitor. -->
<script type="text/javascript">window.lpTag=window.lpTag||{},'undefined'==typeof window.lpTag._tagCount?(window.lpTag={wl:lpTag.wl||null,scp:lpTag.scp||null,site:'12345678'||'',section:lpTag.section||'',tagletSection:lpTag.tagletSection||null,autoStart:lpTag.autoStart!==!1,ovr:lpTag.ovr||{},_v:'1.10.0',_tagCount:1,protocol:'https:',events:{bind:function(t,e,i){lpTag.defer(function(){lpTag.events.bind(t,e,i)},0)},trigger:function(t,e,i){lpTag.defer(function(){lpTag.events.trigger(t,e,i)},1)}},defer:function(t,e){0===e?(this._defB=this._defB||[],this._defB.push(t)):1===e?(this._defT=this._defT||[],this._defT.push(t)):(this._defL=this._defL||[],this._defL.push(t))},load:function(t,e,i){var n=this;setTimeout(function(){n._load(t,e,i)},0)},_load:function(t,e,i){var n=t;t||(n=this.protocol+'//'+(this.ovr&&this.ovr.domain?this.ovr.domain:'lptag.liveperson.net')+'/tag/tag.js?site='+this.site);var o=document.createElement('script');o.setAttribute('charset',e?e:'UTF-8'),i&&o.setAttribute('id',i),o.setAttribute('src',n),document.getElementsByTagName('head').item(0).appendChild(o)},init:function(){this._timing=this._timing||{},this._timing.start=(new Date).getTime();var t=this;window.attachEvent?window.attachEvent('onload',function(){t._domReady('domReady')}):(window.addEventListener('DOMContentLoaded',function(){t._domReady('contReady')},!1),window.addEventListener('load',function(){t._domReady('domReady')},!1)),'undefined'===typeof window._lptStop&&this.load()},start:function(){this.autoStart=!0},_domReady:function(t){this.isDom||(this.isDom=!0,this.events.trigger('LPT','DOM_READY',{t:t})),this._timing[t]=(new Date).getTime()},vars:lpTag.vars||[],dbs:lpTag.dbs||[],ctn:lpTag.ctn||[],sdes:lpTag.sdes||[],hooks:lpTag.hooks||[],identities:lpTag.identities||[],ev:lpTag.ev||[]},lpTag.init()):window.lpTag._tagCount+=1;</script>
<!-- END LivePerson Monitor. -->

So, I went to the website and clicked on the Live Chat button.
LiveChatButton

When the chat window opened, I still didn't see the welcome message, but the bot asked the first question
WebsiteHostedChatWindow

So, now the bot was... half working???

To recap, I have:

  • a fully functional bot using the emulator and azure web chat
  • a non-functional bot using LivePerson test chat
  • a half-working bot in the web chat window hosted in a webpage

Since the webpage was where the users would be interfacing with the bot, it was critical the bot send the welcome message and ask the first question in the chat window.

I had to find why the welcome message was not showing.

The Saga of Bot Welcome Messages

The internet had plenty of questions about why bot welcome messages were not being sent when hosted in Channels, but very little answers. In the answers that were there, the information was either:

  • dealing with client-side code that hosted the bot, which did not apply to me, because I didn't "own" the client code, LivePerson did (the LivePerson Tag).
  • using bot framework v3 (very different than v4)

So many of the answers either didn't apply or didn't make sense. However, a couple common themes emerged, mainly:

  • welcome messages were disappear when going to host bots in a variety of channels, not just Direct Line. Facebook, Teams, Twitter, you name it, people were getting their bots running in channels and welcome messages were getting dropped
  • a lot of code samples (including Microsoft docs) showed welcome message implementations using an event called "conversationUpdate".

Our RootDialog implementation looked like this:

public class DialogBot<T> : ActivityHandler where T : Dialog
{
...
    new OnConversationUpdateActivity()
    {
        Actions = new List<Dialog>
        {
            new Foreach()
            {
                ItemsProperty = "turn.activity.membersAdded",
                Actions = new List<Dialog>()
                {
                    new IfCondition()
                    {
                        Condition = "$foreach.value.name != turn.activity.recipient.name",
                        Actions = new List<Dialog>()
                        {
                            new SendActivity(message),
                            new BeginDialog(nameof(WelcomeDialog))
                        }
                    }
                }
            };
        }
    }
}

There is OnConversationUpateActivity() which I could only assume was the Adaptive Dialog version of "conversationUpdate".

Looking at Microsoft's docs, they use "conversationUpate" for sending a welcome message. Our code was doing the same thing.

So where was my welcome message?

Don't Use "conversationUpdate"

The smoking gun was this 2018 blog post by the Bot Framework Team.

Although many samples online use conversationUpdate to send a welcome message to the user when they first open a chat window, this blog was saying don't do that.

Here are some pertinent snippets from the blog:

One issue some customers have been having lately, is that when using trying the conversationUpdate, is that their bot behaves as expected during local testing when using the Emulator, however when the bot is running live, it does not work and user data cannot be submitted to the back channel

This looked promising. Reading on:

You should not use conversationUpdate. Why? If you’re using WebChat or direct line, the bot’s ConversationUpdate is sent when the conversation is created and the user sides’ ConversationUpdate is sent when they first send a message. When ConversationUpdate is initially sent, there isn’t enough information in the message to construct the dialog stack. The reason that this appears to work in the emulator, is that the emulator simulates a sort of pseudo DirectLine, but both conversationUpdates are resolved at the same time in the emulator, and this is not the case for how the actual service performs.

So using OnConversationUpdateActivity() in our RootDialog was not going to work for sending a welcome message to the user when using Direct Line.

The blog did offer a solution:

Instead of conversationUpdate, post an event activity in client side code. In your bot code, respond to the event with the desired welcome message.

Okay great! Wait, how do I "post an event activity in client side code"?

Reading on, the solution assumed that I had control over the client code and could post an event activity using javascript. Problem was, I didn't control the client code, LivePerson owned and controlled the client code. So there was no way for me to post an event activity when the bot loaded.

To make matters worse, the c# example code was based on the bot framework v3, which made mention of something called MessagesController. There is no MessageController in v4 of the bot framework, there is a BotController and the two implementations are very different.

I needed to figure out how to remove the dependency on conversationUpdate to send a welcome message in our code and how to send an "event" from client code to the bot in a client I didn't even own.

Luckily, buried in the LivePerson documents, it looks like someone had thought of that.

"The Welcome Event" in LivePerson

Under the Microsoft Direct Line docs in LivePerson, I found a section called Basic Content, and an area of the page called The Welcome Event and this snippet from the page:

A chat conversation is considered started when the chat is routed to an agent. Best practice is for the agent to provide the first response. In this scenario, there is no text from the consumer to parse, thus the default ‘WELCOME’ event is utilized as a start point for the bot to prompt the user to provide input and progress the conversation. Ensure you have an ‘entry point’ in your bot that responds to the default ‘WELCOME’ action send by a new chat customer.

(FYI, the above snippet talks about an "agent". In LivePerson, and "agent" is either a human OR a bot.)

This is exactly what I was looking for. An event that basically signaled to the bot "hey, someone is here and you should greet them." This was LivePerson's version of
"posting an event activity" from the bot framework team's blog.

LivePerson sends this json to the bot:

{
  // ...
  "type": "message",
  "text": "",
  "channelData": {
    "action": {
      "name": "WELCOME"
    }
  }
}

To wire this up, I needed to find a way to "Ensure you have an ‘entry point’ in your bot that responds to the default ‘WELCOME’ action send by a new chat customer".

Digging Out the WELCOME Event

I wasn't too sure how to handle the LivePerson Welcome Event. Was it something that would pass through the BotController straight into our bot code? Did I need a new Http endpoint?

I tried hooking into every adaptive dialog I could think if:

  • OnEventActivity()
  • OnActivity()
  • OnBeginDialog()

but most were not being run, and the ones that were had no idea of or were not invoked by the LivePerson Welcome event.

I also tried different overloads in our DialogBot class:

  • OnMessageActivityAsync()
  • OnMembersAddedAsync()
  • OnEventActivityAsync()

but none of these were being run.

In a final Hail Mary, I turned to the one overload in our DialogBot class that was being invoked, OnTurnAsync(). When I started unpacking ITurnContext, I found an Activity class, and unpacking Activity, I found ChannelData.

I inspected ChannelData as a JObject and looked for "action:name" with a value of "WELCOME" and it worked!

The final implementation of interrogating ChannelData for the LivePerson WELCOME event looks like this:

public class DialogBot<T> : ActivityHandler where T : Dialog
{
   public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        ...
        if (turnContext.Activity.ChannelData != null)
            {
                var channelData = JsonConvert.SerializeObject(turnContext.Activity.ChannelData);

                var jObject = JObject.Parse(channelData);
                if (jObject["action"] != null)
                {
                    if (jObject["action"]["name"] != null)
                    {
                        var channelDataActionName = (string)jObject["action"]["name"];
                        if (channelDataActionName == "WELCOME")
                        {
                            //this is the welcome message
                            await turnContext.SendActivityAsync("Thank you for calling the Afni customer support center. My name is Ida.");
                        }
                    }
                }
            }

            await DialogManager.OnTurnAsync(turnContext, cancellationToken: cancellationToken).ConfigureAwait(false);
        } 
}

I deployed my code and opened up the chat hosted in the web page:
WelcomeMessageAndFirstQuestionWorkingInWebPage

Success!

I received both the welcome message and first question from the bot. The nice part about this solution is I could keep the OnConversationUpdateActivity() in RootDialog to handle the welcome message for running in the emulator and Azure web chat and this code would take care of sending the welcome message when running via Direct Line.

In Closing

It was frustrating to work through this problem so late in the delivery cycle. I understand why Microsoft built both the Bot Emulator and Azure Web Chat to do "conversationUpdate" magic behind the scenes (quick adoption, immediate functionality, ease of development), but the fact that I had to dig around the internet for days, and finally rely on a outdated blog post from the Bot Framework Team so I could even begin to understand what was happening and why it was happening was really trying.

As the bot framework continues to evolve, the tools around it continue to evolve and 3rd party systems like LivePerson improve their ease of integrations with 3rd-party bots, this surely will become easier.

For now, I'd recommend getting your bot chat window through to your channel provider and optional 3rd party integration in place as soon as you get your first dialog written.

Although you should be relying on the emulator for local dev testing, you should be doing end-to-end testing all they way through to your channel as early in the process as possible. Automated tests could help here as well.

Happy Bot'ing!

References

Adaptive Dialogs

Direct Line

Where is My Welcome Message?

Send welcome message to users

LivePerson

BotBuilder-Samples