Integrate multiple LUIS apps and QnA services with the Dispatch tool

This tutorial demonstrates how to use a LUIS model generated by the Dispatch tool, to integrate your bot with multiple Language Understanding (LUIS) apps and QnAMaker services.

Imagine you've developed the following services, and you want to create a bot that integrates with all of them.

Service type Name Description
LUIS app HomeAutomation Recognizes the HomeAutomation.TurnOn, HomeAutomation.TurnOff, and HomeAutomation.None intents.
LUIS app Weather Recognizes the Weather.GetForecast and Weather.GetCondition intents.
QnAMaker service FAQ Provides answers to questions about a home automation lighting system

Let's first create the apps and services, then integrate them together.

Create the LUIS apps

The fastest way to create the HomeAutomation and Weather LUIS apps is to download the homeautomation.json and weather.json files. Then go to the LUIS website and sign in. Click on My Apps > Import new app and choose the homeautomation.json file. Name the new app homeautomation. Click on My Apps > Import new app and choose the weather.json file. Name this other new app weather.

Create the QnA Maker service

Go to the QnA Maker website and sign in. Select Create new service and create a new service named "FAQ". Click the Select file button and upload the sample TSV file. Click Create, and once the service is created, click Publish.

Use the Dispatch tool to create the dispatcher LUIS app

Next, let's create a LUIS app to combine each of the services we created.

Install the Dispatch tool by running this command at a Node.js command prompt.

npm install -g botdispatch

Run the following command to initialize the Dispatch tool of the name CombineWeatherAndLights. Substitute your LUIS authoring key for "YOUR-LUIS-AUTHORING-KEY".

dispatch init -name CombineWeatherAndLights -luisAuthoringKey "YOUR-LUIS-AUTHORING-KEY" -luisAuthoringRegion westus

For each of the LUIS apps that you created, get the LUIS app ID. Those can be found for each app under My Apps on the LUIS site; click on the app name, and then click on Settings to see the Application ID.

Then run the dispatch add command for each of the LUIS apps and the QnA Maker service you created:

dispatch add -type luis -id "HOMEAUTOMATION-APP-ID" -name homeautomation -version 0.1 -key "YOUR-LUIS-AUTHORING-KEY"
dispatch add -type luis -id "WEATHER-APP-ID" -name weather -version 0.1 -key "YOUR-LUIS-AUTHORING-KEY"
dispatch add -type qna -id "QNA-KB-ID" -name faq -key "YOUR-QNA-SUBSCRIPTION-KEY"

Run dispatch create:

dispatch create

This creates the dispatcher LUIS app named CombineWeatherAndLights. You can see the new app in https://www.luis.ai/home.

The dispatcher app in LUIS.ai

Click on the new app. Under Intents you can see it has the l_homeautomation, l_weather, and q_faq intents.

The dispatcher intents in LUIS.ai

Click the Train button to train the LUIS app, and use the PUBLISH tab to publish it. Click on Settings to copy the ID of the new app to use in your bot.

Create the bot

Now you can hook up the dispatcher app's intents to logic in your bot, which routes messages to the original LUIS apps and QnAMaker service.

You can use the sample included with the Bot Builder SDK as a starting point.

Start with the code in the LUIS Dispatch sample. In Visual Studio, update Nuget packages to the latest prerelease versions of the following:

  • Microsoft.Bot.Builder.Integration.AspNet.Core
  • Microsoft.Bot.Builder.Ai.QnA (required for QnA Maker)
  • Microsoft.Bot.Builder.Ai.Luis (required for LUIS)

Set up the sample to use the dispatcher app.

In appsettings.json in the LUIS Dispatch sample, edit the following fields.

Name Description
Luis-SubscriptionKey Your LUIS subscription key. This can be an endpoint key or an authoring key, described here.
Luis-ModelId-Dispatcher App ID for the LUIS app that the Dispatch tool generates.
Luis-ModelId-HomeAutomation App ID of the app you created from homeautomation.json
Luis-ModelId-Weather App ID of the app you created from weather.json
QnAMaker-Endpoint-Url This should be set to https://westus.api.cognitive.microsoft.com/qnamaker/v2.0 for Preview QnA Maker services.
Set this to https://YOUR-QNA-SERVICE-NAME.azurewebsites.net/qnamaker for new (GA) QnA Maker services.
QnAMaker-SubscriptionKey Your QnA Maker subscription key.
QnAMaker-KnowledgeBaseId The ID of the knowledge base you create at the QnAMaker portal.

In Startup.cs, take a look at the ConfigureServices method. It contains code to initialize LuisRecognizerMiddleware using the app ID of the LUIS app you just generated.

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(this.Configuration);
    services.AddBot<LuisDispatchBot>(options =>
    {
        options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);

        var (luisModelId, luisSubscriptionKey, luisUri) = GetLuisConfiguration(this.Configuration, "Dispatcher");

        var luisModel = new LuisModel(luisModelId, luisSubscriptionKey, luisUri);

        // If you want to get all the intents and scores that LUIS recognized, set Verbose = true in luisOptions
        var luisOptions = new LuisRequest { Verbose = true };

        var middleware = options.Middleware;
        middleware.Add(new LuisRecognizerMiddleware(luisModel, luisOptions: luisOptions));
    });
}

The GetLuisConfiguration and GetQnAMakerConfiguration methods get your LUIS and QnA configuration from appsettings.json.

public static (string modelId, string subscriptionId, Uri uri) GetLuisConfiguration(IConfiguration configuration, string serviceName)
{
    var modelId = configuration.GetSection($"Luis-ModelId-{serviceName}")?.Value;
    var subscriptionId = configuration.GetSection("Luis-SubscriptionKey")?.Value;
    var uri = new Uri(configuration.GetSection("Luis-Url")?.Value);
    return (modelId, subscriptionId, uri);
}

