10/2/2016 Webmaster
Implementing Language Understanding Intelligent Service (LUIS) In Microsoft Bot Framework
Using Language Understanding Intelligent Service (LUIS) in your Microsoft Bot Framework application allows you to create chat bots that are easier for your end-users to interact with.
For this example, we will start with the code created in the article, Implementing A SQL Server Database With The Microsoft Bot Framework. This article covers creating a number guessing game and storing, and displaying, the high scores. In that article, the user is required to type in the exact words, ‘high scores’ and they could only see the high scores for the past day.
In this article, we will alter the code to allow the user to see the high scores for the past week, the past month, or the past days that they specify (up to 30 days). Most importantly we will allow the user to type their request, using normal language, and then detect the intent of the user and the important related entities (such as the number of days).
We will do this by creating an application using the Language Understanding Intelligent Service (LUIS) and then interfacing that application with our existing Bot that is programmed using the SDK of, and deployed in, the Microsoft Bot Framework.
Create The LUIS Application
The first step is to go to https://www.luis.ai/ and create an account on the Language Understanding Intelligent Service (LUIS) site, and log in.
Create a New Application.
We will call it HighScores, and after filling out any other fields, we click Add App.
Now we need to create Entities. These are values that we will need to identify and gather so that our Bot can perform operations based on their value. In our example, we want to create two Entities:
- PeriodOfTime – Detect if the user entered a word describing the amount of days (but not the actual days). For example, ‘week’ or ‘month’
- Days – Detect if the user entered the actual days such as ‘3’ or ‘three’
Click the plus button next to the Entities label (on the left hand side of the editor) to open a box that will allow you to add each Entity (one at a time).
When you’re done, the Entities will be displayed.
Next, we will add an Intent.
( Note: the ‘none’ Intent , to be triggered when an utterance by the user does not match a programmed intent, will be automatically created)
Click the plus button next to the Intents label.
Enter HighScores for the Intent name.
Enter Show me the high scores for the past week for Enter an example of a command that triggers this intent (this is also known as a sample utterance).
Click Save.
The utterance will now display in the New utterances tab.
We want this utterance to trigger the HighScores Intent, so select it from the dropdown.
We also want to detect the associated Entity .
Click on the word week, it will then be highlighted, and a popup box will appear.
Select PeriodOfTime.
Finally, click Submit.
Continue to enter and label additional utterances.
( Note: remember to label any Entities that indicate a number as the Days Entity)
Continue to train the service.
You will notice that eventually it will start detecting the Entities on its own.
However, many times you will still have to correct it.
Enter and correct at least ten different utterances.
Click the Train button in the bottom left-hand corner of the interface to train the model.
You now need to get an Azure subscription key.
Click the Settings button.
If you don’t already have an Azure subscription to LUIS, click the Click here to buy a key from Azure button to get one.
When that process is completed, you will be able to come back to the screen and add the subscription key.
You will then have the ability to select a subscription key to assign to the application.
For more information, see: https://www.luis.ai/Help#CreatingKeys
Return back to your applications by selecting My Applications.
Select your application.
Next, click the Publish button.
Finally, click the Publish web service button.
The published end-point will be displayed.
Make note of the App ID, and Subscription Key. You will need these later to be used in the Bot application.
Now, we need to update the Bot to call the LUIS service…
Update The Bot Application
Open the project from the article: Implementing A SQL Server Database With The Microsoft Bot Framework (you can get the code on the Downloads page of this site), in Visual Studio .
Add a new file called LUISDialogClass.cs using the following code:
using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Luis; using Microsoft.Bot.Builder.Luis.Models; using Microsoft.Bot.Connector; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Web; namespace AiNumberGuesserBot { [LuisModel("{Your App ID}", "{Your Subscription Key}")] [Serializable] public class LUISDialogClass : LuisDialog<object> { #region public async Task None(IDialogContext context, LuisResult result) [LuisIntent("")] [LuisIntent("None")] public async Task None(IDialogContext context, LuisResult result) { // Not a match -- Start a new Game context.Call(new NumberGuesserDialog(), null); } #endregion #region public async Task HighScores(IDialogContext context, LuisResult result) [LuisIntent("HighScores")] public async Task HighScores(IDialogContext context, LuisResult result) { // See if the intent has a > .99 match bool boolIntentMatch = false; foreach (var objIntent in result.Intents) { // If the HighScores Intent is detected // and it's score is greater than or = to .99 // set boolIntentMatch = true if ( (objIntent.Intent == "HighScores") && (objIntent.Score >= .99f) ) { boolIntentMatch = true; } } if (boolIntentMatch) { // ** To Do: Code to handle a Match ** } else { // Not a match -- Start a new Game var objNumberGuesserDialog = new NumberGuesserDialog(); context.Call(objNumberGuesserDialog, null); } } #endregion } }
This code will pass text entered into the Bot to the LUIS application and trigger the None or HighScores methods based on what the LUIS application determines the Intent is that the entered text matches.
We decorate each method with a LuisIntent decoration, setting the name of an Intent (for example [LuisIntent("HighScores")] ) to indicate which method should be triggered.
( Note: the code is not complete at this point, we will complete the code in later steps)
Ensure that you enter the App ID and Subscription Key, from your LUIS application, in the LuisModel decoration at the top of the class.
This how the code knows what LUIS application to connect to.
We now need to call LUISDialogClass, instead of the previously configured NumberGuesserDialog class, when a user communicates with the Bot.
The new LUIS code (LUISDialogClass) will call the NumberGuesserDialog class when needed (for example, when the user has not triggered the HighScores intent).
Open the MessagesController.cs file (in the Controllers folder)
Replace the following code:
// Detect if the user enters the words "high score" if (activity.Text.ToLower().Contains("high score")) { // Call the ShowHighScores method // passing to it, the current Activity ShowHighScores(activity); } else { // Call NumberGuesserDialog await Conversation.SendAsync(activity, () => new NumberGuesserDialog()); }
With:
// Call the LUIS Dialog await Conversation.SendAsync(activity, () => new LUISDialogClass());
There are places where the NumberGuesserDialog will need to call LUISDialogClass (for example, when the user has entered a response that is not a number being guessed as part of the game). The NumberGuesserDialog class will need to return control back to the LUISDialogClass.
Replace the code in the NumberGuesserDialog class that starts (and is encapsulated in the brackets) like the text below:
// See if a number was passed if (!int.TryParse(message.Text, out intGuessedNumber)) { ... }
With the following code:
// See if a number was passed if (!int.TryParse(message.Text, out intGuessedNumber)) { // A number was not passed // Call the LUISDialogClass // Placing it on the dialog stack // context.Forward allows us to 'send forward' // the current message to the child dialog // otherwise the child dialog would start but wait for // the user to send (another) message // See: // http://stackoverflow.com/questions/37522294/calling-forms-from-dialogs await context.Forward( new LUISDialogClass(), null, message, System.Threading.CancellationToken.None); }
The NumberGuesserDialog class is no longer the root class, so we will need to alter it slightly again so that it starts up the game properly when it is triggered by the LUISDialogClass.
Locate the following code in the StartAsync method:
// Start the Game context.Wait(MessageReceivedAsync);
Replace it with:
Activity replyToConversation = (Activity)context.MakeMessage(); replyToConversation.Recipient = replyToConversation.Recipient; replyToConversation.Type = "message"; string strAiHelpWebsite_Small = String.Format(@"{0}/{1}", strBaseURL, "Images/NumberGuesserCard.png"); List<CardImage> cardImages = new List<CardImage>(); cardImages.Add(new CardImage(url: strAiHelpWebsite_Small)); // Create the Buttons List<CardAction> cardButtons = CreateButtons(); HeroCard plCard = new HeroCard() { Images = cardImages, Buttons = cardButtons, }; Attachment plAttachment = plCard.ToAttachment(); replyToConversation.Attachments.Add(plAttachment); replyToConversation.AttachmentLayout = "list"; await context.PostAsync(replyToConversation); // Start the Game context.Wait(MessageReceivedAsync);
This code shows the welcome screen to start the number guessing game.
Implement High Scores
At this point, if we run the application, and connect to it in the emulator (see: Creating a Hello World! Bot Using The Microsoft Bot Framework to see how this is done), we can play the game.
However, if we type anything that triggers the HighScores LUIS Intent, we will receive an error (because the code to handle this has not been implemented yet).
Open the LUISDialogClass.cs file and locate the following code:
if (boolIntentMatch) { // ** To Do: Code to handle a Match ** }
Replace it with:
if (boolIntentMatch) { // Determine the days in the past // to search for High Scores int intDays = -1; #region PeriodOfTime EntityRecommendation PeriodOfTime; if (result.TryFindEntity("PeriodOfTime", out PeriodOfTime)) { switch (PeriodOfTime.Entity) { case "month": intDays = -30; break; case "day": intDays = -1; break; case "week": intDays = -7; break; default: intDays = -1; break; } } #endregion #region Days EntityRecommendation Days; if (result.TryFindEntity("Days", out Days)) { // Set Days int intTempDays; if (int.TryParse(Days.Entity, out intTempDays)) { // A Number was passed intDays = (Convert.ToInt32(intTempDays) * (-1)); } else { // A number was not passed // Call ParseEnglish Method // From: http://stackoverflow.com/questions/11278081/convert-words-string-to-int intTempDays = ParseEnglish(Days.Entity); intDays = (Convert.ToInt32(intTempDays) * (-1)); } // 30 days maximum if (intDays > 30) { intDays = 30; } } #endregion await ShowHighScores(context, intDays); context.Wait(this.MessageReceived); }
This code will set the number of days the user is asking the high scores to include. It first tries to detect if a period of time was passed (in the PeriodOfTime entity). If it has been, the code converts a known period of time to a number of days.
If a number of days was passed (in the Days entity), the code tries to convert the days into a number. If it cannot, it assumes the number of days was passed as a word not a number. If this is the case, it passes the value to the ParseEnglish method that converts the word to a number.
( Note: You can get the ParseEnglish method from: http://stackoverflow.com/questions/11278081/convert-words-string-to-int )
The number of days (and the current context) is then passed to the ShowHighScores method.
As the final step, paste the following code, to implement the ShowHighScores method, into the LUISDialogClass:
#region private async Task ShowHighScores(IDialogContext context, int paramDays) private async Task ShowHighScores(IDialogContext context, int paramDays) { // Get the High Scores Models.BotDataEntities DB = new Models.BotDataEntities(); // Get Yesterday var ParamYesterday = DateTime.Now.AddDays(paramDays); var HighScores = (from UserLog in DB.UserLogs where UserLog.CountOfTurnsToWin != null where UserLog.created > ParamYesterday select UserLog) .OrderBy(x => x.CountOfTurnsToWin) .Take(5) .ToList(); System.Text.StringBuilder sb = new System.Text.StringBuilder(); sb.Append("High Scores:\n\n"); foreach (var Score in HighScores) { sb.Append(String.Format("Score: {0} - {1} - ({2} {3})\n\n" , Score.CountOfTurnsToWin , Score.WinnerUserName , Score.created.ToLocalTime().ToShortDateString() , Score.created.ToLocalTime().ToShortTimeString())); } // Create a reply message var resultMessage = context.MakeMessage(); resultMessage.Type = "message"; resultMessage.Text = sb.ToString(); // Send Message await context.PostAsync(resultMessage); } #endregion
Microsoft Links
Bot Framework Forum (stack overflow)
Download
You can download the code from the Download page