Category Archives: Bot Framework

Echo Bot deconstructed

This post has sat in draft for a year, hopefully it’s still relevent…

I’ve returned to doing some stuff using the Microsoft Bot Framework and updated everything to SDK 4 and some things have changed. I’m not going to waste time look at the changes so much as looking at the EchoBot template which comes with the latest SDK.

Getting started

If you haven’t already got it, you’ll need to install the Bot Builder V4 SDK Template for Visual Studio.

Note: If you instead decide to first go to Azure and create a Bot sample, the code for the EchoBot is (at the time of writing) very different to the template’s code.

Now if you create a new project in Visual Studio using this template you’ll end up a simple Bot that you can immediately run. Next, using the new Bot Framework Emulator (V4) select Open Bot from the emulator’s Welcome screen, then locate the .Bot file that came with the EchoBot and open this (or if you prefer put the Url http://localhost:3978/api/messages into the dialog). Then the Bot will be started and within a short amount of type you should see the following text

conversationUpdate event detected

Note: you will see the text twice, don’t worry about that, it’s normal at this point.

Now typing something into the emulator and press enter and you’ll be presented with something like the following

Turn 1: You sent ‘hi’

As you can see, I typed “Hi” and the Bot echoes it back.

At this point we’ve a working Bot.

Navigating the code

Program.cs

The Program.cs file contains the (as you’d expect) main start-up code for Kestrel to run etc. It configures logging and then bootstraps the Startup class. By default UseApplicationInsights() is commented out but obviously this is very useful once we deploy to Azure, for now leave it commented out. We can also enable logging to the Console etc., see Logging in ASP.NET Core for further information on options here.

Startup.cs

Startup is used to configure services etc. It’s the place we’ll add code for dependency injection etc. The first thing we’ll see within the constructor is the following

_isProduction = env.IsProduction();
var builder = new ConfigurationBuilder()
   .SetBasePath(env.ContentRootPath)
   .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
   .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
   .AddEnvironmentVariables();

   Configuration = builder.Build();

_isProduction is set to development when running on your local machine and to production by default, when run from Azure. The rest of the code creates a combined set of configuration, using the supplied appsettings.json file (part of the template’s project) along with any appsettings.json file for the specified environment. Meaning when running in development we might have differing configuration values from the production environment.

The next interesting method is ConfigureServices, where, as you’ll have guessed, we set-up any dependencies prior to their uses within the Bot code etc. The template code also expects the .bot file to exist. This file acts as further configuration and is not a requirement for a Bot to work, you could easily remove the .bot file and move configuration to the appsettings.json and (as stated previously) use the URL instead within the emulator (instead of the .bot file) and in fact. according to Manage bot resources the Bot file is being deprecated in favour of appsettings.json or .env file. However, for now it’s still part of the template, so I’m not going to cover the Bot file much further here, suffice to say it’s configuration is added to the dependency container in case you need access to it.

Jumping over where we located the endpoint from the .bot file, next up, a data store is created. This one is a MemoryStorage object and it’s primarily for local/debug builds in that it’ll lose its state upon a Bot restart. For production environments where we need to store such state we’ve use something like AzureBlobStorage , CosmoDB or any other cloud based persistence storage.

The ConversationState is then created and will be stored within the previously create IStorage, i.e. the MemoryStorage in this example. Conversation state is basically the state from the conversation taking place between user and Bot. This obviously becomes very useful in less trivial examples where dialogs are involved. In the echo Bot example it tracks the conversation counter state.

Finally within the ConfigureServices method we add our Bot to the container setting up any credentials as well as setting up a catch-all error handler.

The last method within the Startup.cs sets up the ability for the web app to display default and static files as well setting up endpoints etc. for the Bot framework.

EchoBotSampleAccessors.cs

The accessors file will be named after your project name and is basically a wrapper around the conversation state, see Create state property accessors. In the case of the Echo template it wraps the counter state and conversation state.

wwwroot/default.htm

By default, when you run the Bot an HTML page is displayed.

EchoBotBot.cs

Depending on your project name you’ll have a class which implements IBot. The key method of an IBot is the OnTurnAsync method, this is similar to an event look in Windows, it’s basically called upon each turn of a conversation.

Guiding the conversation with the Bot framework and FormFlow

FormFlow creates and manages a “guided conversation”. It can be used to gain input from a user in a menu driven kind of way.

Note: the example on the Basic features of FormFlow page covers the basic features really well. In my post I’ll just try to break down these steps and hopefully add some useful hints/tips.

Let’s get started

Let’s implement something from scratch to gain an idea of the process of creating a form flow. Like the example supplied by Microsoft, we’ll begin by creating a set of options as enumerations and get FormFlow to create the conversation for us. We’re going to create a really simple PC building service.

First off we’re going to create the PcBuilder class and hook it into the “conversation”. Here’s the builder

[Serializable]
public class PcBuilder
{
   public static IForm<PcBuilder> BuildForm()
   {
      return new FormBuilder<PcBuilder>()
         .Message("Welcome to PC Builder")
         .Build();
   }
}

Now in the MessageController.cs we want the ActivityType.Message to be handled like this

if (activity.Type == ActivityTypes.Message)
{
   await Conversation.SendAsync(
      activity, 
      () => Chain.From(
         () => FormDialog.FromForm(PcBuilder.BuildForm)
      )
   );
}

When a message comes in to initiate a conversation (i.e. just type some text into the Bot emulator and press enter to initiate a message) the FormDialog will take control of our conversation using the PcBuilder to create an menu driven entry form.

Note: Running this code “as is” will result in a very short conversation. No options will be displayed and nothing will be captured.

In it’s basic form we can use enumerations and fields to capture information, so for example our first question to a user wanting to build a PC is “what processor do you want?”. In it’s basic form we could simply declare an enum such as

public enum Processor
{
   IntelCoreI3,
   IntelCoreI7,
   ArmRyzen3
}

and we need to not only capture this but tell FormFlow to use it. All we need to do is add a field to the PcBuilder class such as

public class PcBuilder
{
   public Processor? Processor;

   // other code
}

Now if we initiate a conversation, FormFlow takes a real good stab at displaying the processor options in a human readable way. On my emulator this displays

Welcome to PC Builder

Please select a processor
Intel Core I 3
Intel Core I 7
Arm Ryzen 3

and now FormFlow waits for me to choose an option. Once chosen (I chose Intel Core I 7) it’ll display

Is this your selection?
   Processor: Intel Core I 7

to which the response expected is Y, Yes, N or No (cases insensitive). A “no” will result in the menu being displayed again and the user can being choosing options from scratch.

The first problem I can see is that, whilst it takes a good stab at converting the enum’s into human readable strings, we know that usually Intel Core I 7 would be written as Intel Core i7 so it’d be good if we had something like the component DescriptionAttribute to apply to the enum for FormFlow to read.

Luckily they’ve thought of this already with the DescribeAttribute which allows us to override the description text, however if you change the code to

public enum Processor
{
   [Describe("Intel Core i3")]
   IntelCoreI3,
   [Describe("Intel Core i7")]
   IntelCoreI7,
   [Describe("ARM Ryzen 3")]
   ArmRyzen3
}

things will not quite work as hoped. Selecting either of the Intel options (even via the buttons in the emulator) will result in a By “Intel Core” processor did you mean message with the two Intel options, selecting either will result in “Intel Core i7” is not a processor option. What we need to do is now add options to the enum to override the “term” used for the selection, so our code now looks like this

public enum Processor
{
   [Describe("Intel Core i3")]
   [Terms("Intel Core i3", "i3")]
   IntelCoreI3,
   [Describe("Intel Core i7")]
   [Terms("Intel Core i7", "i7")]
   IntelCoreI7,
   [Describe("ARM Ryzen 3")]
   ArmRyzen3
}

Let’s move things along…

Next we want the user to choose from some predefined memory options, so again we’ll add an enum for this (I’m not going to bother adding Describe and Terms to these, just to reduce the code)

public enum Memory
{
   TwoGb, FourGb, EightGb, 
   SixtenGb, ThiryTwoGb, SixtyFourGb
}

To register these within the FormFlow conversation we add this to the PcBuilder like we did with the Processor. The order is important, place the field before Processor and this will be the first question asked, place after it and obviously the Processor will be asked about first. So we now have

public class PcBuilder
{
   public Processor? Processor;
   public Memory? Memory;

   // other code
}

So far we’ve look into single options, but what about if we have a bunch of “add-ons” to our PC builder, you might want to add speakers, keyboard, a mouse etc. We can simply add a new enum and then a field of type List<AddOn>. For example

public enum AddOns
{
   Speakers = 1, Mouse, Keyboard, MouseMat
}

Note: the 0 value enum is reserved for unknown values, so either use the above, where you specify the Speaker (in this example) as starting the enum at the value 1 or put an Unkown (or whatever name you want) as the 0 value.

Note: Also don’t use IList for your field or you’ll get find no options are displayed. Ofcourse this makes sense as the field is not a concrete type that can be created by FormFlow.Note: By default, a list of options will not include duplicates. Hence an input of 1, 1, 4 will result in the value Speakers and MouseMat (no second set of Speakers).

Dynamic fields

In our example we’ve allowed a pretty standard set of inputs, but what if the user chose an Intel Core i3 and now needed to choose a motherboard. It would not make sense to offer up i7 compatible or ARM compatible motherboards. So let’s look at how we might solve this. We’ll create a enum for motherboards, like this

public enum Motherboard
{
   I3Compatible1,
   I3Compatible2,
   I7Compatible,
   ArmCompatible
}

It’s been a while since I built my last computer, so I’ve no idea what the current list of possible motherboards might be. But this set of options should be self-explanatory.

Currently (I haven’t found an alternative for this) the way to achieve this is to take over the creation and handling of fields, for example BuildForm would now have the following code

return new FormBuilder<PcBuilder>()
   .Message("Welcome to PC Builder")
   .Field(new FieldReflector<PcBuilder>(nameof(Processor)))
   .Field(new FieldReflector<PcBuilder>(nameof(Motherboard))
      .SetType(typeof(Motherboard))
      .SetDefine((state, f) =>
      {
         const string i3Compat1 = "i3 Compatible 1";
         const string i3Compat2 = "i3 Compatible 2";
         const string i7Compat = "i7 Compatible";
         const string armCompat = "ARM Compatible";

         f.RemoveValues();
         if (state.Processor == Dialogs.Processor.IntelCoreI3)
         {
            f.AddDescription(Dialogs.Motherboard.I3Compatible1, i3Compat1);
            f.AddTerms(Dialogs.Motherboard.I3Compatible1, i3Compat1);
            f.AddDescription(Dialogs.Motherboard.I3Compatible2, i3Compat2);
            f.AddTerms(Dialogs.Motherboard.I3Compatible2, i3Compat2);
         }
         else if (state.Processor == Dialogs.Processor.IntelCoreI7)
         {
            f.AddDescription(Dialogs.Motherboard.I7Compatible, i7Compat);
            f.AddTerms(Dialogs.Motherboard.I7Compatible, i7Compat);
         }
         else if (state.Processor == Dialogs.Processor.ArmRyzen3)
         {
            f.AddDescription(Dialogs.Motherboard.ArmCompatible, armCompat);
            f.AddTerms(Dialogs.Motherboard.ArmCompatible, armCompat);
         }
         else
         {
            f.AddDescription(Dialogs.Motherboard.I3Compatible1, i3Compat1);
            f.AddTerms(Dialogs.Motherboard.I3Compatible1, i3Compat1);

            f.AddDescription(Dialogs.Motherboard.I3Compatible2, i3Compat2);
            f.AddTerms(Dialogs.Motherboard.I3Compatible2, i3Compat2);

            f.AddDescription(Dialogs.Motherboard.I7Compatible, i7Compat);
            f.AddTerms(Dialogs.Motherboard.I7Compatible, i7Compat);

            f.AddDescription(Dialogs.Motherboard.ArmCompatible, armCompat);
            f.AddTerms(Dialogs.Motherboard.ArmCompatible, armCompat);
         }
         return Task.FromResult(true);
   }))
   .OnCompletion(OnCompletion)
   .AddRemainingFields()
   .Build();

Notice once we start to supply the fields we’re pretty much taking control of the supply of and order of fields which the data entry takes.

Ofcourse the code to supply the descriptions/terms could be a lot nicer.

Customization

We can customize some of the default behaviour (as seen with Terms and Describe). We can also change the prompt for a field, for example

[Prompt("What {&} would you like? {||}")]
public List<AddOns> AddOns;

Now when this part of the conversation is reached the prompt will say “What add ons would you like?” and then list them. The {&} is replaced by the field description and {||} by the options.

We can also mark a field as Optional, so for example we don’t want to force a user to select an AddOn

[Optional]
public List<AddOns> AddOns;

Now a fifth option “No Preference” is added to our list. In other words the list will be null.

Other FormFlow attributes include Numeric (allowing us to specify restrictions on the values range input). Pattern Allows us to define RegEx to validate a string field and Template allows us to supply the template to use to generate prompts and prompt values.

How do we use our data

So we’ve gathered our data, but at the end of the conversation we need to actually do something with it, like place an order.

To achieve this we amend our BuildForm method and add a method to handle the data upon completion, i.e.

public static IForm<PcBuilder> BuildForm()
{
   return new FormBuilder<PcBuilder>()
      .Message("Welcome to PC Builder")
      .OnCompletion(OnCompletion)
      .Build();
}

private static Task OnCompletion(IDialogContext context, PcBuilder state)
{
   // the state argument includes the selected options.
   return Task.CompletedTask;
}

The Bot equivalent of message boxes

In Windows (and most user interface frameworks) we have the concept of message boxes and dialog boxes.

We’ve already seen that we would create an implementation of an IDialog to interact with user input/commands, but the Bot framework also includes the equivalent of a Yes/No message box. For example

public async Task MessageReceivedAsync(
   IDialogContext context, 
   IAwaitable<IMessageActivity> argument)
{
   PromptDialog.Confirm(
      context,
      CalculateAsync,
      "Do you want to calculate risk?",
      "Unknown option");
}

public async Task CalculateAsync(
   IDialogContext context, 
   IAwaitable<bool> argument)
{
   var confirm = await argument;
   await context.PostAsync(confirm ? "Calculated" : "Not Calculated");
   context.Wait(MessageReceivedAsync);
}

What we have now is a prompt dialog which, when seen in the Bot framework channel emulator will ask the question “Do you want to calculate risk?”. This prompt will also display two buttons Yes & No. The user can press the button or type Y, N, Yes or No. Assuming a valid response is given by the user then the CalculateAsync method is called. If the response is not Y, Yes, N or No (obviously on an English language setup) then the prompt is displayed with the “Unknown option” reply that we specified and the dialog again waits for input (you can set the number of retries if you want the user to have three attempts, for example, to respond correctly to a prompt).

We can remove the Yes/No buttons by using promptStyle: PromptStyle.None, i.e.

PromptDialog.Confirm(
   context,
   CalculateAsync,
   "Do you want to calculate risk?",
   "Unknown option", 
   promptStyle: PromptStyle.None);

Using the Bot framework

Introduction

If you’re not sure what a Bot is, then check out What is Microsoft Bot Framework Overview.

A Bot doesn’t have to be intelligent and/or understand complex input. Although this sounds the most exciting aspects of using Bots we could also just use a Bot like an API into our services/applications.

What Bots do generally have is some simple user interface for a user to interact with the Bot. We’ve probably heard of or used a “chat bot” in the guise of a text input box which the user types into, but the bot could take input from buttons as well or other control/input mechanisms.

Prerequisites

Whilst we can write our Bot code from scratch we may as well use the Bot items templates which can be downloaded from http://aka.ms/bf-bc-vstemplate

Cortana skills templates can be downloaded from https://aka.ms/bf-cortanaskill-template but are not required for this post.

Once downloaded, place the zip files (for these templates) into %USERPROFILE%\Documents\Visual Studio 2017\Templates\ProjectTemplates\Visual C#.

Now when you start/restart Visual Studio 2017 these templates will be available in New | Project.

Next up you might like to download the Bot framework emulator from Microsoft/BotFramework-Emulator on github. I’m using the botframework-emulator-Setup-3.5.31 version for this and other posts.

Writing my first Bot

  • Create New | Project and select Bot application (mine’s named TestBot)
  • Build the project to pull in all the NuGet packages

At this point we have an ASP.NET application or more specifically we’ve created an ASP.NET Web API. The key areas of interest for us (at this point) is the Controllers/MessageController.cs file and the Dialogs/RootDialog.cs file.

At this point the template has created an Echo bot. We can run up this Bot application and you’ll be presented with a web page stating we need to register our Bot. Ofcourse we’d probably prefer to test things out first on our local machine without this extra requirement. We can run the Bot Framework Channel Emulator (as mentioned in the prerequisites section). So let’s see our Bot in action

  • Run you Bot application
  • Run the Bot emulator
  • In the emulator type the following http://localhost:3979/api/messages (obviously replace the localhost:3979 with whatever host/port you are running your Bot application from).
  • If all worked, simply type some message into the “Type your message…” textbox and the Bot your respond with “You sent which was

If you click on either the message or response, the emulator will display the JSON request and response data.

What’s the code doing?

We’ve seen that we can communicate with our Bot application but what’s really happening?

Obviously the Bot framework handles a lot of the communications for us. I’m not going to go too in depth with this side of things for the simple reason that, at the moment, I don’t know enough about how it works to confidently state how everything fits together. So hopefully I’ll get around to revisiting this question at another time.

I mentioned that MessagesController.cs was a key file. This is where we received messages and also where system messages can be handled. Think of it as the entry point to your application. In the template echo Bot we have this code

if (activity.Type == ActivityTypes.Message)
{
   await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}

When a Message type is received (types can be Message, Event, Ping and more, see ActivityTypes).

If you take a look at Dialogs/RootDialog.cs within your solution you’ll see that this implements the IDialog<> interface which requires the Task StartAsync(IDialogContext context) to be implemented. This method basically calls the method MessagedReceivedAsync method (shown below) which is where our “echo” comes from

private async Task MessageReceivedAsync(
   IDialogContext context, 
   IAwaitable<object> result)
{
   var activity = await result as Activity;

   // calculate something for us to return
   int length = (activity.Text ?? string.Empty).Length;

   // return our reply to the user
   await context.PostAsync(
      $"You sent {activity.Text} which was {length} characters");

   context.Wait(MessageReceivedAsync);
}

Making the Bot a little more useful

We’ve looked at generating a echo Bot which is a great starting point but let’s now start code something of our own.

You’ll notice from the code

int length = (activity.Text ?? string.Empty).Length;

This shows that the activity is supplied with the text sent to the Bot via the message input. Obviously in a more complicated interaction, one might send this text to a NPL service, such as Luis or slightly more simply, use RegEx or ofcrouse creating your own parser. But if our Bot was just designed to respond to simple commands, be they sent as messages via a user textual input or from a button press, then the MessageReceivedAsync code could be turned into the equivalent of a switch statement.

Let’s rewrite the MessageReceivedAsync method to act as a basic calculator where we have three commands, using ? to list the commands, and the add and subtract commands, both of which take 2 parameters (we could easily extend these to more parameters or switch to using operators but let’s start simple).

Simply remove the existing MessageReceivedAsync method and replace with the following

private async Task MessageReceivedAsync(
   IDialogContext context, IAwaitable<object> result)
{
   var activity = await result as Activity;

   string response = "I do not understand this command";
   var command = activity
      .Text
      .ToLower()
      .Split(new []{' '}, 
         StringSplitOptions.RemoveEmptyEntries);

   switch (command[0])
   {
      case "?":
         response = ListCommands();
         break;
      case "add":
         response = Add(command[1], command[2]);
         break;
      case "subtract":
         response = Subtract(command[1], command[2]);
         break;
   }

   await context.PostAsync(response);

   context.Wait(MessageReceivedAsync);
}

private string ListCommands()
{
   return "? => list commands\n\nadd a b => adds a and b\n\nsubtract a b => subtract a from b\n\n";
}

private string Add(string a, string b)
{
   double v1, v2;
   if (Double.TryParse(a, out v1) && Double.TryParse(b, out v2))
   {
      return (v1 + v2).ToString();
   }

   return "Non numeric detected";
}

private string Subtract(string a, string b)
{
   double v1, v2;
   if (Double.TryParse(a, out v1) && Double.TryParse(b, out v2))
   {
      return (v2 - v1).ToString();
   }

   return "Non numeric detected";
}

Obviously we’re light on error handling code etc. but you get the idea.

Note: In the ListCommands, to output a new line (i.e. display the list of commands in a multi-line response using the emulator) we need to use two newline characters. We could call context.PostAsync multiple times but this would appear as different message responses.