public static (string knowledgeBaseId, string subscriptionKey, string uri) GetQnAMakerConfiguration(IConfiguration configuration)
{
    var knowledgeBaseId = configuration.GetSection("QnAMaker-KnowledgeBaseId")?.Value;
    var subscriptionKey = configuration.GetSection("QnAMaker-SubscriptionKey")?.Value;
    var uri = configuration.GetSection("QnAMaker-Endpoint-Url")?.Value;
    return (knowledgeBaseId, subscriptionKey, uri);
}

Dispatch the message

Take a look at LuisDispatchBot.cs, where the bot dispatches the message to the LUIS app or QnA Maker for a subcomponent.

In DispatchToTopIntent, if your dispatcher app detects the l_homeautomation or l_weather intent, it calls a DispatchToTopIntent method that creates a LuisRecognizer to call the original homeautomation and weather apps. If the bot detects the q_faq intent, or the none intent that is used as a fallback case, it calls a method that queries QnAMaker.

Note

If the intent names l_homeautomation, l_weather or q_faq don't match the LUIS app you created using Dispatch, edit them to match the lower case version of the intent names you see in the LUIS portal.

private async Task DispatchToTopIntent(ITurnContext context, (string intent, double score)? topIntent)
{
    switch (topIntent.Value.intent.ToLowerInvariant())
    {
        case "l_homeautomation":
            await DispatchToLuisModel(context, this.luisModelHomeAutomation, "home automation");
            break;
        case "l_weather":
            await DispatchToLuisModel(context, this.luisModelWeather, "weather");
            break;
        case "none":
        // 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 "q_faq":
            await DispatchToQnAMaker(context, this.qnaEndpoint, "FAQ");
            break;
        default:
            // The intent didn't match any case, so just display the recognition results.
            await context.SendActivity($"Dispatch intent: {topIntent.Value.intent} ({topIntent.Value.score}).");

            break;
    }
}

The DispatchToQnAMaker method sends the user's message to the QnA Maker service. Make sure you have published that service in the QnA Maker portal before you run the bot.

private static async Task DispatchToQnAMaker(ITurnContext context, QnAMakerEndpoint qnaOptions, string appName)
{
    QnAMaker qnaMaker = new QnAMaker(qnaOptions);
    if (!string.IsNullOrEmpty(context.Activity.Text))
    {
        var results = await qnaMaker.GetAnswers(context.Activity.Text.Trim()).ConfigureAwait(false);
        if (results.Any())
        {
            await context.SendActivity(results.First().Answer);
        }
        else
        {
            await context.SendActivity($"Couldn't find an answer in the {appName}.");
        }
    }
}

The DispatchToLuisModel method sends the user's message to the original homeautomation and weather LUIS apps. Make sure you have published those LUIS apps in the LUIS portal before you run the bot.

private static async Task DispatchToLuisModel(ITurnContext context, LuisModel luisModel, string appName)
{
    await context.SendActivity($"Sending your request to the {appName} system ...");
    var (intents, entities) = await RecognizeAsync(luisModel, context.Activity.Text);

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

    if (entities.Count() > 0)
    {
        await context.SendActivity($"The following entities were found in the message:\n\n{string.Join("\n\n", entities)}");
    }
    
    // Here, you can add code for calling the hypothetical home automation or weather service, 
    // passing in the appName and any intent or entity information that you need 
}

The RecognizeAsync method calls a LuisRecognizer to get results from a LUIS app.

private static async Task<(IEnumerable<string> intents, IEnumerable<string> entities)> RecognizeAsync(LuisModel luisModel, string text)
{
    var luisRecognizer = new LuisRecognizer(luisModel);
    var recognizerResult = await luisRecognizer.Recognize(text, System.Threading.CancellationToken.None);

    // list the intents
    var intents = new List<string>();
    foreach (var intent in recognizerResult.Intents)
    {
        intents.Add($"'{intent.Key}', score {intent.Value}");
    }

    // list the entities
    var entities = new List<string>();
    foreach (var entity in recognizerResult.Entities)
    {
        if (!entity.Key.ToString().Equals("$instance"))
        {
            entities.Add($"{entity.Key}: {entity.Value.First}");
        }
    }

    return (intents, entities);
}

Run the bot

Test out the bot using the Bot Framework Emulator. Send it messages like "turn on the lights" to dispatch the message to the home automation LUIS app, and send it messages like "get the weather in Seattle" to dispatch to the weather LUIS app.

Note

Before you run the bot, make sure you have published all the LUIS apps that you created in the LUIS portal, and check that you've published the QnA Maker service in the QnA Maker portal.

Send messages to the dispatch bot

Evaluate the dispatcher's performance

Sometimes there are user messages that are provided as examples in both the LUIS apps and the QnA maker services, and the combined LUIS app that Dispatch generates won't perform well for those inputs. Check your app's performance using the eval option.

dispatch eval

Running dispatch eval generates a Summary.html file that provides statistics on the performance of the new language model.

Tip

You can actually run dispatch eval on any LUIS app, not just LUIS apps created by the dispatch tool.

Edit intents for duplicates and overlaps

Review example utterances that are flagged as duplicates in Summary.html, and remove similar or overlapping examples. For example, let's say that in the homeautomation LUIS app for homeautomation, 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 homeautomation LUIS app, and add the utterances in that intent to the "None" intent in the dispatcher app.
  • If you don't remove the "None" intent from the original LUIS app, you need to add logic in your bot to pass those messages that match that intent on to the QnA maker service.

Tip

Review Best practices for Language Understanding for tips on improving your language model's performance.

Additional resources