Adding prompts to a Microsoft bot

Introduction

In recent posts I have covered the basics of creating a bot. One of the last features that was added was the ability to remove a team from a competition. In a normal desktop or web application, basic user experience rules would have us ensure that the user was prompted to confirm such a decision. Our bot didn’t have this feature, which would increase the possibility of users mistakenly deleting data. In todays post we are going to look at adding prompts to our bot.

Basic yes or no questions

The first prompt we will look at is a basic confirmation. This prompt will allow the user to respond with Yes or No. As some of the channels we can connect our bot into allow for buttons, we will automatically get these displayed. We will see these in the image from our emulator later in the post, but we need to remember that depending on channel used (i.e. SMS) this may not be available to the user.

To implement our prompt we call PromptDialog.Confirm. We need to provide the context, the name of a suitable Action and also some options for how we what we will display in the prompt. In this example, for the options we just provide a caption for the dialog. In the example later in the post we will create a more complete set of PromptOptions.

private string TeamName;

[LuisIntent("RemoveTeam")]
public async Task RemoveTeam(IDialogContext context, LuisResult result)
{
    EntityRecommendation rec;
    if (result.TryFindEntity("TeamName", out rec))
    {
        TeamName = rec.Entity;
        if (champs.DoesTeamExist(TeamName))
        {
            PromptDialog.Confirm(context, RemoveTeamPromptAsync, 
                    $"Are you sure you want to delete the team { TeamName }?");
        }
        else
        {
            await context.PostAsync($"The team {TeamName} was not found.");
            context.Wait(MessageReceived);
        }
    }
    else
    {
        await context.PostAsync("An error occured. We were unable to remove the team.");
        context.Wait(MessageReceived);
    }

}

In the RemoveTeamPromptAsync method we created we need to add the code for handling the users response. For a confirm dialog the result is always going to be a bool, so we can await this result and use that to determine the action to carry out. If the user selects or states No then no action is taken and the user is informed of this. At the end of the method we have the IDialogContext await the next message.

private async Task RemoveTeamPromptAsync(IDialogContext context, IAwaitable<bool> result)
{
    if (await result)
    {
        champs.RemoveTeam(TeamName);
        await context.PostAsync($"{TeamName} has been removed from the championships.");
    }
    else
    {
        await context.PostAsync($"OK, we have not deleted them.");
    }
    context.Wait(MessageReceived);
}

We can test our code now in the Emulator and see how the prompt is displayed to the users.

Simple Prompt

Custom prompts

Although Yes/No prompts are great, sometimes you want to provide a more specific set of options. Imaging creating a booking application and the user asks to book an appointment. You would not respond back with a yes or no question of if they want to book unless you had more details. So rather than requiring the user to specify a time initially, you could offer them a list of available times via a prompt.

In the example below I have added a new feature to my Championships class that returns the top three teams. This is a simple example, but provides enough to show the necessary steps. The LUIS application has also been updated to have a new Intent called RemoveGoodTeam. This allows the user to inform the bot to “Remove a good team” and the bot will return a list of the top three teams and ask the user which it should remove.

Outside of the bot code, I have called the GetTopThreeTeams from my sample app and assigned to result to a List this will be used in the prompt options. We now create PromptOptions object which we will use with our PromptDialog. Within these options we can provide a number of settings. In this example not only do I provide the prompt for the user as in we did in our earlier example, but I also provide messages for incorrect entries, too many attempts, the list of options to display (taken from our GetTopThreeTeamsMethod) and finally how many attempts the user gets.

The final line of the code calls the PromptDialog but this time instead of Confirm we call Choice.

[LuisIntent("RemoveGoodTeam")]
public async Task RemoveGoodTeam(IDialogContext context, LuisResult result)
{
    List goodTeams = champs.GetTopThreeTeams();
    PromptOptions options = new PromptOptions("Select which of the top teams to remove",
            "Sorry please try again", "I give up on you", goodTeams, 2);
    PromptDialog.Choice(context, RemoveGoodTeamAsync, options);
}

The code for our action is pretty much the same as our first example, with the notable exception that we are awaiting a string and not a bool. The string value will be one of the options we provided earlier. The bot will only return one of our specified options, so we don’t have to worry as much about invalid entries as we would have done if the user had sent the request themselves.

private async Task RemoveGoodTeamAsync(IDialogContext context, IAwaitable<string> result)
{
    string res = await result;
    if (champs.DoesTeamExist(res))
    {
        champs.RemoveTeam(res);
        await context.PostAsync($"{res} has been removed from the championships.");
    }
    else
    {
        await context.PostAsync($"The team {res} was not found.");
    }
    context.Wait(MessageReceived);
}

