A sample bot integrated with a LUIS.ai application for understanding natural language.
The minimum prerequisites to run this sample are:
- Latest Node.js with NPM. Download it from here.
- The Bot Framework Emulator. To install the Bot Framework Emulator, download it from here. Please refer to this documentation article to know more about the Bot Framework Emulator.
- [Recommended] Visual Studio Code for IntelliSense and debugging, download it from here for free.
If you want to test this sample, you have to import the pre-build LuisBot.json file to your LUIS account.
The first step to using LUIS is to create or import an application. Go to the home page, www.luis.ai, and log in. After creating your LUIS account you'll be able to Import an Existing Application where can you can select a local copy of the LuisBot.json file an import it.
Once you imported the application you'll need to "train" the model (Training) before you can "Publish" the model in an HTTP endpoint. For more information, take a look at Publishing a Model.
Finally, edit the .env file and update the LUIS_MODEL_URL
variable with your's Model URL.
In the LUIS application's dashboard, click the "Publish App" button in the right side bar, select an Endpoint Key and then click the "Publish" button. After a couple of moments, you will see a url that makes your models available as a web service.
One of the key problems in human-computer interactions is the ability of the computer to understand what a person wants, and to find the pieces of information that are relevant to their intent. In the LUIS application, you will bundle together the intents and entities that are important to your task. Read more about Planning an Application in the LUIS Help
The BotBuilder Node SDK contains Recognizer plugins that allow to detect intention from user messages using different methods, from Regex to natural language understanding. These Recognizer plugins and the IntentDialog are useful for building more open ended bots that support natural language style understanding.
Out of the box, Bot Builder comes with a LuisRecognizer that can be used to call a machine learning model you have trained via the LUIS web site. You can create a LuisRecognizer that is pointed at your model and then pass that recognizer to an IntentDialog at creation time using the options structure, or you can register a global recognizer that will listen to every user message and detect intention. Check out how to register a global LuisRecognizer:
// You can provide your own model by specifing the 'LUIS_MODEL_URL' environment variable
// This Url can be obtained by uploading or creating your model from the LUIS portal: https://www.luis.ai/
var recognizer = new builder.LuisRecognizer(process.env.LUIS_MODEL_URL);
bot.recognizer(recognizer);
Intent recognizers return matches as named intents. To match against an intent from a recognizer you pass the name of the intent you want to handle to IntentDialog.matches() or use the dialog's triggerAction() by specifing the intent name with matches property. See how the bot matches the SearchHotels
, ShowHotelsReviews
and Help
intents.
bot.dialog('SearchHotels', [
// ... waterfall dialog ...
]).triggerAction({
matches: 'SearchHotels'
});
bot.dialog('ShowHotelsReviews', function (session, args) {
// ...
}).triggerAction({
matches: 'ShowHotelsReviews'
});
bot.dialog('Help', function (session) {
// ...
}).triggerAction({
matches: 'Help'
});
LUIS can not only identify a users intention given an utterance, it can extract entities from their utterance as well. Any entities recognized in the users utterance will be passed to the intent handler via its args
parameter.
Bot Builder includes an EntityRecognizer
class to simplify working with these entities. You can use EntityRecognizer.findEntity()
and EntityRecognizer.findAllEntities()
to search for entities of a specific type by name. Check out how city and airport entities are extracted.
var cityEntity = builder.EntityRecognizer.findEntity(args.intent.entities, 'builtin.geography.city');
var airportEntity = builder.EntityRecognizer.findEntity(args.intent.entities, 'AirportCode');
The AirportCode
entity makes use of the LUIS Pattern Features which helps LUIS infer entities based on an Regular Expression match, for instance, Airport Codes consist of three consecutive alphabetic characters. You can read more about Pattern Features in the Add Features section of the LUIS Help Docs.
Another LUIS Model Feature used is Phrase List Features, for instance, the model includes a phrase list named Near which categorizes the words: near, around, close and nearby. Phrase list features work for both words and phrase and what LUIS learns about one phrase will automatically be applied to the others as well.
Note: Both RegEx and Phrase List are transparent from the Bot's implementation perspective. Think of model features as "hints" used by the Machine Learning algorithm to help categorize and recognize words that compound Entities and Intents.
In our sample, we are using a waterfall dialog for the hotel search. This is a common pattern that you'll likely use for most of your intent handlers. The way waterfalls work in Bot Builder is the very first step of the waterfall is called when a dialog (or in this case intent handler) is triggered. The step then does some work and continues execution of the waterfall by either calling another dialog (like a built-in prompt) or calling the optional next()
function passed in. When a dialog is called in a step, any result returned from the dialog will be passed as input to the results parameter for the next step.
Our bot tries to check if an entity of city or airport type were matched and forwards it to the next step. If that's not the case, the user is prompted with a destination. The next step will receive the destination or airport code in the results
argument.
bot.dialog('SearchHotels', [
function (session, args, next) {
session.send('Welcome to the Hotels finder! We are analyzing your message: \'%s\'', session.message.text);
// try extracting entities
var cityEntity = builder.EntityRecognizer.findEntity(args.intent.entities, 'builtin.geography.city');
var airportEntity = builder.EntityRecognizer.findEntity(args.intent.entities, 'AirportCode');
if (cityEntity) {
// city entity detected, continue to next step
session.dialogData.searchType = 'city';
next({ response: cityEntity.entity });
} else if (airportEntity) {
// airport entity detected, continue to next step
session.dialogData.searchType = 'airport';
next({ response: airportEntity.entity });
} else {
// no entities detected, ask user for a destination
builder.Prompts.text(session, 'Please enter your destination');
}
},
function (session, results) {
var destination = results.response;
var message = 'Looking for hotels';
if (session.dialogData.searchType === 'airport') {
message += ' near %s airport...';
} else {
message += ' in %s...';
}
session.send(message, destination);
// Async search
Store
.searchHotels(destination)
.then(function (hotels) {
// args
session.send('I found %d hotels:', hotels.length);
var message = new builder.Message()
.attachmentLayout(builder.AttachmentLayout.carousel)
.attachments(hotels.map(hotelAsAttachment));
session.send(message);
// End
session.endDialog();
});
}
]).triggerAction({
matches: 'SearchHotels',
onInterrupted: function (session) {
session.send('Please provide a destination');
}
});
Similarly, the ShowHotelsReviews
uses a single closure to search for hotel reviews.
bot.dialog('ShowHotelsReviews', function (session, args) {
// retrieve hotel name from matched entities
var hotelEntity = builder.EntityRecognizer.findEntity(args.intent.entities, 'Hotel');
if (hotelEntity) {
session.send('Looking for reviews of \'%s\'...', hotelEntity.entity);
Store.searchHotelReviews(hotelEntity.entity)
.then(function (reviews) {
var message = new builder.Message()
.attachmentLayout(builder.AttachmentLayout.carousel)
.attachments(reviews.map(reviewAsAttachment));
session.endDialog(message);
});
}
}).triggerAction({
matches: 'ShowHotelsReviews'
});
NOTE: When using an IntentDialog, you should avoid adding a matches() handler for LUIS’s “None” intent. Add a onDefault() handler instead (or a default dialog when using global recognizers). The reason for this is that a LUIS model will often return a very high score for the None intent if it doesn’t understand the users utterance. In the scenario where you’ve configured the IntentDialog with multiple recognizers that could cause the None intent to win out over a non-None intent from a different model that had a slightly lower score. Because of this the LuisRecognizer class suppresses the None intent all together. If you explicitly register a handler for “None” it will never be matched. The onDefault() handler (or the bot's default dialog) however can achieve the same effect because it essentially gets triggered when all of the models reported a top intent of “None”.
If you want to enable spelling correction, set the IS_SPELL_CORRECTION_ENABLED
key to true
in the .env file.
Microsoft Bing Spell Check API provides a module that allows you to to correct the spelling of the text. Check out the reference to know more about the modules available.
spell-service.js is the core component illustrating how to call the Bing Spell Check RESTful API.
In this sample we added spell correction as a middleware. Check out the middleware in app.js.
if (process.env.IS_SPELL_CORRECTION_ENABLED === 'true') {
bot.use({
botbuilder: function (session, next) {
spellService
.getCorrectedText(session.message.text)
.then(function (text) {
session.message.text = text;
next();
})
.catch(function (error) {
console.error(error);
next();
});
}
});
}
You will see the following in the Bot Framework Emulator when opening and running the sample solution.
To get more information about how to get started in Bot Builder for Node and LUIS please review the following resources:
- Bot Builder for Node.js Reference
- Understanding Natural Language
- LUIS Help Docs
- Cognitive Services Documentation
- IntentDialog
- EntityRecognizer
- Alarm Bot in Node
- Microsoft Bing Spell Check API
Limitations
The functionality provided by the Bot Framework Activity can be used across many channels. Moreover, some special channel features can be unleashed using the Message.sourceEvent method.The Bot Framework does its best to support the reuse of your Bot in as many channels as you want. However, due to the very nature of some of these channels, some features are not fully portable.
The features used in this sample are fully supported in the following channels:
- Skype
- Microsoft Teams
- DirectLine
- WebChat
- Slack
- GroupMe
- Telegram
They are also supported, with some limitations, in the following channels:
- Kik
On the other hand, they are not supported and the sample won't work as expected in the following channel:
- SMS