Turning Natural Language into Meaning with LUIS (Natural Language Understanding)

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction

In the first article in this bot series, we looked at the basics of the Microsoft Bot Framework, including how to send a basic message reply. In this article, we will take a look at how to use LUIS to recognize what the user is saying and how to apply that recognition to drive a conversation.

Getting Started with LUIS

LUIS is part of the Microsoft Cognitive Services suite hosted inside of Azure. LUIS performs natural language understanding and translates a sentence or phrase into an easy-to-program for intent. It can even extract key parts of a word or phrase without you ever needing to touch complex parsing code.

To get started, head on over to http://www.luis.ai and create an account.

LUIS is organized into intents, utterances, and entities. An intent is a thing you want to recognize, like “The user wants an to know the account balance.” An utterance is a phrase or sentence that a user might say that you want to map to that intent. You can map many utterances to a single intent. An entity is used as a parameter for the intent.

In this example, we are creating a BankerBot that the user can ask what the balances of their various accounts are. The user will ask what their account balance is and will need to name a specific account like, “Checking,” “Money Market,” or “Savings.” The account type in this example is the entity. You will need to create this as a custom entity type before you create your first intent.

To create the entity, click the “+” symbol next to the “entity” menu and you will get a simple dialog that lets you name the entity. You also can define child entities here, but that isn’t necessary for this step. If you create child entities, it will create a defined ontology of members that should map to the entity.

To create an intent, click the “+” symbol next to the intents menu. You’ll see a dialog like the one in Figure 1. We’ll label this intent “Account Balances” and add a parameter called “account” that maps to the custom AccountType entity. When you create a new entity for the first time, you also can give it an example utterance for it to map to.

LUIS1
Figure 1: Adding a new intent

Once your intent has been created, you can map as many utterances to the intent as you want by clicking the “New Utterances” tab at the top of the page. You don’t have to precisely create each variation of the phrases you want to match. In general, LUIS is good at mapping to small variations on the sentence, but you will want to grow the utterances supported over time. LUIS helps you learn what to add under the “Suggest” tab. The suggest tab keeps a catalog of phrases it saw from end users that it wasn’t sure how to map to intents and you can use this page to see what those phrases are. After you’ve seen what users are saying that isn’t matched, you’re only a few short clicks away from mapping it to your existing intents.

Let’s Have a Conversation: Integrating LUIS into Your Bot

LUIS is excellent at categorizing utterances into intents, but a non-trivial bot needs to be able to have a conversation with the user. The user should be able to ask questions, get answers, and ask follow-up questions that retain context.

The way you can do this with LUIS is to create a sub-class of the LuisDialog object. The LuisDialog object lets you define what methods map to what intents and build out dialog trees under those intents. The LuisModel decorator on the object lets you identify your app by your subscription ID and identify which model you’re using, as seen in the following snippet. This code snippet also defines some basic mock data.

[LuisModel("YourLUISModelID", "YourSubscriptionID")]
[Serializable]
public class BankerBotDialog : LuisDialog<object>
{
   public BankerBotDialog(ILuisService service = null) :
      base(service)
   {
      // MOCK DATA
      this.Accounts.Add("checking", new Account() {
         AccountNumber = "113122231",
      AccountType = "Checking",
      Balance = 11416.25M });
      this.Accounts.Add("savings", new Account() {
         AccountNumber = "612351251",
         AccountType = "Savings",
         Balance = 51618.88M });
   }

Next, you will want to map one of your methods to a LUIS intent. The following method includes an attribute, called LuisIntent, that you can use to map this method as being the handler for that intent. It passes in an IDialogContext that includes information about the current chat session and a LuisResult that includes the original text the user sent along with LUIS’ top recommended matches. By default, you know that your method was the top recommended method from LUIS, but sometimes it’s helpful to see what some of the other near-miss intents are.

The next code segment uses a helper method we’ll look at in a second, called “TryFindAccount,” to see which account the user wants to see the balance on. If it gets a match, it uses the context object to post the response to the user. If it doesn’t match an account, it will ask the user a follow-up question as to which account they want the balance on.

[LuisIntent("Account Balances")]
public async Task AccountBalanceAction(IDialogContext context,
   LuisResult result)
{
   if (TryFindAccount(result, out this.CurrentAccount))
   {
      await context.PostAsync(string.Format("Your current account balance "+
      "for {0} ({1}) is {2}.",
      this.CurrentAccount.AccountNumber,
      this.CurrentAccount.AccountType,
      this.CurrentAccount.Balance));
   }
   else
   {
      await context.PostAsync("Which account do you want a balance on?");
   }
   context.Wait(MessageReceived);
}

The TryFindAccount method looks at the LuisResult and attempts to pull out a matched EntityType. It then takes this matched entity and sees if we can find a matching account in the mock-data account collection.

public bool TryFindAccount(LuisResult result,
   out Account CurrentAccount)
{
   CurrentAccount = null;
   string what;
   EntityRecommendation title;
   if (result.TryFindEntity(AccountTypeEntity,
      out title))
   {
      what = title.Entity;
   }
   else
   {
      what = DefaultAccount;
   }
   return this.Accounts.TryGetValue(what.ToLower(),
      out CurrentAccount);
}

The final step in this process is to wire up the LuisDialog created into the main MessagesController.cs. Modify the Post method as seen below to pass the incoming message to your LuisDialog.

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

Exploring the Source Code

The source code for this example can be downloaded here. You will need to have the Microsoft Bot Framework installed, as described in the first article in this series as a prerequisite. You also will need to open a free account at luis.ai and create a model that matches the source code’s LUIS intents. You also will need to modify the AppIDs and AppSecrets in the web.config file and LuisDialog, respectively, for this source code to work.

The code in MessagesController.cs is mostly boilerplate code from the default template with the minor changes noted in the examples above in the Post function. BankerBotDialog.cs contains the implementation of the LuisDialog object and has most of the operative code.

Conclusion

LUIS makes it surprisingly easy to add sophisticated language meaning recognition to your application. In addition to the easy integration to the Microsoft Bot Framework explored in this article, you also can use their API directly to send it utterances and get recognized meanings back.

In the next article in this series, we will take a look at some architectural approaches toward building a better bot!

About the Author

David Talbot is the director of Architecture & Strategy at a leading digital bank. He has almost two decades of innovation across many startups and has written extensively on technology.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read