Dialogs

Overview

Bot Builder uses dialogs to manage a bots conversations with a user. To understand dialogs its easiest to think of them as the equivalent of routes for a website. All bots will have at least one root ‘/’ dialog just like all websites typically have at least one root ‘/’ route. When the framework receives a message from the user it will be routed to this root ‘/’ dialog for processing. For many bots this single root ‘/’ dialog is all that’s needed but just like websites often have multiple routes, bots will often have multiple dialogs.

var builder = require('botbuilder');

var connector = new builder.ConsoleConnector().listen();
var bot = new builder.UniversalBot(connector);
bot.dialog('/', [
    function (session, args, next) {
        if (!session.userData.name) {
            session.beginDialog('/profile');
        } else {
            next();
        }
    },
    function (session, results) {
        session.send('Hello %s!', session.userData.name);
    }
]);

bot.dialog('/profile', [
    function (session) {
        builder.Prompts.text(session, 'Hi! What is your name?');
    },
    function (session, results) {
        session.userData.name = results.response;
        session.endDialog();
    }
]);

The example above shows a bot with 2 dialogs. The first message from a user will be routed to the Dialog Handler for the root ‘/’ dialog. This function gets passed a session object which can be used to inspect the users message, send a reply to the user, save state on behalf of the user, or redirect to another dialog.

When a user starts a conversation with our bot we’ll first look to see if we know the users name by checking a property off the session.userData object. This data will be persisted across all of the users interactions with the bot and can be used to store things like profile information. If we don’t know the users name we’re going to redirect them to the ‘/profile’ dialog to ask them their name using a call to session.beginDialog().

The ‘/profile’ dialog is implemented as a waterfall and when beginDialog() is called the first step of the waterfall will be immediately executed. This step simply calls Prompts.text() to ask the user their name. This built-in prompt is just another dialog that gets redirected to. The framework maintains a stack of dialogs for each conversation so if we were to inspect our bots dialog stack at this point it would look something like [‘/’, ‘/profile’, ‘BotBuilder:Prompts’]. The conversations dialog stack helps the framework know where to route the users reply to.

When the user replies with their name, the prompt will call session.endDialogWithResult() with the users response. This response will be passed as an argument to the second step of the ‘/profile’ dialogs waterfall. In this step we’ll save the users name to session.userData.name property and return control back to the root ‘/’ dialog through a call to endDialog(). At that point the next step of the root ‘/’ dialogs waterfall will be executed ad a custom greeting will be sent to the user.

It’s worth noting that the built-in prompts will let the user cancel an action by saying something like ‘nevermind’ or ‘cancel’. It’s up to the dialog that called the prompt to determine what cancel means so to detect that a prompt was canceled you can either check the ResumeReason code returned in result.resumed or simply check that result.response isn’t null. There are actually a number of reasons that can cause the prompt to return without a response so checking for a null response tends to be the best approach. In our example bot, should the user say ‘nevermind’ when prompted for their name, the bot would simply ask them for their name again.

Dialog Handlers

A bots dialogs can be expressed using a variety of forms.

Waterfall

Waterfalls will likely be the most common form of dialog you use so understanding how they work is a fundamental skill in bot development. Waterfalls let you collect input from a user using a sequence of steps. A bot is always in a state of providing a user with information or asking a question and then waiting for input. In the Node version of Bot Builder its waterfalls that drive this back-n-forth flow.

Paired with the built-in Prompts you can easily prompt the user with a series of questions:

bot.dialog('/', [
    function (session) {
        builder.Prompts.text(session, 'Hi! What is your name?');
    },
    function (session, results) {
        session.send('Hello %s!', results.response);
    }
]);

Bots based on Bot Builder implement something we call “Guided Dialog” meaning that the bot is generally driving (or guiding) the conversation with the user. With waterfalls you drive the conversation by taking an action that moves the waterfall from one step to the next. Calling a built-in prompt like Prompts.text() moves the conversation along because the users response to the prompt is passed to the input of the next waterfall step. You can also call [session.beginDialog())] to start one of your own dialogs to move the conversation to the next step:

bot.dialog('/', [
    function (session) {
        session.beginDialog('/askName');
    },
    function (session, results) {
        session.send('Hello %s!', results.response);
    }
]);
bot.dialog('/askName', [
    function (session) {
        builder.Prompts.text(session, 'Hi! What is your name?');
    },
    function (session, results) {
        session.endDialogWithResult(results);
    }
]);

This achieves the same basic behavior as before but calls a child dialog to prompt for the users name. That’s somewhat pointless in this example but could be a useful way of partitioning the conversation if you had multiple profile fields you wanted to populate.

This example can actually be simplified some. All waterfalls contain a phantom last step which automatically returns the result from the last step so we could actually simplify this to:

bot.dialog('/', [
    function (session) {
        session.beginDialog('/askName');
    },
    function (session, results) {
        session.send('Hello %s!', results.response);
    }
]);
bot.dialog('/askName', [
    function (session) {
        builder.Prompts.text(session, 'Hi! What is your name?');
    }
]);

The first step of a waterfall can receive arguments passed to the dialog and every step receives a next() function that can be used to advance the waterfall forward manually. In the example below we’ve paired these two features together to create an ‘/ensureProfile’ dialog that will verify that a users profile is filled in and prompt the user for any missing fields. This pattern would let us add fields to the profile later that would be automatically filled in as users message the bot:

bot.dialog('/', [
    function (session) {
        session.beginDialog('/ensureProfile', session.userData.profile);
    },
    function (session, results) {
        session.userData.profile = results.response;
        session.send('Hello %(name)s! I love %(company)s!', session.userData.profile);
    }
]);
bot.dialog('/ensureProfile', [
    function (session, args, next) {
        session.dialogData.profile = args || {};
        if (!session.dialogData.profile.name) {
            builder.Prompts.text(session, "What's your name?");
        } else {
            next();
        }
    },
    function (session, results, next) {
        if (results.response) {
            session.dialogData.profile.name = results.response;
        }
        if (!session.dialogData.profile.company) {
            builder.Prompts.text(session, "What company do you work for?");
        } else {
            next();
        }
    },
    function (session, results) {
        if (results.response) {
            session.dialogData.profile.company = results.response;
        }
        session.endDialogWithResult({ response: session.dialogData.profile });
    }
]);

In the ‘/ensureProfile’ dialog we’re using session.dialogData to temporarily hold the users profile. We do this because when our bot is distributed across multiple compute nodes, every step of the waterfall could be processed by a different compute node. The dialogData field ensures that the dialogs state is properly maintained between each turn of the conversation. You can store anything you want into this field but should limit yourself to JavaScript primitives that can be properly serialized.

It’s worth noting that the next() function can be passed an [IDialogResult](idialogresult) so it can mimic any results returned from a built-in prompt or other dialog which sometimes simplifies your bots control logic.

Closure

You can also pass a single function for your dialog handler which simply results in creating a 1 step waterfall:

bot.dialog('/', function (session) {
    session.send("Hello World");
});

Dialog Object

For more specialized dialogs you can add an instance of a class that derives from the Dialog base class. This gives maximum flexibility for how your bot behaves as the built-in prompts and even waterfalls are implemented internally as dialogs.

bot.dialog('/', new builder.IntentDialog()
    .matches(/^hello/i, function (session) {
        session.send("Hi there!");
    })
    .onDefault(function (session) {
        session.send("I didn't understand. Say hello to me!");
    }));

SimpleDialog

Implementing a new Dialog from scratch can be tricky as there are a lot of things to consider. To try and cover the bulk of the scenarios not covered by waterfalls, we include a SimpleDialog class. The closure passed to this class works very similar to a waterfall step with the exception that the results from calling a built-in prompt or other dialog will be passed back to one closure. Unlike a waterfall there’s no phantom step that the conversation is advanced to. This is powerful but also dangerous as your bot can easily get stuck in a loop so care should be used:

bot.dialog('/', new builder.SimpleDialog(function (session, results) {
    if (results && results.response) {
        session.send(results.response.toString('base64'));
    }
    builder.Prompts.text(session, "What would you like to base64 encode?");
}));

The above example is a base64 bot that all it does is convert a users input to base64. It sits in a tight loop prompting the user for string which it then encodes.