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.

Author: Dave Green

Software developer focusing on Microsoft Technologies, MCSD in Windows Store Apps, full time developer at a real job, part time writer and terrible golf player. Follow on twitter @IntelligentLabs

Leave a Reply

Your email address will not be published. Required fields are marked *