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. If you need more background on LUIS, see language understanding for bots.

Prerequisites

This topic walks you through setting up a simple bot that uses LUIS to recognize a few different intents. The code in this article is based on the NLP with LUIS sample in C# and JavaScript.

Create a LUIS app in the LUIS portal

First, sign up for an account at luis.ai and create a LUIS app in the LUIS portal following the instructions shown here. If you want to create your own version of the sample LUIS app used in this article, within the LUIS portal import this LUIS.Reminders.json file (C# | JS) to build your LUIS app, then train, and publish it.

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 or the CLI tools.

Using LUIS portal

  • Select your published LUIS app from luis.ai.
  • With your published LUIS app open, select the MANAGE tab.
  • Select the Application Information tab on the left side, record the value shown for Application ID as <YOUR_APP_ID>.
  • 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>. Scroll down to the end of the page, record the value shown for Region as <YOUR_REGION>, and record the value shown for Endpoint as <YOUR_ENDPOINT>.

Using CLI tools

You can use the luis and msbot BotBuilder CLI tools to get metadata about your LUIS app and add it to your .bot file.

  1. Open a terminal or command prompt and navigate to the root directory for your bot project.

  2. Make sure that the luis and msbot tools are installed.

    npm install luis msbot
    
  3. Run luis init to create a LUIS resource file (.luisrc). Provide your LUIS authoring key and your region when prompted. You do not need to enter your app ID at this time.

  4. Run the following command to download your metadata and add it to your bot's configuration file. If you've encrypted your configuration file, you will need to provide your secret key to update the file.

    luis get application --appId <your-app-id> --msbot | msbot connect luis --stdin [--secret <YOUR-SECRET>]
    

Configure your bot to use your LUIS app

A reference to the LUIS app is first added when initializing the bot. Then we can call it within our bot logic.

Prerequisite

Before we begin coding, make sure you have the packages necessary for the LUIS app.

Add the following NuGet package to your bot.

  • Microsoft.Bot.Builder.AI.Luis

Download and open the NLP LUIS sample code found here. We will modify the code as needed.

First, add the information required to access your LUIS app including application id, authoring key, subscription key, endpoint and region into the BotConfiguration.bot file. These are the values you saved previously from your published LUIS app.

{
  "name": "LuisBot",
  "services": [
    {
      "type": "endpoint",
      "name": "development",
      "endpoint": "http://localhost:3978/api/messages",
      "appId": "",
      "appPassword": "",
      "id": "1"
    },
    {
      "type": "luis",
      "name": "LuisBot",
      "AppId": "<YOUR_APP_ID>",
      "SubscriptionKey": "<YOUR_SUBSCRIPTION_KEY>",
      "AuthoringKey": "<YOUR_AUTHORING_KEY>",
      "GetEndpoint": "<YOUR_ENDPOINT>",
      "Region": "<YOUR_REGION>"
    }
  ],
  "padlock": "",
  "version": "2.0"
}

Next, we initialize a new instance of the BotService class BotServices.cs, which grabs the above information from your .bot file. Add the following code to the BotServices.cs file.

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 we register the LUIS app as a singleton in the Startup.cs file by add the following code within ConfigureServices.

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 ?? @".\BotConfiguration.bot", secretKey);
    services.AddSingleton(sp => botConfig ?? throw new InvalidOperationException($"The .bot config file could not be loaded. ({botConfig})"));

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

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

        // Creates a logger for the application to use.
        ILogger logger = _loggerFactory.CreateLogger<LuisBot>();

        // Catches any errors that occur during a conversation turn and logs them.
        options.OnTurnError = async (context, exception) =>
        {
            logger.LogError($"Exception caught : {exception}");
            await context.SendActivityAsync("Sorry, it looks like something went wrong.");
        };
        /// ...
    });
}

Next, we need to give your bot this LUIS instance. Open up LuisBot.cs, and add the following code at the top of the file.

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. Please check your '.bot' file for a LUIS service named '{LuisKey}'.");
        }
    }
    /// ...
}

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.

Extract entities

Besides recognizing intent, a LUIS app can also extract entities, which are important words for fulfilling a user's request. For example, for a weather bot, the LUIS app might be able to extract the location for the weather report from the user's message.

A common way to structure your conversation is to identify any entities in the user's message, and prompt for any of the required entities that are not found. Then, the subsequent steps handle the response to the prompt.

When gathering information like entities from multiple steps in a conversation, it can be helpful to save the information you need in your state. If an entity is found, it can be added to the appropriate state field. In your conversation if the current step already has the associated field completed, the step to prompt for that information can be skipped.

Additional resources

For a sample using LUIS, check out the projects for [C#] or [JavaScript].

Next steps