When we test this in the emulator we can see the options now displayed instead of the Yes/No prompts. If we typed in a team name that was not on the options list, the bot would return the message we entered for incorrect entries. After the specified number of attempts had failed, the user would have to re ask the bot to remove a good team.

CustomPrompt

What next?

So far in the last three posts we have created a basic bot, added natural language support with LUIS and now provided prompts and questions. Our next stage is to deploy our bot outside of our local environment and connect up to some of the channels available.

Using Natural Language in a bot with LUIS

Introduction

Bots may be great, but one thing that can take up a lot of a developers time, is coding for as many variations of input as they can think of. Even with all this work, some customers may find that the bot does not recognise what they are entering. A great way to handle this is to use a natural language service, so you can leave the variations of language to that. As part of the Microsofts Cognitive Services, they have developed a system called Language Understanding Intelligent Service. Or just simply LUIS. In this blog we are going to create a simple LUIS application and integrate it with a bot. For those people who read my last post, we are simply using the same bot but creating a new LuisDialog to replace our old Dialog.

Create a LUIS application

Much of our work with LUIS will be done within the browser. From the LUIS site we are able to create and configure our applications. We will also use this site to train our applications, which is something we will cover later on in this post. For now we are going to create our application.

If you navigate to the LUIS site luis.ai you will be taken to your “My Applications” page. If you are logging in for the first time, you will need to register with a Live account.

Within My Applications we have the option to create a new App. When you select this, a dialog will appear and you will need to enter an application name. I will be sticking with the English application culture for this one, mainly as that is the only language I can do more than just order food and beer in. When you press Add App you will need to wait for a minute or two whilst the application is built.

Add New Application

Once the application has been created, the browser will navigate to that applications page.

Application Area

Responding to the user – Intents

In a LUIS application each different type of command you want to raise based on the users input needs to be created as an Intent. When you create an Intent you need to provide a name for it and an example utterance.

The name should be named with the same care as a class/method name in your code. This will be used in the bot code to identify the intent, it is also how you will train and configure your application within LUIS.

An utterance is an example of what a user might say when communicating with the service. In the example below we have an intent that provides the number of teams called TeamCount. The example utterance we provide is “How many teams are there”. This is a natural way to ask for this information, it is not the only way however, so we will want to add some more later on.

Create New Intent

Once we have created an Intent, we can add more utterances from the application page. To do this, from the “New utterances” tab enter a new expression in the text box and press the arrow key. You will now have the option to select which intent this utterance should be assigned too. Note that there is always an intent called “None”, this indicates that the application does not understand what has been asked. This will be used in our code for a simple message back to the user saying that we don’t understand their question.

Adding utterances

Training and publishing

Before we can use our app we need to publish it. But before we can do that we need to press the Train button. This takes all the information on utterances that we have entered and does its magic in the background.

Training

Once trained, we can then select Publish and we a dialog similar to the one below will be shown. Its worth noting that you can test your application by just entering text into the Query field and pressing enter. This will open a new page in your browser with the JSON return.

Capture6

Getting the keys

So we are have our application built, trained and published, what we need to do now is link it to our bot code. This is actually relatively simple, but what we need from the LUIS site is the App Id and our own subscription key. These will be copied into the LUISDialog we create. You can get your App Id from the App Settings page.

App Key

The subscription key can be found on your main account settings page. When you sign up for LUIS you get a subscription key that allows you to use apps for development purposes. If you need more calls than this will allow, you can purchase a key from Azure and add this to your application.

Sub Key

Updating your bot with a LuisDialog

OK, for a blog that has the C# tag it has taken us some time to get to code. But here we are. What we are going to do is create a new LuisDialog inside of a bot project. I am not going to go through the bot application as this was covered in my last post. For now, we just create a new class within our project. I have called mine ChampionshipDialogLUIS. This class extends the LuisDialog. We then add the attribute for LuisModel which has a parameter for App Id and then one for Subscription Key, these are the details we took from our application on the LUIS site.

[LuisModel("e0357190-4455-4506-8764-055b7e04a674",
    "a91e3e2044be4be99c291c54a153f3a6")]
[Serializable]
public class ChampionshipDialogLUIS : LuisDialog<object>
{

}

In a change from our standard Dialog, in the LuisDialog we create a method for each of our intents and use the attribute LuisIntent with the name of the intent as shown below. For the builtin None intent we just use the attribute LuisIntent(“”).

