Use multiple LUIS and QnA models

APPLIES TO: yesSDK v4 no SDK v3

In this tutorial, we demonstrate how to use the Dispatch service to route utterances when there are multiple LUIS models and QnA maker services for different scenarios supported by a bot. In this case, we configure Dispatch with multiple LUIS models for conversations around home automation and weather information, plus QnA maker service to answer questions based on a FAQ text file as input. This sample combines the following services.

Name Description
HomeAutomation A LUIS app that recognizes a home automation intent with associated entity data.
Weather A LUIS app that recognizes the Weather.GetForecast and Weather.GetCondition intents with location data.
FAQ A QnA Maker KB that provides answers to simple questions about the bot.

Prerequisites

Create the services and test the bot

You may follow the README instructions for C# or JS to create this bot using Command Line Interface calls, or follow the steps below to manually create your bot using the Azure, LUIS, and QnAMaker User Interfaces.

Create your bot using service UI

To begin manually creating your bot, download the following 4 files located in the GitHub BotFramework-Samples repository into a local folder: home-automation.json, weather.json, nlp-with-dispatchDispatch.json, QnAMaker.tsv One method to accomplish this is to open the GitHub repository link above, click on BotFramework-Samples, then "Clone or download" the repository to your local machine. Note that these files are in a different repository than the sample mentioned in the prerequisites.

Manually create LUIS apps

Log into the LUIS web portal. Under section My apps select the Tab Import new app. The following Dialog Box will appear:

Import LUIS json file

select the button Choose app file and select the downloaded file 'home-automation.json'. Leave the optional name field blank. Select Done.

Once LUIS opens up your Home Automation app, select the Train button. This will train your app using the set of utterances you just imported using the 'home-automation.json' file.

When training is complete, select the Publish button. The following Dialog Box will appear:

Publish LUIS app

Choose the 'production' environment and then select the Publish button.

Once your new LUIS app has been published, select the MANAGE Tab. From the 'Application Information' page, record the values Application ID and Display name. From the 'Key and Endpoints' page, record the values Authoring Key and Region. These values will later be used by your 'nlp-with-dispatch.bot' file.

Once completed, Train and Publish both your LUIS weather app and your LUIS dispatch app by repeating these same steps for the locally downloaded 'weather.json' and 'nlp-with-dispatchDispatch.json' files.

Manually create QnA Maker app

The first step to setting up a QnA Maker knowledge base is to first set up a QnA Maker service in Azure. To do that, follow the step-bystep instructions found here. Now log into the QnAMaker web portal. Move down to Step 2

Create QnA Step 2

and select

  1. Your Azure AD account.
  2. Your Azure subscription name.
  3. The name you created for your QnA Maker service. (If your Azure QnA service does not initially appear in this pull down list, try refreshing the page.)

Move to Step 3

Create QnA Step 3

Provide a name for your QnA Maker knowledgebase. For this example we will be using the name 'sample-qna'.

Move to Step 4

Create QnA Step 4

select the option + Add File and select the downloaded file 'QnAMaker.tsv'

There is an additional selection to add a Chit-chat personality to your knowledgebase but our example does not include this option.

Select Save and train and when finished select the PUBLISH Tab and publish your app.

Once your QnA Maker app is published, select the SETTINGS Tab, and scroll down to 'Deployment details'. Record the following values from the Postman Sample HTTP request.

POST /knowledgebases/<Your_Knowledgebase_Id>/generateAnswer
Host: <Your_Hostname>
Authorization: EndpointKey <Your_Endpoint_Key>

These values will later be used by your 'nlp-with-dispatch.bot' file.

Manually update your .bot file

Once all of your service apps are created, the information for each needs to be added into your 'nlp-with-dispatch.bot' file. Open this file within the C# or JS Sample file that you previously downloaded. Add the following values to each section of "type": "luis" or "type": "dispatch"

"appId": "<Your_Recorded_App_Id>",
"authoringKey": "<Your_Recorded_Authoring_Key>",
"subscriptionKey": "<Your_Recorded_Authoring_Key>",
"version": "0.1",
"region": "<Your_Recorded_Region>",

For the section of "type": "qna" Add the following values:

"type": "qna",
"name": "sample-qna",
"id": "201",
"kbId": "<Your_Recorded_Knowledgebase_Id>",
"subscriptionKey": "<Your_Azure_Subscription_Key>", // Used when creating your QnA service.
"endpointKey": "<Your_Recorded_Endpoint_Key>",
"hostname": "<Your_Recorded_Hostname>"

