Creating a Chat Bot – The Technology

Here @chatsecrets we just published a technology training course called ‘Marketing & Selling Using Chat‘. In the first post we talked about the course itself with the course outline of modules and lectures. This second post is a deeper dive into the technology and architecture of the bot. By the end you will know how everything fits together. You might even build such a course bot for your own training needs. The final post in the series will discuss some interesting ideas for the future of coaching delivered this way

 

Once we decided we needed chat bot technology for our training course the first issue we faced was how to embed it. I mean actually embed it in the course. Not as a clickable chat window in an iframe. We use Teachable so could we do it on the free plan :).

Lets go?

Direct Line

The bot is built using the Bot Framework which allows a direct line of communication between your bot and your own client application by using the Direct Line API. This is provided so you can connect the bot to a custom chat interface or a custom web or mobile app. The customization was done in css and imported as a code snippet into the head of the page for an enrolled student

<script src="https://unpkg.com/botframework-webchat/botchat.js"></script>
<link href="https://chatsecrets.com/downloads/css/chatsecrets-botchat.css" rel="stylesheet" />

and then wherever we want to show the bot to explain some concept we can run Web Chat inline like this

<div class="botWrapper" id="BotChatGoesHere">
 <script> BotChat.App({
 directLine: {
 secret: "direct_line_secret "
 },
 user: { id: 'student' },
 bot: { id: 'chatsecrets' },
 }, document.getElementById("BotChatGoesHere"));
 </script>
</div>

Architecture

The lectures themselves are implemented as dialog libraries. These are reusable parts which can be chained together to enable the development of complex bots.

bot.library(require('./dialogs/lecture1').createLibrary());
bot.library(require('./dialogs/lecture2').createLibrary());
bot.library(require('./dialogs/lecture3').createLibrary());
bot.library(require('./dialogs/lecture4').createLibrary());
// Validators
bot.library(require('./validators').createLibrary());

Each lecture has topics (such as Understand Context, Use Anticipation and Use Animation) which are in an image carousel with switch/case logic for the user selection.

Notice the use of lib.dialog and not bot.dialog

var lib = new builder.Library('lecture1');
lib.library(locationDialog.createLibrary(process.env.BING_MAPS_KEY));

lib.dialog('/', [
        function (session) {
            session.send('To return to this menu type  \'Lecture 1\'')
            var msg = new builder.Message()
                .attachmentLayout(builder.AttachmentLayout.carousel)
                .addAttachment({ 
                    thumbnailUrl: 'https://chatsecrets.com/wp-content/uploads/2017/10/context.png',
                    actions: [ { title: 'Understand Context', message: 'Understand Context' }]
                 })
                .addAttachment({ 
                    thumbnailUrl: 'https://chatsecrets.com/wp-content/uploads/2017/10/anticipation.png',
                    actions: [ { title: 'Use Anticipation', message: 'Use Anticipation' }]
                 })
                .addAttachment({ 
                    thumbnailUrl: 'https://chatsecrets.com/wp-content/uploads/2017/10/animation.png',
                    actions: [ { title: 'Use Animation', message: 'Use Animation' }]
                 });

            builder.Prompts.choice(session, msg, "Understand Context|Use Anticipation|Use Animation");
        },
     function (session, results) {
        // call the correct dialog    
        switch (results.response.index) {
            case 0:
                session.beginDialog("UnderstandContext");
            break;
            case 1:
                session.beginDialog("Anticipation");
            break;
            case 2:
                session.beginDialog("Animate");
            break;
            default:
                session.endDialog();
                break;
        } 
    },
      function (session) {
        // Reload menu
        session.replaceDialog('/');
    }
]).reloadAction('showMenu', null, { matches: /^(Lecture 1)/i });

Lecture 1

 

For ‘Understanding Context’ we show a different message depending on the channel. We take this directly from session.message.address.channelId

if(session.message.address.channelId === 'directline'){
        session.send("Thanks for choosing our Chat Secrets training course! Chat Secrets loves you :)");
        setTimeout(function () {
         session.beginDialog('chat:/');
       }, 10000); }

‘Anticipation’ is a competition to answer the question ‘In one sentence tell us why we should build your chat bot’ . The prize is for us to build a chat bot for free for the winner. Given this we  needed some way to permanently store answers and contact details. We created a separate library of marketing tools which exports a number of functions to solve this.  It allows us to insert details into a database, integrate contact info with MailChimp as well as log all interactions. I answered a question on Quora with more details of how we set up the database connection in node.js

Another interesting feature in this Lecture is the use of session.sendTyping() which sounds simple and unsurprising. It is actually pretty important. It makes the user understand that we are doing something as opposed to “I don’t know if the bot actually works or understands.” Future Chat Secrets training courses will likely make much more use of this.

Lecture 2