The LUISDialog does have some functionality already that saves us a bit of work, so now when we finish in a method we just add context.Wait(MessageReceived) and the LuisDialog takes care of listening for the next input. The remainder of the code for our TeamCount intent is the same as it would be for a standard dialog.

[LuisModel("e0357190-4455-4506-8764-055b7e04a674",
    "a91e3e2044be4be99c291c54a153f3a6")]
[Serializable]
public class ChampionshipDialogLUIS : LuisDialog<object>
{
    [LuisIntent("TeamCount")]
    public async Task GetTeamCount(IDialogContext context, LuisResult result)
    {
        Championships champs = new Championships();
        await context.PostAsync($"There are {champs.GetTeamCount()} teams in the championships.");
        context.Wait(MessageReceived);
    }
        
    [LuisIntent("")]
    public async Task None(IDialogContext context, LuisResult result)
    {
        await context.PostAsync("No clue what you are talking about");
        context.Wait(MessageReceived);
    }
}

What I have now gone and done is add a few more intents in exactly the same way as our TeamCount one. This just shows how easy it is to have the LuisDialog handle the various message we get from the bot.

[LuisIntent("TopTeam")]
public async Task TopTeam(IDialogContext context, LuisResult result)
{
    await context.PostAsync($"The highest rated team is {champs.GetHighestRatedTeam()}.");
    context.Wait(MessageReceived);
}

[LuisIntent("BottomTeam")]
public async Task BottomTeam(IDialogContext context, LuisResult result)
{
    await context.PostAsync($"The lowest rated team is {champs.GetLowestRatedTeam()}");
    context.Wait(MessageReceived);
}

Getting user entered data – Entities

The problem with what we have done so far, is that we have only handled simple requests from the users. In reality a user is going to want to do more than just ask for some predefined information, they are going to want to ask a more detailed question. For this we will bring in Entities.

Entities allow us to take part of the expression entered by the user and use this as an argument. For example, my test app provides information about a football championship, it is therefore logical that the user will want to ask about a specific team. So that is what we are going to add.

Back in the LUIS site, we add a new entity. For this we enter a name using the same care as with our intents. In my case I have called mine TeamName.

Capture7

What I have also done is create an intent that allows us to remove a specific team, called RemoveTeam. When you are entering utterances, you can select a word and declare it as an entity. This tells the application that it is for the intent RemoveTeam but that the text in this area would form an entity. This should become clear when we look at the code.

Selecting a sample entity

Back to the code

In our code we add a new method for remove team and add the necessary attribute. This method is different to the others as we now use the TryFindEntity method on the LuisResult. The first argument is the name of our entity (see this is why I keep saying about care in naming them). The output of this is of an EntityRecommendation, its easy to think of this as a recommendation as it may not always be correct and is down to the training and experience of the application. Once we have this we can get the Entity from the EntityRecommendation and use that in our code to remove the selected team.

[LuisIntent("RemoveTeam")]
public async Task RemoveTeam(IDialogContext context, LuisResult result)
{
    string teamName = "";
    EntityRecommendation rec;
    if (result.TryFindEntity("TeamName", out rec))
    {
        teamName = rec.Entity;
        try
        {
            champs.RemoveTeam(teamName);
            await context.PostAsync($"{teamName} has been removed from the championships.");
        }
        catch (TeamNotFoundException)
        {
            await context.PostAsync($"The team {teamName} was not found.");
        }
    }
    else
    {
        await context.PostAsync("An error occured. We were unable to remove the team.");
    }
    context.Wait(MessageReceived);
}

Retraining

Before we go any further, we should retrain our application from the web page. This will make sure any changes that we have made, such as our new entities are added. Once we have our application working and it is receiving requests, we can go to the Suggest tab and look at what intent the application has assigned to the input from our bot. This wont always be correct, but you can select each one and select the correct intent and add entities if appropriate. Something you will notice is that the more you use this, the more accurate it becomes.

Retraining

On the right of the web page are some statistics on how accurate the application has performed. It also shows how many utterances have been entered for an intent. If you have an intent that isn’t getting picked up as you expect, it might be worth checking to see if you have not provided enough utterances for the application to be able to accurately predict the intent.

Response Recommendations

Testing in the emulator

We are now coming to the end, so we just need to check this in the emulator. As you can see from the screenshot, the application can pick up what you trying to say and then provide the right response.

Emulator

What next

This post has covered natural language and my last post covered the basics of bots, so continuing the theme I will be writing about prompts and images in your conversations on my next post.

Developing a Bot with the Microsoft Bot Framework

Introduction

