Implementing LUIS Routing within BotFramework

In the previous LUIS article, I introduced how to set up and train (and publish) a LUIS language interpreting web service, getting an “intent” and extracting “entities” from a given “utterance”

In this article I’ll use LUIS to enhance your botframework chatbot

If you haven’t done so already, create your bot using botframework, and set up a LUIS application.

Now that we’ve laid the foundations, let’s build a house. A ..um. chatbot house.. yeah.

LUIS entry point

Botframework has a special type of Dialog especially for interacting with LUIS; the imaginatively titled LuisDialog!

Create a new class that extends LuisDialog<object> and add the LuisModel attribute, adding in your Model Id and Subscription Key from LUIS; an easy way to get these values is to hit “PUBLISH” and “Publish web service” for your LUIS app:

LUIS service URL

Then check the URL and copy the SubscriptionKey and id (for your ModelId) values.

[LuisModel("<LuisModelId>","<LuisSubscriptionKey>")]
public class RootLuisDialog : LuisDialog<object>
{
}

(You can get these values elsewhere within the LUIS pages, but this was the easiest method for me)

Now edit your MessagesController such that the conversation is now started using the static Conversation.SendAsync method, passing in the incoming message activity as the first param and your new dialog as the result of a function for the 2nd param:

await Conversation.SendAsync(activity, () => new RootLuisDialog());

Your MessagesController should now look a bit like this:


public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
    if (activity.Type == ActivityTypes.Message)
    {
        // return our reply to the user
        await Conversation.SendAsync(activity, () => new RootLuisDialog());
    }
    else
    {
        HandleSystemMessage(activity);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK);
    return response;
}

Right now there won’t be any actual functionality since our new dialog class is empty. Let’s rectify that!

As well as having an attribute to map our LUIS app to a Dialog in our codebase, there is another attribute to map a LUIS intent to a method; the attribute just needs the intent name from our LUIS app:

[LuisIntent("local.weather")]
public async Task StartWeatherDialog(IDialogContext context, LuisResult result)
{
}

The method signature needs to take an IDialogContext and a LuisResult and return a Task.

Assuming you’ve TRAINed and PUBLISHed your LUIS app, and you’ve got the Model, Subscription, and Intent attribute values entered correctly, then we can set a breakpoint in that empty method and start debugging.

Open up the bot emulator and enter a phrase that you know LUIS will map to your intent, e.g. “will it rain in London tomorrow?”

As per usual the message hits theMessagesController, but this time we start a Conversation with the RootLuisDialog.

Since you’ve got your LuisModel attribute set up this message will now be posted to your LUIS app’s endpoint.

The resulting json is used to route the response to a method; if the response from LUIS contains the intent local.weather and if I have a method with a matching LuisIntent attribute, then the json response will be mapped to a LuisResult and passed in as a parameter.

Let’s dig into the LuisResult a bit – I’ll ask “is it going to rain in bristol this weekend?”:

LuisResult properties

That’s our query – nice. There are our entities! How handy.

LuisResult entities

What else is in there?

LuisResult intents

Looks like we have the three intents as defined within the LUIS app:

LuisResult intent local.news

local.news gets the lowest score..

LuisResult intent None

Then None

LuisResult local.weather

And local.weather gets the highest score – as it should do. Which is why the method that gets called is the one with a `LuisIntent` attribute configured with the value `local.weather`

No match?

You should always be sure to add in a method that will be called if LUIS fails to match the input to any intent. Use the attribute with an empty string if you haven’t set up a “catch all” in your LUIS app, and add on you “catch all” intent as well if you have one, e.g.

[LuisIntent("")]
[LuisIntent("None")]
public async Task DidntUnderstand(IDialogContext context, LuisResult result){
}

This will ensure you’re always able to respond, perhaps with a “Sorry, I didn’t understand that – can you try saying it in a different way?”

Summary

Hopefully this article has shown you how to take a LUIS app and wire it up to your botframework code, extracting the entities from the utterance and using these to create a dynamic response.

Let me know how you get on!

Leave a Reply

Your email address will not be published. Required fields are marked *