‘Warm Up’ uses an adaptive card technology for the Quiz. The lack of logic control of questions makes me miss XForms technology so maybe someone should port Enketo to a bot. The way to submit the form also raised an eyebrow. If two session values exist we process the submit.

 if (session.message && session.message.value) {
        // A Card's Submit Action obj was received
        processSubmitAction(session, session.message.value);
        return;
    }

and then store the answers using our marketing tools.

function processSubmitAction(session, value) {

    var defaultErrorMessage = 'Please complete all the questions in the quiz!';

    if (!value.help || !value.impact || !value.invest ) {
        session.send(defaultErrorMessage);
    } else {
        var marketingDetails = {
            help: value.help,
            impact: value.impact,
            invest: value.invest,
            feedback: value.feedback

        };
       marketingTools.saveQuiz(marketingDetails);
       sendPDF(session)
    }
} 

Finally we send a free pdf with a nice chat hack as a thank you for taking part.  ‘Qualify’ is interesting only because it uses the Bing location control to collect and validate the user’s location.Most noteworthy with ‘Sell’ is that it is a standalone e-commerce solution. It is based on the Bot Framework example.

Lecture 3

‘Turn on the Engines’ is a quick way to see the potential for Messenger Marketing. We ask some questions and then calculate how much more revenue you can generate with a bot than with email. The questions are:

  • How many active subscribers would you say you have on your list?
  • What is the average cost of your product or service?
  • What is your average conversion rate on your sales (10%, 15%, 20%, more)?

Lecture 4

We published a high level look at the objective for this lecture over on Chatbots Magazine. The main technology interest is the use of a custom Luis prompt which we add to the root dialog. We use the assigned score to either trigger the feedback or call the correct intent

lib.dialog('/', [
     function (session) {
          builder.Prompts.myLuisPrompt(session, "I am a fitness marketing AI so lets talk about things related to fitness. When you are bored \'Menu\' takes you back to the main menu");
     },
     function (session, results) {
         session.userData.luisMsg = session.message.text;
          if (results.response.score < 0.6){
             
             var myArray = [];
         

             for(var i = 0; i < results.response.intents.length; i++) {
                 var intent = results.response.intents[i].intent;
                 if (intent == 'Fitness.LogActivity'){
                        var name = 'I want to log an Activity';
                   }else if (intent == 'Fitness.LogWeight'){
                        var name = 'I want to lose weight';
                   }else if (intent == 'Fitness.AddNote'){
                        var name = 'I want to add a note';        
                   }else if (intent == 'Fitness.Marketing'){
                        var name = 'I want to see some offers';   
                   }else if (intent == 'Fitness.GetRemaining'){
                        var name = 'I want to see the remaining time';   
                   }else if (intent == 'None'){
                        var name = 'None of these';       
                   }
               
          
                myArray.push(name);
                var luisData = myArray;
        


                builder.Prompts.choice(session, "Not sure I understand. Can you help me out? Did you mean one of these?", luisData);        
                                            }else{
 
               case 'Fitness.LogActivity':
                  session.beginDialog("LogActivity");
                    break;
                case 'Fitness.LogWeight':
                  session.beginDialog("LogWeight");
                    break;
                case 'Fitness.Marketing':
                  session.beginDialog("FitnessMarketing");
                    break;
                case 'Fitness.AddNote':
                  session.beginDialog("FitnessAddNote");
                    break;
                case 'Fitness.GetRemaining':
                  session.beginDialog("FitnessGetRemaining");
                    break;
                case 'None':
                  session.beginDialog("None");
                    break;
                                           }
                                                }
       
                        },

If the marketing intent is fired then we use cashbot.ai to look for related products on Amazon. The only thing we needed to do with the results it delivers is loop them into a carousel. We did this by using an array and pushing each card on to it.

lib.dialog('FitnessMarketing', [

    function (session, args) {
     session.send('You want to see some offers! Here you go');
   
     cashbot.sendUserInput(session.message.user.id, {
           recommend: true,
           n: 3,
           type: 'text',
            value: {
               intent: 'fitness',
               value: session.userData.luisMsg
                   }
             }).then(function(r) {

                    var cards = [];

                    var msg = new builder.Message(session);
                    msg.attachmentLayout(builder.AttachmentLayout.carousel)
                    
                    r.forEach(function(data) {
                    var card = new builder.HeroCard(session)
                    .title(data.title)
                    .text(data.description)
                    .images([builder.CardImage.create(session, data.image)])
                    .buttons([
                        builder.CardAction.openUrl(session, data.url, "Buy")
                    ])

     cards.push(card); 
},this)

                    msg.attachments(cards);          
    
            session.send(msg).endDialog();
             }).catch(function(e) {
             console.error(e);
                                     });


    }
 
    ]).triggerAction({
    matches: 'FitnessMarketing',
    onInterrupted: function (session) {
        session.send('This is our best offer');
    }
});

It is very powerful technology being able to visually train product selection using cashbot. In the next post we will talk about some ideas we have for deeper integration with it. Also up is automated grading of students assignments using a git url, certificates of completion on the blockchain and lots more. Until then, enjoy!

Leave a Reply