hckr.fyi // thoughts

Handling Multiple Dialogs in the Microsoft Bot Framework

by Michael Szul on

As some of you know, I've been putting together some videos on programming, and my first playlist collection deals with the Microsoft Bot Framework. I started with some example bots (including codepunk's own RSS bot), and then moved onto a step-by-step process, going from a basic hello world console bot to a hello world emulator bot to a more practice bot example that actually does something. The last video I put together dealt with multiple dialogs, and how to pass control around. I wanted to detail some of that here.

If you want to take a look at the example code that I've been adding to for the video, you can find it on GitHub.

Basic dialog flow is pretty easy to set up and control in the Bot Framework. You just used the dialog() method and pass it an id and an array of functions. The id allows you to identify the dialog when you want to start moving around through different dialogs, while the array of functions represents the flow of conversation within the dialog--often most useful when the bot is going back-and-forth with the user.

bot.dialog("/", [
        (sess, args, next) => {
            ...
        },
        (sess, result) => {
            ...
        }
    );
    

In the above code, we have a dialog identified by a root slash, which basically sits on the /api/messages web service for when an initial call is made to the bot. You can also forgo this by adding a default to the builder.UniversalBot() call in your bot setup code, but it's generally best practices to pull this out.

If you wanted to call a different dialog, you'd just use the beginDialog() method:

bot.dialog("/", [
        (sess, args, next) => {
            if(!sess.userData.name) {
                sess.beginDialog("/profile");
            }
            else {
                next()
            }
        },
        (sess, result) => {
            ...
        }
    );
    

This is a very basic example that you'll find in a lot of online tutorials and documentation, and that's because it's pretty easy to follow the need for a separate dialog that handles profile information.

In this code, if the userData object in session doesn't have a name property, we pass control to the dialog called "/profile", otherwise, we move to the next function in the waterfall.

A very basic profile dialog would look like:

bot.dialog("/profile", [
        (sess, args, next) => {
            builder.Prompts.text(sess, "Hello, user! What is your name");
        },
        (sess, result) => {
            sess.userData.name = result.response;
            sess.endDialog();
        }
    ]);
    

You'll see this a lot in online examples as well. Again, it's easy to follow the need to abstract profile handling from the basic bot handling.

In this example, we prompt the user for their name, and when they respond, control is in the second function in the waterfall, which sets the userData.name value in the session to the result.response. The dialog is then ended with endDialog(), which passes control back to the calling dialog. The "/" dialog in this case.

Now there are some instances where there isn't a need to start or end dialogs to maintain a control flow, but instead you want to completely swap out the current dialog with a different one. You use replaceDialog() for this. With beginning and ending dialogs, the current dialog is suspended before control is passed, but with replacing dialogs, the current dialog is ended rather than suspended.

The following example shows a use for replacing a dialog:

(sess, result) => {
        if(result.response.entity === "Yes") {
            let route = sess.userData.lastRoute;
            sess.send(`The current status of the train from ${route.from} to ${route.to} is ${route.status}`);
        }
        else {
            sess.replaceDialog("/");
        }
    }
    

In this example, this function (which is one of several in a waterfall) follows a choice prompt. If the user selected "Yes" then a message is sent to them about a train route; otherwise, the dialog is replaced with the "/" dialog, which basically restarts the current dialog. This is different than calling reset(), which you can call to reset the conversation at a desired dialog, but also clear out the session information. Another control flow option (not with this example, but in general) would be to use cancelDialog(), which allows you to specify which dialog you want to cancel (including a parent dialog).

In addition to endDialog() which allows you to end the dialog and optionally send a message to the user, there is also an endDialogWithResults(), which allows you to end the dialog, while passing data back to the parent dialog. Also, both beginDialog() and replaceDialog() let you pass arguments to the new dialog that you are send the user too.

As you can tell, there are several options for passing around dialog control, so keeping a close eye on how you want your bot to flow is important. This is why bots are being referred to as the conversation UI--both interface and experience are highly important.