Add natural language understanding to your bot

Note

This topic is for the latest release of the SDK (v4). You can find content for the older version of the SDK (v3) here.

The ability to understand what your user means conversationally and contextually can be a difficult task, but can provide your bot a more natural conversation feel. Language Understanding, called LUIS, enables you to do just that so that your bot can recognize the intent of user messages, allow for more natural language from your user, and better direct the conversation flow. This topic walks you through setting up a simple bot that uses LUIS to recognize a few different intents.

Prerequisites

Create a LUIS app in the LUIS portal

Sign in to the LUIS portal to create your own version of the sample LUIS app. You can create and manage your applications on My Apps.

  1. Select Import new app.
  2. Click Choose App file (JSON format)...
  3. Select reminders.json file located in the CognitiveModels folder of the sample. In the Optional Name, enter LuisBot. This file contains three intents: Calendar_Add, Calendar_Find, and None. We'll use these intents to understand what the user meant when they send a message to the bot. If you want to include entities, see the optional section at the end of this article.
  4. Train the app.
  5. Publish the app to production environment.

Obtain values to connect to your LUIS app

Once your LUIS app is published, you can access it from your bot. You will need to record several values to access your LUIS app from within your bot. You can retrieve that information using the LUIS portal.

Retrieve application information from the LUIS.ai portal

The .bot file act as the place to bring all service references together in one place. The infomration you retrieve will be added to the .bot file in the next section.

  1. Select your published LUIS app from luis.ai.
  2. With your published LUIS app open, select the MANAGE tab.
  3. Select the Application Information tab on the left side, record the value shown for Application ID as <YOUR_APP_ID>.
  4. Select the Keys and Endpoints tab on the left side, record the value shown for Authoring Key as <YOUR_AUTHORING_KEY>. Note that your subscription key is the same as your authoring key.
  5. Scroll down to the end of the page, record the value shown for Region as <YOUR_REGION>.
  6. Record the value shown for Endpoint as <YOUR_ENDPOINT>.

Update the bot file

Add the information required to access your LUIS app including application id, authoring key, subscription key, endpoint and region into the nlp-with-luis.bot file. These are the values you saved previously from your published LUIS app.

{
    "name": "LuisBot",
    "description": "",
    "services": [
        {
            "type": "endpoint",
            "name": "development",
            "endpoint": "http://localhost:3978/api/messages",
            "appId": "",
            "appPassword": "",
            "id": "166"
        },
        {
            "type": "luis",
            "name": "LuisBot",
            "appId": "<luis appid>",
            "version": "0.1",
            "authoringKey": "<luis authoring key>",
            "subscriptionKey": "<luis subscription key>",
            "region": "<luis region>",
            "id": "158"
        }
    ],
    "padlock": "",
    "version": "2.0"
}

Configure your bot to use your LUIS app

Be sure that the nuget package Microsoft.Bot.Builder.AI.Luis is installed for your project.

Next, we initialize a new instance of the BotService class in BotServices.cs, which grabs the above information from your .bot file. The external service is configured using the BotConfiguration class.

using Microsoft.Bot.Builder.AI.Luis;
using Microsoft.Bot.Configuration;

public class BotServices
{
    // Initializes a new instance of the BotServices class
    public BotServices(BotConfiguration botConfiguration)
    {
        foreach (var service in botConfiguration.Services)
        {
            switch (service.Type)
            {
                case ServiceTypes.Luis:
                {
                    var luis = (LuisService)service;
                    if (luis == null)
                    {
                        throw new InvalidOperationException("The LUIS service is not configured correctly in your '.bot' file.");
                    }

                    var app = new LuisApplication(luis.AppId, luis.AuthoringKey, luis.GetEndpoint());
                    var recognizer = new LuisRecognizer(app);
                    this.LuisServices.Add(luis.Name, recognizer);
                    break;
                    }
                }
            }
        }

    // Gets the set of LUIS Services used. LuisServices is represented as a dictionary.  
    public Dictionary<string, LuisRecognizer> LuisServices { get; } = new Dictionary<string, LuisRecognizer>();
}

Then register the LUIS app as a singleton in the Startup.cs file using the following code within ConfigureServices method.