In this post we are going to look at creating a very basic bot using the Microsoft Bot Framework. Although the bot itself will be simple, we will connect it to the LUIS natural language service to allow us to train the service to handle responses. From this example, it should be easy to move forward with more complex bots and features.

Create Project in Visual Studio

To create a bot application, you need to install the Bot Application template and Bot Emulator which can both be found here.

When these are installed, create a new project using the Bot Application template. We have called our one MagicEightBall.

Bot Project

The template provides the basic code needed to run a bot. The main area you will edit is the MessagesController class in the Controllers folder. The example code as shown below will return a count of how many characters you have entered. It is a good idea to debug this and test your emulator now with this code.

public async Task Post([FromBody]Message message)
{
    if (message.Type == "Message")
    {
        // calculate something for us to return
        int length = (message.Text ?? string.Empty).Length;

        // return our reply to the user
        return message.CreateReplyMessage($"You sent {length} characters");
    }
    else
    {
        return HandleSystemMessage(message);
    }
}

Using Natural Language

To simplify the process of creating question and responses for our Bot we are going to use LUIS (Language Understanding Intelligent Service). This service allows you to train intents to understand what users have entered and then respond accordingly. The LUIS site can be found here.

Create a new LUIS application

Once you have registered for LUIS, create a new application for the Magic Eight Ball as shown below. The LUIS site has an excellent 10 minute video on creating and training an app, if you have not created one before then I highly recommend that you view this.

LUIS

Train the application to understand a question

The first thing you need to do is assign an intent, which will determine the questions that the user can ask. We have created a basic one called Question that we will train. We would then add new utterances to show what examples of text would be assigned to this intent. The LUIS server will then learn and try and pick up other text that it considers as having the same meaning.

Capture3

If we were getting entities to pass data to our code, we can assign part of our utterances to that entity. In the example below, the intent would be picked up as “Am I”, whilst the Action entity for this would be assigned to “funny”. This allows you to handle various options and settings sent by users in a statement.

Capture4

Connecting to LUIS service

The code sample below is of a MagicEight class that extends the LuisDialog. For each intent we have we should have a method that has the attribute [LuisIntent([IntentName])] as shown below. This method will be called when the bot determines that this question/intent has been called. For our example we do not care about any actions or details from the message, just that it was a valid question. We then send a message of a random response from the list of responses on a standard magic eight ball.

We also provide a method for the None intent by using the attribute [LuisIntent(“”)]. This allows us to provide a default response to users when it can not determine the intent.

Please note the App Id and Key shown in the example below is not valid and you will need to get these from a valid LUIS application.

[LuisModel("b514324f-d6d3-418e-a911-c7fasda6699e2", "a91e3e2044a987876291c54a153f3a6")]
[Serializable]
public class MagicEight : LuisDialog<object>
{
    private static Random rand = new Random(14891734);
    string[] responses = { "It is certain.", "It is decidedly so.",
        "Without a doubt.", "Yes, definately.", "You may rely on it.",
        "As I see it, yes.", "Most likely.", "Outlook good.", "Yes.",
        "Signs point to yes.", "Reply hazy try again.", "Ask again later.",
        "Better not tell you now.", "Cannot predict now",
        "Concentrate and ask again", "Don't count on it.",
        "My reply is no.", "My sources say no.", "Outlook not so good",
        "Very doubtful."};

    [LuisIntent("Question")]
    public async Task AnswerQuestion(IDialogContext context, LuisResult result)
    {
        int x = rand.Next(20);
        string message = responses[x - 1];
        await context.PostAsync(message);
        context.Wait(MessageReceived);
    }

    [LuisIntent("")]
    public async Task None(IDialogContext context, LuisResult<object> result)
    {
        string message = "Sorry I did not understand";
        await context.PostAsync(message);
        context.Wait(MessageReceived);
    }
}

Our final task is to return to our MessagesController class and update the Post method to send new messages to our MagicEight class.

public async Task Post([FromBody]Message message)
{
    if (message.Type == "Message")
    {
        return await Conversation.SendAsync(message, () => new MagicEight());
    }
    else
    {
        return HandleSystemMessage(message);
    }
}

Test in Emulator

Now that we have the code complete, test the application in the emulator.

Capture5

You may not always get the correct response straight away. This is due to the LUIS application having not been trained much, the more work you put into labelling the utterings within the LUIS application the more accurate it will become. Any uttering sent to the service will be available for you to label, or approve the automatically assigned label that the service determined.

You can now take what you have learnt in this post and expand it into more complex bots and connect what you have created into web/windows applications.