When all changes are inplace, save this file.

Test your bot

Now run the sample using the emulator. Once the emulator is opened, select the 'nlp-with-dispatch.bot' file.

For your reference, here are some of the questions and commands that are covered by the services we've included:

  • QnA Maker
    • hi, good morning
    • what are you, what do you do
  • LUIS (home automation)
    • turn on bedroom light
    • turn off bedroom light
    • make some coffee
  • LUIS (weather)
    • whats the weather in redmond washington
    • what's the forecast for london
    • show me the forecast for nebraska

Connecting to the services from your bot

To connect to the Dispatch, LUIS, and QnA Maker services, your bot pulls information from the .bot file.

In Startup.cs, ConfigureServices reads in the configuration file, and InitBotServices uses that information to initialize the services. Each time it's created, the bot is initialized with the registered BotServices object. Here are the relevant parts of these two methods.

public void ConfigureServices(IServiceCollection services)
{
    //...
    var botConfig = BotConfiguration.Load(botFilePath ?? @".\nlp-with-dispatch.bot", secretKey);
    services.AddSingleton(sp => botConfig
        ?? throw new InvalidOperationException($"The .bot config file could not be loaded. ({botConfig})"));

    // ...
    
    var connectedServices = InitBotServices(botConfig);
    services.AddSingleton(sp => connectedServices);
    
    services.AddBot<NlpDispatchBot>(options =>
    {
          
          // The Memory Storage used here is for local bot debugging only. 
          // When the bot is restarted, everything stored in memory will be gone.

          Storage dataStore = new MemoryStorage();

          // ...

          // Create Conversation State object.
          // The Conversation State object is where we persist anything at the conversation-scope.

          var conversationState = new ConversationState(dataStore);
          options.State.Add(conversationState);
     });
}

The following code initializes the bot's references to external services. For example, LUIS and QnaMaker services are created here. These external services are configured using the BotConfiguration class (based on the contents of your .bot file.)

private static BotServices InitBotServices(BotConfiguration config)
{
    var qnaServices = new Dictionary<string, QnAMaker>();
    var luisServices = new Dictionary<string, LuisRecognizer>();

    foreach (var service in config.Services)
    {
        switch (service.Type)
        {
            case ServiceTypes.Luis:
                {
                    // ...
                    var app = new LuisApplication(luis.AppId, luis.AuthoringKey, luis.GetEndpoint());
                    var recognizer = new LuisRecognizer(app);
                    luisServices.Add(luis.Name, recognizer);
                    break;
                }

            case ServiceTypes.Dispatch:
                // ...
                var dispatchApp = new LuisApplication(dispatch.AppId, dispatch.AuthoringKey, dispatch.GetEndpoint());

                // Since the Dispatch tool generates a LUIS model, we use the LuisRecognizer to resolve the
                // dispatching of the incoming utterance.
                var dispatchARecognizer = new LuisRecognizer(dispatchApp);
                luisServices.Add(dispatch.Name, dispatchARecognizer);
                break;

            case ServiceTypes.QnA:
                {
                    // ...
                    var qnaEndpoint = new QnAMakerEndpoint()
                    {
                        KnowledgeBaseId = qna.KbId,
                        EndpointKey = qna.EndpointKey,
                        Host = qna.Hostname,
                    };

                    var qnaMaker = new QnAMaker(qnaEndpoint);
                    qnaServices.Add(qna.Name, qnaMaker);
                    break;
                }
        }
    }

    return new BotServices(qnaServices, luisServices);
}

Calling the services from your bot

The bot logic checks the user input against the combined Dispatch model.

In the NlpDispatchBot.cs file, the bot's constructor gets the BotServices object that we registered at startup.

private readonly BotServices _services;

public NlpDispatchBot(BotServices services)
{
    _services = services ?? throw new System.ArgumentNullException(nameof(services));

    //...
}

In the bot's OnTurnAsync method, we check incoming messages from the user against the Dispatch model.

// Get the intent recognition result
var recognizerResult = await _services.LuisServices[DispatchKey].RecognizeAsync(context, cancellationToken);
var topIntent = recognizerResult?.GetTopScoringIntent();

if (topIntent == null)
{
    await context.SendActivityAsync("Unable to get the top intent.");
}
else
{
    await DispatchToTopIntentAsync(context, topIntent, cancellationToken);
}

Working with the recognition results

When the model produces a result, it indicates which service can most appropriately process the utterance. The code in this bot routes the request to the corresponding service, and then summarizes the response from the called service.