public void ConfigureServices(IServiceCollection services)
{
    var secretKey = Configuration.GetSection("botFileSecret")?.Value;
    var botFilePath = Configuration.GetSection("botFilePath")?.Value;

    // Loads .bot configuration file and adds a singleton that your Bot can access through dependency injection.
    var botConfig = BotConfiguration.Load(botFilePath ?? @".\nlp-with-luis.bot", secretKey);
    services.AddSingleton(sp => botConfig ?? throw new InvalidOperationException($"The .bot config file could not be loaded. ({botConfig})"));

    // Initialize Bot Connected Services client.
    var connectedServices = new BotServices(botConfig);
    services.AddSingleton(sp => connectedServices);

    services.AddBot<LuisBot>(options =>
    {
        // Retrieve current endpoint.
        var environment = _isProduction ? "production" : "development";
        var service = botConfig.Services.Where(s => s.Type == "endpoint" && s.Name == environment).FirstOrDefault();
        if (!(service is EndpointService endpointService))
        {
            throw new InvalidOperationException($"The .bot file does not contain an endpoint with name '{environment}'.");
        }

        options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);

        // ...
    });
}

Next, in the LuisBot.cs file, the bot gets this LUIS instance.

public class LuisBot : IBot
{
    public static readonly string LuisKey = "LuisBot";
    private const string WelcomeText = "This bot will introduce you to natural language processing with LUIS. Type an utterance to get started";

    // Services configured from the ".bot" file.
    private readonly BotServices _services;

    // Initializes a new instance of the LuisBot class.
    public LuisBot(BotServices services)
    {
        _services = services ?? throw new System.ArgumentNullException(nameof(services));
        if (!_services.LuisServices.ContainsKey(LuisKey))
        {
            throw new System.ArgumentException($"Invalid configuration....");
        }
    }
    // ...
}

LUIS is now configured for your bot. Next, let's look at how to get the intent from LUIS.

Get the intent by calling LUIS

Your bot gets results from LUIS by calling the LUIS recognizer.

To have your bot simply send a reply based on the intent that the LUIS app detected, call the LuisRecognizer, to get a RecognizerResult. This can be done within your code whenever you need to get the LUIS intent.

public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))

{
    if (turnContext.Activity.Type == ActivityTypes.Message)
    {
        // Check LUIS model
        var recognizerResult = await _services.LuisServices[LuisKey].RecognizeAsync(turnContext, cancellationToken);
        var topIntent = recognizerResult?.GetTopScoringIntent();
        if (topIntent != null && topIntent.HasValue && topIntent.Value.intent != "None")
        {
            await turnContext.SendActivityAsync($"==>LUIS Top Scoring Intent: {topIntent.Value.intent}, Score: {topIntent.Value.score}\n");
        }
        else
        {
            var msg = @"No LUIS intents were found.
                        This sample is about identifying two user intents:
                        'Calendar.Add'
                        'Calendar.Find'
                        Try typing 'Add Event' or 'Show me tomorrow'.";
            await turnContext.SendActivityAsync(msg);
        }
        }
        else if (turnContext.Activity.Type == ActivityTypes.ConversationUpdate)
        {
            // Send a welcome message to the user and tell them what actions they may perform to use this bot
            await SendWelcomeMessageAsync(turnContext, cancellationToken);
        }
        else
        {
            await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected", cancellationToken: cancellationToken);
        }
}

Any intents recognized in the utterance will be returned as a map of intent names to scores and can be accessed from recognizerResult.Intents. A static recognizerResult?.GetTopScoringIntent() method is provided to help simplify finding the top scoring intent for a result set.

Any entities recognized will be returned as a map of entity names to values and accessed using recognizerResults.entities. Additional entity metadata can be returned by passing a verbose=true setting when creating the LuisRecognizer. The added metadata can then be accessed using recognizerResults.entities.$instance.

Test the bot

  1. Run the sample locally on your machine. If you need instructions, refer to the readme file for C# or JS sample.

  2. In the emulator, type a message as shown below.

test nlp sample input

The bot will respond back with the top scoring intent, which in this case is the 'Calendar_Add' intent. Recall that the reminders.json file you imported in the luis.ai portal defined the intents 'Calendar_Add', 'Calendar_Find', and 'None'.

test nlp sample response

A prediction score indicates the degree of confidence LUIS has for prediction results. A prediction score is between zero (0) and one (1). An example of a highly confident LUIS score is 0.99. An example of a score of low confidence is 0.01.

Optional - Extract entities

Besides recognizing user intent, a LUIS app can also return entities. Entities are important words related to the intent and can sometimes be essential to fulfilling a user's request or allow your bot to behave more intelligently.

