Implementing Language Understanding Intelligent Service (LUIS) In Microsoft Bot Framework

Oct 2

Written by:
10/2/2016 12:00 PM  RssIcon

image

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.

image

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.

image

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

image

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.

image

Create a New Application.

image14

We will call it HighScores, and after filling out any other fields, we click Add App.

image

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’

image

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).

image

When you’re done, the Entities will be displayed.

image

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.

image

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.

image

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.

image

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.

image

Finally, click Submit.

image

Continue to enter and label additional utterances.

(Note: remember to label any Entities that indicate a number as the Days Entity)

image

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.

image

Click the Train button in the bottom left-hand corner of the interface to train the model.

image

You now need to get an Azure subscription key.

Click the Settings button.

image

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

image

Return back to your applications by selecting My Applications.

image

Select your application.

image

Next, click the Publish button.

image

Finally, click the Publish web service button.

image

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

image

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)

 

image

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.

image

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());

 

image

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);
    }

 

image

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

image

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).

image

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

 

 

Ai Help Website Links

Creating a Hello World! Bot Using The Microsoft Bot Framework

Introduction To Using Dialogs With The Microsoft Bot Framework

Creating a Skype Bot Using The Microsoft Bot Framework

Microsoft Links

Microsoft Bot Framework

Bot Builder (GitHub)

Bot Framework Forum (stack overflow)

LUIS (Help)

Download

You can download the code from the Download page