// Depending on the intent from Dispatch, routes to the right LUIS model or QnA service.
private async Task DispatchToTopIntentAsync(
    ITurnContext context,
    (string intent, double score)? topIntent,
    CancellationToken cancellationToken = default(CancellationToken))
{
    const string homeAutomationDispatchKey = "l_Home_Automation";
    const string weatherDispatchKey = "l_Weather";
    const string noneDispatchKey = "None";
    const string qnaDispatchKey = "q_sample-qna";

    switch (topIntent.Value.intent)
    {
        case homeAutomationDispatchKey:
            await DispatchToLuisModelAsync(context, HomeAutomationLuisKey);

            // Here, you can add code for calling the hypothetical home automation service, passing in any entity
            // information that you need.
            break;
        case weatherDispatchKey:
            await DispatchToLuisModelAsync(context, WeatherLuisKey);

            // Here, you can add code for calling the hypothetical weather service,
            // passing in any entity information that you need
            break;
        case noneDispatchKey:
            // You can provide logic here to handle the known None intent (none of the above).
            // In this example we fall through to the QnA intent.
        case qnaDispatchKey:
            await DispatchToQnAMakerAsync(context, QnAMakerKey);
            break;

        default:
            // The intent didn't match any case, so just display the recognition results.
            await context.SendActivityAsync($"Dispatch intent: {topIntent.Value.intent} ({topIntent.Value.score}).");
            break;
    }
}

// Dispatches the turn to the request QnAMaker app.
private async Task DispatchToQnAMakerAsync(
    ITurnContext context,
    string appName,
    CancellationToken cancellationToken = default(CancellationToken))
{
    if (!string.IsNullOrEmpty(context.Activity.Text))
    {
        var results = await _services.QnAServices[appName].GetAnswersAsync(context).ConfigureAwait(false);
        if (results.Any())
        {
            await context.SendActivityAsync(results.First().Answer, cancellationToken: cancellationToken);
        }
        else
        {
            await context.SendActivityAsync($"Couldn't find an answer in the {appName}.");
        }
    }
}


// Dispatches the turn to the requested LUIS model.
private async Task DispatchToLuisModelAsync(
    ITurnContext context,
    string appName,
    CancellationToken cancellationToken = default(CancellationToken))
{
    await context.SendActivityAsync($"Sending your request to the {appName} system ...");
    var result = await _services.LuisServices[appName].RecognizeAsync(context, cancellationToken);

    await context.SendActivityAsync($"Intents detected by the {appName} app:\n\n{string.Join("\n\n", result.Intents)}");

    if (result.Entities.Count > 0)
    {
        await context.SendActivityAsync($"The following entities were found in the message:\n\n{string.Join("\n\n", result.Entities)}");
    }
}

Edit intents to improve performance

Once your bot is running, it is possible to improve the bot's performance by removing similar or overlapping utterances. For example, let's say that in the Home Automation LUIS app requests like "turn my lights on" map to a "TurnOnLights" intent, but requests like "Why won't my lights turn on?" map to a "None" intent so that they can be passed on to QnA Maker. When you combine the LUIS app and the QnA Maker service using dispatch, you need to do one of the following:

  • Remove the "None" intent from the original Home Automation LUIS app, and instead add the utterances from that intent to the "None" intent in the dispatcher app.
  • If you don't remove the "None" intent from the original LUIS app, you will instead need to add logic into your bot to pass the messages that match your "None" intent on to the QnA maker service.

Either of the above two actions will reduce the number of times that your bot responds back to your users with the message, 'Couldn't find an answer.'

Additional resources

Update or create a new LUIS model: This sample is based on a preconfigured LUIS model. Additional information to help you update this model, or create a new LUIS model, can be found here.

Delete resources: This sample creates a number of applications and resources that you can delete using the steps listed below, but you should not delete resources that any other apps or services rely on.

LUIS resources

  1. Sign in to the luis.ai portal.
  2. Go to the My Apps page.
  3. Select the apps created by this sample.
    • Home Automation
    • Weather
    • NLP-With-Dispatch-BotDispatch
  4. Click Delete, and click Ok to confirm.

QnA Maker resources

  1. Sign in to the qnamaker.ai portal.
  2. Go to the My knowledge bases page.
  3. Click the delete button for the Sample QnA knowledge base, and click Delete to confirm.

Best practice: To improve services used in this sample, refer to best practice for LUIS, and QnA Maker.