Why use entities

LUIS entities allow your bot to intelligently understand certain things or events that are different than the standard intents. This enables you to gather extra information from the user, which lets your bot respond more intelligently or possibly skip certain questions where it asks the user for that information. For example, in a weather bot, the LUIS app might use an entity Location to extract the location of the requested weather report from within the user's message. This would allow your bot to skip the question of where the user is located and provide the user with a smarter and more concise conversation.

Prerequisites

To use entities with this sample, you will need to create a LUIS app that includes entities. Follow the steps in the above section for creating your LUIS app, but instead of using the file reminders.json, use the reminders-with-entities.json file to build your LUIS app. This file provides the same intents plus an additional three entities: Appointment, Meeting, and Schedule. These entities assist LUIS in determining the intent of your user's message.

Extract and display entities

The following optional code can be added to this sample app to extract and display the entity information when an entity is used by LUIS to help identify the user's intent.

The following helper function can be added to your bot to get entities out of the RecognizerResult from LUIS. It will require the use of the Newtonsoft.Json.Linq library, which you'll have to add to your using statements. If entity information is found when parsing the JSON returned from LUIS, the Newtonsoft DeserializeObject function will convert this JSON into a dynamic object, providing access to the entity information.

using Newtonsoft.Json.Linq;

private string ParseLuisForEntities(RecognizerResult recognizerResult)
{
   var result = string.Empty;

   // recognizerResult.Entities returns type JObject.
   foreach (var entity in recognizerResult.Entities)
   {
       // Parse JObject for a known entity types: Appointment, Meeting, and Schedule.
       var appointFound = JObject.Parse(entity.Value.ToString())["Appointment"];
       var meetingFound = JObject.Parse(entity.Value.ToString())["Meeting"];
       var schedFound = JObject.Parse(entity.Value.ToString())["Schedule"];

       // We will return info on the first entity found.
       if (appointFound != null)
       {
           // use JsonConvert to convert entity.Value to a dynamic object.
           dynamic o = JsonConvert.DeserializeObject<dynamic>(entity.Value.ToString());
           if (o.Appointment[0] != null)
           {
              // Find and return the entity type and score.
              var entType = o.Appointment[0].type;
              var entScore = o.Appointment[0].score;
              result = "Entity: " + entType + ", Score: " + entScore + ".";
              
              return result;
            }
        }

        if (meetingFound != null)
        {
            // use JsonConvert to convert entity.Value to a dynamic object.
            dynamic o = JsonConvert.DeserializeObject<dynamic>(entity.Value.ToString());
            if (o.Meeting[0] != null)
            {
                // Find and return the entity type and score.
                var entType = o.Meeting[0].type;
                var entScore = o.Meeting[0].score;
                result = "Entity: " + entType + ", Score: " + entScore + ".";
                
                return result;
            }
        }

        if (schedFound != null)
        {
            // use JsonConvert to convert entity.Value to a dynamic object.
            dynamic o = JsonConvert.DeserializeObject<dynamic>(entity.Value.ToString());
            if (o.Schedule[0] != null)
            {
                // Find and return the entity type and score.
                var entType = o.Schedule[0].type;
                var entScore = o.Schedule[0].score;
                result = "Entity: " + entType + ", Score: " + entScore + ".";
                
                return result;
            }
        }
    }

    // No entities were found, empty string returned.
    return result;
}

This detected entity information can then be displayed along with the identified user intent. To display this information, add the following few lines of code into the sample code's OnTurnAsync task, just after intent information has been displayed.

// See if LUIS found and used an entity to determine user intent.
var entityFound = ParseLuisForEntities(recognizerResult);

// Inform the user if LUIS used an entity.
if (entityFound.ToString() != string.Empty)
{
   await turnContext.SendActivityAsync($"==>LUIS Entity Found: {entityFound}\n");
}
else
{
   await turnContext.SendActivityAsync($"==>No LUIS Entities Found.\n");
}

Test bot with entities

  1. To test your bot that includes entities, run the sample locally as explained above.

  2. In the emulator, enter the message shown below.

test nlp sample input

The bot now responds back with both the top scoring intent 'Calendar_Add' plus the 'Meetings' entity that was used by LUIS to determine user intent.

test nlp sample response

Detecting entities can help improve the overall performance of your bot. For example, detecting the "Meeting" entity (shown above) could allow your application to now call a specialized function designed to create a new meeting on the user's calendar.

Next steps