Porting from Gupshup to Microsoft Bot Framework - node.js

I am new to Microsoft Bot Framework. Earlier I was using Gupshup to build my bots. Gupshup had designed the workflow in a very nice manner. I had used api.ai NLP engine with Gupshup. I want to switch and try MS Bot Framework now with api.ai.
Below is my Gupshup's code:
function MessageHandler(context, event) {
sendMessageToApiAi({
message : event.message,
sessionId : new Date().getTime() +'api',
nlpToken : "74c04b2c16284c738a8dbcf6bb343f",
callback : function(res){
if(JSON.parse(res).result.parameters.Ent_1=="Hello"){
context.sendResponse("Hello");
}
}
},context);
};
function sendMessageToApiAi(options,botcontext) {
var message = options.message; // Mandatory
var sessionId = options.sessionId || ""; // optinal
var callback = options.callback;
if (!(callback && typeof callback == 'function')) {
return botcontext.sendResponse("ERROR : type of options.callback should be function and its Mandatory");
}
var nlpToken = options.nlpToken;
if (!nlpToken) {
if (!botcontext.simpledb.botleveldata.config || !botcontext.simpledb.botleveldata.config.nlpToken) {
return botcontext.sendResponse("ERROR : token not set. Please set Api.ai Token to options.nlpToken or context.simpledb.botleveldata.config.nlpToken");
} else {
nlpToken = botcontext.simpledb.botleveldata.config.nlpToken;
}
}
var query = '?v=20150910&query='+ encodeURIComponent(message) +'&sessionId='+context.simpledb.roomleveldata.session+'&timezone=Asia/Calcutta&lang=en '
var apiurl = "https://api.api.ai/api/query"+query;
var headers = { "Authorization": "Bearer " + nlpToken};
botcontext.simplehttp.makeGet(apiurl, headers, function(context, event) {
if (event.getresp) {
callback(event.getresp);
} else {
callback({})
}
});
}
I have started off with MS bot Framework and linked with api.ai. Below is my code:
var builder = require('botbuilder');
var restify = require('restify');
var apiairecognizer = require('api-ai-recognizer');
var request = require('request');
//=========================================================
// Bot Setup
//=========================================================
// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
console.log('%s listening to %s', server.name, server.url);
});
// Create chat bot
var connector = new builder.ChatConnector({
appId: "8c9f2d7b-dfa6-4116-ac45-po34eeb1d25c",
appPassword: "7CCO8vBGtdcTr9PoiUVy98tO"
});
server.post('/api/messages', connector.listen());
var bot = new builder.UniversalBot(connector);
var recognizer = new apiairecognizer("74c04b2c16284c738a8dbcf6bb343f");
var intents = new builder.IntentDialog({
recognizers: [recognizer]
});
bot.dialog('/',intents);
intents.matches('Flow_1',function(session, args){
var fulfillment = builder.EntityRecognizer.findEntity(args.entities, 'fulfillment');
if (fulfillment){
var speech = fulfillment.entity;
session.send(speech);
console.log("Inside fulfillment");
}else{
session.send('Sorry...not sure how to respond to that');
}
});
intents.matches('Intro',function(session, args){
var fulfillment = builder.EntityRecognizer.findEntity(args.entities, 'fulfillment');
if (fulfillment){
var speech = fulfillment.entity;
session.send(speech);
}else{
session.send('Sorry...not sure how to respond to that');
}
});
intents.matches('Default Fallback Intent',function(session, args){
var fulfillment = builder.EntityRecognizer.findEntity(args.entities, 'fulfillment');
if (fulfillment){
var speech = fulfillment.entity;
session.send(speech);
}else{
session.send('Sorry...not sure how to respond to that');
}
});
Now here is what I want to achieve:
JSON.parse(res).result.parameters.Ent_1 was a easy of parsing and getting the paramerters. How can I achieve something similar to that in Bot Framework? Do I have to construct a function sendMessageToApiAi() or is there a different way to achieve in MS Bot Framework?

Actually, Gupshup's template doesn't care about the intent which is sending the response. The template just gets the response from the API call and allows you to parse the response as desired.
Now in MSbot framework, if you want to get the value of Ent_1 then you can use the below sample code considering Flow_1 is the intent which will contain the entity Ent_1
intents.matches('Flow_1',function(session, args){
var fulfillment = builder.EntityRecognizer.findEntity(args.entities, 'Ent_1');
if (fulfillment){
var speech = fulfillment.entity;
session.send(speech);
console.log("Inside fulfillment");
}else{
session.send('Sorry...not sure how to respond to that');
}
});
You can also go through this blog which will help.

Related

LUIS is not recognizing the intent when I try the same input second time

I am using LUIS for azure search and QnA recognizer for the first time together in the same code.
I am using intent.match to match the recognizers.
My problem is:
for the first time, If I ask a question which matches to the QnA intent, it returns the answer from the QnA base.
followed by the questions which are matching with the azuresearch intents. Azure search also deliver the result.
But if I repeat the questions which must be matched to the QnA, it says "no intent handler found for null"
var util = require('util');
var _ = require('lodash');
var builder = require('botbuilder');
var restify = require('restify');
var cognitiveservices = require('botbuilder-cognitiveservices');
/// <reference path="../SearchDialogLibrary/index.d.ts" />
var SearchLibrary = require('../SearchDialogLibrary');
var AzureSearch = require('../SearchProviders/azure-search');
var qintent;
// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
console.log('%s listening to %s', server.name, server.url);
});
// Create chat bot and listen for messages
var connector = new builder.ChatConnector({
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
});
server.post('/api/messages', connector.listen());
// Bot Storage: Here we register the state storage for your bot.
// Default store: volatile in-memory store - Only for prototyping!
// We provide adapters for Azure Table, CosmosDb, SQL Azure, or you can implement your own!
// For samples and documentation, see: https://github.com/Microsoft/BotBuilder-Azure
var inMemoryStorage = new builder.MemoryBotStorage();
var qnarecognizer = new cognitiveservices.QnAMakerRecognizer({
knowledgeBaseId: '6b30ac2a-5ce9-4576-a1cb-c89abfb73889',
authKey: 'd457c897-71cf-46bf-a3d9-9a1f51246fad',
endpointHostName: 'https://qnadispatchwg.azurewebsites.net/qnamaker'
});
const LuisModelUrl = 'https://westeurope.api.cognitive.microsoft.com/luis/v2.0/apps/180a9aaa-9d67-4d40-b3d3-121917f4dbb8?subscription-key=39155bb750dc4b2abd84d410d80fce21&timezoneOffset=0&q=';
var recognizer = new builder.LuisRecognizer(LuisModelUrl);
// Bot with main dialog that triggers search and display its results
var bot = new builder.UniversalBot(connector);
bot.set('storage', inMemoryStorage);
var intents = new builder.IntentDialog({ recognizers: [recognizer, qnarecognizer] });
bot.dialog('/', intents);
intents.matches('Greeting', (session) => {
session.send('You reached the Greeting intent. You said \'%s\'.', session.message.text);
session.endDialog();
});
intents.matches('qna', [
function (session, args) {
console.log("my args in qna:=========================================================================================== %o", args);
var answerEntity = builder.EntityRecognizer.findEntity(args.entities, 'answer');
session.send(answerEntity.entity);
}
]);
intents.matches('Search.Aco', [
function (session, args, next) {
console.log("my args in SearchDialog:=========================================================================================== %o", args);
var intent = args.intent;
console.log("our intent is " + intent);
var entities = builder.EntityRecognizer.findEntity(args.entities, 'businessterm');
console.log("recognnized entitiy is: "+ entities.entity);
// qintent = title;
//session.send('You reached the Search.Aco intent. You enquire for the entitiy \'%s\'.', qintent);
//console.log(" SearchDialog: before updating the session.message.text------------->" + session.message.text);
session.message.text= entities.entity;
//console.log(" SearchDialog: after updating the session.message.text------------->" + session.message.text);
SearchLibrary.begin(session);
},
function (session, args, results) {
// Process selected search results
session.send(
'Done! For future reference, you selected these properties: %s',
args.selection.map(function (i) { return i.key; }).join(', '));
}
]);
var azureSearchClient = AzureSearch.create('aco-intel2', '4105C6676D0CDD9B2E7891952B9E9E00', 'azureblob-index');
var jobsResultsMapper = SearchLibrary.defaultResultsMapper(jobToSearchHit);
// Register Search Dialogs Library with bot
bot.library(SearchLibrary.create({
multipleSelection: true,
search: function (query) { return azureSearchClient.search(query).then(jobsResultsMapper); },
refiners: ['people', 'content', 'location']
}));
// Maps the AzureSearch Job Document into a SearchHit that the Search Library can use
function jobToSearchHit(acosearch) {
console.log("inside jobToSearchHit");
console.log("inside acosearch.DocUrl" + acosearch.DocUrl + "-------" + acosearch.metadata_storage_name);
return {
key: acosearch.id,
title: acosearch.metadata_storage_name,
description: acosearch.content.substring(0, 100)+"...",
documenturl:acosearch.DocUrl,
imageUrl: acosearch.imageurl
};
}
module.exports = { qintent: "qintent"};
for the second time if I am mixing the
This is how it is showing:
Emulator output
can you please help me to understand what is the difference between the intent.match and dialog' matches. I tried the following way too but it does not recognize the answering entity. How can be QnA be called from the following code?
var util = require('util');
var _ = require('lodash');
var builder = require('botbuilder');
var restify = require('restify');
var cognitiveservices = require('botbuilder-cognitiveservices');
/// <reference path="../SearchDialogLibrary/index.d.ts" />
var SearchLibrary = require('../SearchDialogLibrary');
var AzureSearch = require('../SearchProviders/azure-search');
var qintent;
// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
console.log('%s listening to %s', server.name, server.url);
});
// Create chat bot and listen for messages
var connector = new builder.ChatConnector({
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
});
server.post('/api/messages', connector.listen());
// Bot Storage: Here we register the state storage for your bot.
// Default store: volatile in-memory store - Only for prototyping!
// We provide adapters for Azure Table, CosmosDb, SQL Azure, or you can implement your own!
// For samples and documentation, see: https://github.com/Microsoft/BotBuilder-Azure
var inMemoryStorage = new builder.MemoryBotStorage();
var qnarecognizer = new cognitiveservices.QnAMakerRecognizer({
knowledgeBaseId: '6b30ac2a-5ce9-4576-a1cb-c89abfb73889',
authKey: 'd457c897-71cf-46bf-a3d9-9a1f51246fad',
endpointHostName: 'https://qnadispatchwg.azurewebsites.net/qnamaker'
});
const LuisModelUrl = 'https://westeurope.api.cognitive.microsoft.com/luis/v2.0/apps/180a9aaa-9d67-4d40-b3d3-121917f4dbb8?subscription-key=39155bb750dc4b2abd84d410d80fce21&timezoneOffset=0&q=';
var recognizer = new builder.LuisRecognizer(LuisModelUrl);
// Bot with main dialog that triggers search and display its results
var bot = new builder.UniversalBot(connector, function (session, args) {
session.send('You reached the default message handler. You said \'%s\'.', session.message.text);
}).set('storage', inMemoryStorage);
bot.recognizer(recognizer, qnarecognizer);
bot.dialog('GreetingDialog',
(session) => {
session.send('You reached the Greeting intent. You said \'%s\'.', session.message.text);
session.endDialog();
}
).triggerAction({
matches: 'Greeting'
})
bot.dialog('HelpDialog',
(session) => {
session.send('You reached the Help intent. You said \'%s\'.', session.message.text);
session.endDialog();
}
).triggerAction({
matches: 'Help'
})
bot.dialog('CancelDialog',
(session) => {
session.send('You reached the Cancel intent. You said \'%s\'.', session.message.text);
session.endDialog();
}
).triggerAction({
matches: 'Cancel'
})
bot.dialog('QnADialog',[
function (session, args, next) {
var answerEntity = builder.EntityRecognizer.findEntity(args.entities, 'answer');
console.log(answerEntity);
session.send(answerEntity.entity);
}
]).triggerAction({
matches: 'Search.QnA'
})
bot.dialog('SearchDialog', [
function (session, args) {
console.log("my args in SearchDialog:=========================================================================================== %o", args);
var intent = args.intent;
title = builder.EntityRecognizer.findEntity(intent.entities, 'businessterm');
console.log(title.entity);
qintent = title.entity;
//session.send('You reached the Search.Aco intent. You enquire for the entitiy \'%s\'.', qintent);
//console.log(" SearchDialog: before updating the session.message.text------------->" + session.message.text);
session.message.text= qintent;
//console.log(" SearchDialog: after updating the session.message.text------------->" + session.message.text);
SearchLibrary.begin(session);
},
function (session, args, results) {
// Process selected search results
session.send(
'Done! For future reference, you selected these properties: %s',
args.selection.map(function (i) { return i.key; }).join(', '));
}
]).triggerAction({
matches: 'Search.Aco'
});
var azureSearchClient = AzureSearch.create('aco-intel2', '4105C6676D0CDD9B2E7891952B9E9E00', 'azureblob-index');
var jobsResultsMapper = SearchLibrary.defaultResultsMapper(jobToSearchHit);
// Register Search Dialogs Library with bot
bot.library(SearchLibrary.create({
multipleSelection: true,
search: function (query) { return azureSearchClient.search(query).then(jobsResultsMapper); },
refiners: ['people', 'content', 'location']
}));
// Maps the AzureSearch Job Document into a SearchHit that the Search Library can use
function jobToSearchHit(acosearch) {
console.log("inside jobToSearchHit");
console.log("inside acosearch.DocUrl" + acosearch.DocUrl + "-------" + acosearch.metadata_storage_name);
return {
key: acosearch.id,
title: acosearch.metadata_storage_name,
description: acosearch.content.substring(0, 100)+"...",
documenturl:acosearch.DocUrl,
imageUrl: acosearch.imageurl
};
}
module.exports = { qintent: "qintent"};
This code gives me traces like following:
logs
Please help me understand whats wrong in the above codes. Would be a great help.
Also, the difference between the intent.match and dialogs match. As per my understanding, bot recognizes the session.message and match it with the 'match:' argurments and call the dialogs. so it can jump to and fro between dialogs.
in the first case, its strange for me because it doesnot do it second time.
Thanks in Advance,
Vivek
Using the IntentDialog class (e.g. var intents = new builder.IntentDialog( ... ) does not allow for intent interruption in the way that registering the dialog directly to the bot does.
Given the following example dialogs:
const bot = new UniversalBot(connector); // Assume an already created ChatConnector
bot.recognizer(new LuisRecognizer(LuisModelUrl); // LUIS model with HowAreYou and Weather intents.
bot.dialog('HowAreYou',[
function (session, args, next) {
builder.Prompts.text(session, `I\'m doing great! How are you?`);
},
function (session, args) {
var result = session.message.text;
session.send(`I\'m glad to hear you\'re doing ${result}!`);
]).triggerAction({
matches: 'HowAreYou'
});
bot.dialog('Weather', [
function (session, args) {
builder.Prompts.text(session `Which city do you want to find the weather forecast for?`);
},
function (session, args) {
// some logic to handle the response and call a weather api
}
]).triggerAction({
matches: 'Weather'
});
When a user asks "How are you?" the 'HowAreYou' intent is detected by LUIS and the bot begins the corresponding dialog. The bot then prompts the user "I'm doing great! How are you?'. If the user then says to the bot, "Show me the weather", this bot will receive that message and detect the "Weather" intent. After doing so, it will add the "Weather" dialog to the top of the dialog stack and begin the Weather dialog.
The UniversalBot doesn't prevent you from switching dialogs when a new intent is detected. In the code for the intent dialog, it continues any dialog you were already in and doesn't support switching to a new intent/dialog.

Intergrating Bing Web Search API with QnA chat bot

I am making a chat bot using the QnA template (Microsoft Azure). Basically, a user asks a question and the bot will try and find the answer in an FAQ document. If it fails, I want it to run a Bing search with the user's query and replies with the most accurate answer. I found this example that uses Bing Web Search API: https://learn.microsoft.com/en-us/azure/cognitive-services/bing-web-search/quickstarts/nodejs. For now, I just want the bot to reply with the first link of the search for example.
However, I don't know how to merge the code in the link, with the generated code for the QnA Bot (in Node.js):
var restify = require('restify');
var builder = require('botbuilder');
var botbuilder_azure = require("botbuilder-azure");
var builder_cognitiveservices = require("botbuilder-cognitiveservices");
var request = require('request');
// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
console.log('%s listening to %s', server.name, server.url);
});
// Create chat connector for communicating with the Bot Framework Service
var connector = new builder.ChatConnector({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword,
openIdMetadata: process.env.BotOpenIdMetadata
});
// Listen for messages from users
server.post('/api/messages', connector.listen());
var tableName = 'botdata';
var azureTableClient = new botbuilder_azure.AzureTableClient(tableName, process.env['AzureWebJobsStorage']);
var tableStorage = new botbuilder_azure.AzureBotStorage({ gzipData: false }, azureTableClient);
// Create your bot with a function to receive messages from the user
var bot = new builder.UniversalBot(connector);
bot.set('storage', tableStorage);
// Recognizer and and Dialog for GA QnAMaker service
var recognizer = new builder_cognitiveservices.QnAMakerRecognizer({
knowledgeBaseId: process.env.QnAKnowledgebaseId,
authKey: process.env.QnAAuthKey || process.env.QnASubscriptionKey, // Backward compatibility with QnAMaker (Preview)
endpointHostName: process.env.QnAEndpointHostName
});
var basicQnAMakerDialog = new builder_cognitiveservices.QnAMakerDialog({
recognizers: [recognizer],
defaultMessage: 'Sorry, I cannot find anything on that topic',
qnaThreshold: 0.3
});
// Override the invokeAnswer function from QnAMakerDialog
builder_cognitiveservices.QnAMakerDialog.prototype.invokeAnswer = function (session, recognizeResult, threshold, noMatchMessage) {
var qnaMakerResult = recognizeResult;
session.privateConversationData.qnaFeedbackUserQuestion = session.message.text;
if (qnaMakerResult.score >= threshold && qnaMakerResult.answers.length > 0) {
if (this.isConfidentAnswer(qnaMakerResult) || this.qnaMakerTools == null) {
this.respondFromQnAMakerResult(session, qnaMakerResult);
this.defaultWaitNextMessage(session, qnaMakerResult);
}
else {
this.qnaFeedbackStep(session, qnaMakerResult);
}
}
else {
this.noMatch(session, noMatchMessage, qnaMakerResult);
}
};
// API call to Bing
basicQnAMakerDialog.noMatch = function (session, noMatchMessage, qnaMakerResult) {
var term = session.message.text;
var key = 'i hid it';
var options = {
url: "https://api.cognitive.microsoft.com/bing/v7.0/search?q=" + term,
method: 'GET',
headers : {
'Ocp-Apim-Subscription-Key' : key
}
};
request(options, function(err,res, body){
if(err){
console.error(err);
session.send(noMatchMessage);
}
body = JSON.parse(body);
session.send("I found a thing: " + body["webPages"]["value"][0]["name"]);
});
};
bot.dialog('basicQnAMakerDialog', basicQnAMakerDialog);
bot.dialog('/', //basicQnAMakerDialog);
[
function (session, results) {
session.replaceDialog('basicQnAMakerDialog');
},
]);
In the function inside the bot.dialog, I think I should add a condition such as: if the bot returns the default message, open a web page and let the "term" to search be the user's last message. However, I don't know how to code this exactly, and where to do so. Also, I don't know how to exit from the replaceDialog function in order to reply with something other than the default message or the answers in the FAQ.
PS: I don't have much experience with javascript or web development in general. Any help will be appreciated :)
What you are implementing involves two steps, extracting out the "noMatch" case to a method so you can make a full response message, and then the API call itself.
The "noMatch" method.
First, you will want to override the invokeAnswer function from QnAMakerDialog (This override changes nothing but adding a call to a separate method for the noMatch case.)
builder_cognitiveservices.QnAMakerDialog.prototype.invokeAnswer = function (session, recognizeResult, threshold, noMatchMessage) {
var qnaMakerResult = recognizeResult;
session.privateConversationData.qnaFeedbackUserQuestion = session.message.text;
if (qnaMakerResult.score >= threshold && qnaMakerResult.answers.length > 0) {
if (this.isConfidentAnswer(qnaMakerResult) || this.qnaMakerTools == null) {
this.respondFromQnAMakerResult(session, qnaMakerResult);
this.defaultWaitNextMessage(session, qnaMakerResult);
}
else {
this.qnaFeedbackStep(session, qnaMakerResult);
}
}
else {
this.noMatch(session, noMatchMessage, qnaMakerResult);
}
};
After this you will define your basicQnAMakerDialog as usual, but consider making the default message field something for an error case or "no results found". (This doesn't really matter, as you can arbitrarily decide what you send back within the noMatch method, but it's nice to have the default there)
var basicQnAMakerDialog = new builder_cognitiveservices.QnAMakerDialog({
recognizers: [recognizer],
defaultMessage: 'Sorry, I can't find anything on that topic'
qnaThreshold: 0.3
});
After this you can define the noMatch method seperately.
basicQnAMakerDialog.noMatch = function (session, noMatchMessage, qnaMakerResult) {
session.send(noMatchMessage); //Contains the default message
this.defaultWaitNextMessage(session, qnaMakerResult);
}
Within this method we can call the API or do basically whatever we want.
Credit to gliddell on this override
The API call.
Within the noMatch method, we will make an API call to Bing
basicQnAMakerDialog.noMatch = function (session, noMatchMessage, qnaMakerResult) {
var term = session.message.text;
var key = <Environment Var containing the Bing key>;
var options = {
url: "https://api.cognitive.microsoft.com/bing/v7.0/search?q=" + term,
method: 'GET',
headers : {
'Ocp-Apim-Subscription-Key' : key
}
};
request(options, function(err,res, body){
if(err){
console.error(err);
session.send(noMatchMessage);
}
body = JSON.parse(body);
session.send("I found a thing: " + body["webPages"]["value"][0]["name"]);
});
};
You can customize the message you are sending back as much as you like after you get the results from the search.

Bot sending the default response instead of customAction

I'm testing out the idea of writing a bot to interact with our teams chat tool and our ticketing system. I have a basic bot that will go fetch the status of a task record in the system and respond back. I set that call up as a customAction. The issue I'm running into is the bot will work the first time you interact with it, but the second time it sends the default message instead. Any ideas what I have wrong?
var restify = require("restify");
var builder = require("botbuilder");
var getSnStatus = require("./getSnStatus");
var botAppInfo = require("./botStuffs.json");
//setup the Restify server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log('listening to %s', server.name, server.url);
});
//create chat connector using the ms bot framework
var connector = new builder.ChatConnector({
appId: botAppInfo.AppID,
appPassword: botAppInfo.password
});
//listen for messages from users
server.post('/api/messages', connector.listen());
// setup bot and Register in-memory storage
var inMemoryStorage = new builder.MemoryBotStorage();
var bot = new builder.UniversalBot(connector, [
function(session){
session.send("Hello, If you want to check the status of something just ask for the status or state with a number.");
}
]).set('storage', inMemoryStorage);
//look for the state or status word and run a state check if that's found
//anywhere in the text sent by the user.
//TODO handle a response with no results.
bot.customAction({
matches: /status|state/gi,
onSelectAction(session, args, next) {
words = session.message.text.split(" ");
for (i = 0; i < words.length; i++) {
if (words[i].match(/\d+/)) {
console.log("Run using the word: " + words[i]);
getSnStatus(words[i], function(state) {
state.result.forEach(function(element) {
session.send("The state of " + element.number +
" is: " + element.state);
});
session.endDialog();
})
}
}
}
})
The output I get is like this:
call:state INC1671479
response:The state of INC1671479 is: Open
call:state INC1671479
response: Hello, If you want to check the status of something just ask for the status or state with a number.
I was hoping that it would instead fire my custom action again on the second call.
Here is the working code after the issue was identified.
var restify = require("restify");
var builder = require("botbuilder");
var getSnStatus = require("./getSnStatus");
var botAppInfo = require("./botStuffs.json");
//setup the Restify server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log('listening to %s', server.name, server.url);
});
//create chat connector using the ms bot framework
var connector = new builder.ChatConnector({
appId: botAppInfo.AppID,
appPassword: botAppInfo.password
});
//listen for messages from users
server.post('/api/messages', connector.listen());
// setup bot and Register in-memory storage
var inMemoryStorage = new builder.MemoryBotStorage();
var bot = new builder.UniversalBot(connector, []).set('storage', inMemoryStorage);
bot.dialog("status", (session, args, next)=>{
words = session.message.text.split(" ");
for (i = 0; i < words.length; i++) {
if (words[i].match(/\d+/)) {
console.log("Run using the word: " + words[i]);
getSnStatus(words[i], function(state) {
state.result.forEach(function(element) {
session.send("The state of " + element.number +
" is: " + element.state);
});
})
}
}
}).triggerAction({matches: /status|state/gi});
The issue I'm running into is the bot will work the first time you interact with it, but the second time it sends the default message instead. Any ideas what I have wrong?
I think this behavior is by design, according to the official document: Bind a customAction, once the action is completed, control is passed back to the dialog that is at the top of the stack and the bot can continue.
So in your code, after the first time your customAction is completed, it will go to the root dialog by default, when the second time user input a "state", this dialog will handle the user's input.
The use of customAction is to perform for some quick action requests without manipulate the dialog stack. So for your scenario, I think it's better to Bind a triggerAction for example like this:
bot.dialog('status', (session, args, next)=>{
var message = session.message.text;
//your work to get status goes here.
}).triggerAction({matches: /status|state/gi});

Bot Response based on user session using botframework (NodeJS)

How to send bot response based on user session in NodeJS.
I have created a sample NodeJS application using botframework and connecting to IBM watson to process the user query to get the appropriate response and sending back the response with 10secs delay.
I have generate the ngrok URL and configured it bot framework for web channel. When i test this in mutipl browsers simulataneously , i am getting the same response for all , not based on the user session and user request.
It's giving the latest request processed response to everyuser. I have copied the sample nodejs code below.
Can someone please look into my code and suggest me on how to send the response based on user session/conversationID?
NodeJS sample code :
'use strict';
var restify = require('restify');
var builder = require('botbuilder');
var Conversation = require('watson-developer-cloud/conversation/v1');
require('dotenv').config({silent: true});
var server = restify.createServer();
var contexts = { CandidateID : "89798" , Location : "USA" }
var workspace= 'XXXXXXXXXXXXXXXX';
let name,id,message,addr,watson;
var savedAddress;
server.listen(process.env.port || process.env.PORT || 3000, function () {
console.log('%s listening to %s', server.name, server.url);
});
// Create the service wrapper
var conversation = new Conversation({
username: 'XXXXXXXXXXXXXXXX',
password: 'XXXXXXXXXXXXXXXX',
url: 'https://gateway.watsonplatform.net/conversation/api',
version_date: 'XXXXXXXXXXXXXXXX'
});
// setup bot credentials
var connector = new builder.ChatConnector({
appId: 'XXXXXXXXXXXXXXXX',
appPassword: 'XXXXXXXXXXXXXXXX'
});
var bot = new builder.UniversalBot(connector,);
server.post('/api/messages', connector.listen());
// root dialog
bot.dialog('/', function (session, args) {
name = session.message.user.name;
id = session.message.user.id;
message = session.message.text;
savedAddress = session.message.address;
var payload = {
workspace_id: workspace,
context: contexts,
input: {
text: session.message.text
}
};
var conversationContext = {
workspaceId: workspace,
watsonContext: contexts
};
if (!conversationContext) {
conversationContext = {};
}
payload.context = conversationContext.watsonContext;
conversation.message(payload, function(err, response) {
if (err) {
console.log(err);
session.send(err);
} else {
console.log(JSON.stringify(response, null, 2));
conversationContext.watsonContext = response.context;
watson = response.output.text;
}
});
console.log(message);
setTimeout(() => {
startProactiveDialog(savedAddress);
}, 10000);
});
// handle the proactive initiated dialog
bot.dialog('/survey', function (session, args, next) {
if (session.message.text === "done") {
session.send("Great, back to the original conversation");
session.endDialog();
} else {
session.send(id + ' ' + watson);
}
});
// initiate a dialog proactively
function startProactiveDialog(address) {
bot.beginDialog(address, "*:/survey");
}
You need to make sure it passes back and forth all of the system context for that user's session. Our botkit middleware essentially does this for you.
https://github.com/watson-developer-cloud/botkit-middleware
It looks like that you are leveraging the sample at https://github.com/Microsoft/BotBuilder-Samples/tree/master/Node/core-proactiveMessages/startNewDialog Sending a dialog-based proactive message to specific user.
The sample is just for reference, it only saves the latest conversion at it only declare one variable savedAddress, each time the new user comes in, the new seession address will assign to savedAddress and cover the old value.
You can try to define an object to save user addresses for quick test:
var savedAddress = {};
bot.dialog('/', function (session, args) {
savedAddress[session.message.address.id] = session.message.address;
var message = 'Hey there, I\'m going to interrupt our conversation and start a survey in a few seconds.';
session.send(message);
message = 'You can also make me send a message by accessing: ';
message += 'http://localhost:' + server.address().port + '/api/CustomWebApi';
session.send(message);
setTimeout(() => {
startProactiveDialog(savedAddress[session.message.address.id]);
}, 5000);
});
For production stage, you can leverage Redis or some other cache storage to store this address list.
Hope it helps.

Isolate BotFramework NodeJS Bot with LUIS into own library

I want to isolate my bot, which uses BotFramework and LUIS, into it's own library and import it into the app.js. I followed the tutorials and examples on the BotFramework GitHub but it's getting me nowhere. Once I put the bot with LUIS dialogues into it's own file and export it, it never reaches out to LUIS:
var builder = require('botbuilder');
//Import our libraries
var profileDialogue = require('../dialogues/profileDialogue');
//=========================================================
// Bot Setup
//=========================================================
// Create chat bot
var bot = new builder.UniversalBot(null, null, 'changeName');
// Add locale tools library to bot
bot.library(profileDialogue.createLibrary());
// Export createLibrary() function
exports.createLibrary = function() {
return bot.clone();
}
var model = URL;
var recognizer = new builder.LuisRecognizer(model);
var dialog = new builder.IntentDialog({ recognizers: [recognizer] });
//=========================================================
// Bots Dialogs
//=========================================================
bot.dialog('/changeName', dialog);
bot.dialog('change name', [
function(session, args, next) {
console.log(args);
if (args.score > 0.5) {
profileDialogue.profile(session);
}
},
function(session, results) {
session.send('Ok... Changed your name to %s', session.userData.name);
}
]);
This code only works if it exists in the app.js file that node calls on, never when I want to isolate it for use in other bots.
Here is my app.js:
var restify = require('restify');
var builder = require('botbuilder');
//Import our libraries
var changeName = require('./bots/changeName');
//=========================================================
// Bot Setup
//=========================================================
// Setup Restify Server
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log('%s listening to %s', server.name, server.url);
});
// Create chat bot
var connector = new builder.ChatConnector({
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
});
var bot = new builder.UniversalBot(connector);
server.post('/api/messages', connector.listen());
//=========================================================
// Bots Dialogs
//=========================================================
bot.dialog('/', [function(session, args, next) { session.send("I don't understand") }]);
// Add locale tools library to bot
bot.library(changeName.createLibrary());
How can I achieve this properly? Am I not thinking about this the right way?
UPDATE
I was able to isolate the LUIS bot by using a different syntax (bot.dialog teamed up with triggerAction):
bot.dialog('/changeName', [
function(session, args, next) {
if (args && args.intent && args.intent.score && args.intent.score > 0.5) {
console.log(args);
profileDialog.profile(session);
}
},
function(session, results) {
session.send('Ok... Changed your name to %s', session.userData.name);
}
]).triggerAction({
matches: 'change name',
intentThreshold: .50
});
The last remaining issue that I have is that the parent app.js NEEDS to have the LUIS endpoint, it doesn't matter if my child bot has it or not. Any additional ideas?
Using this code will isolate the bot and allow it to be called into an app.js file, using LUIS:
bot.dialog('/changeName', [
function(session, args, next) {
if (args && args.intent && args.intent.score && args.intent.score > 0.5) {
console.log(args);
profileDialog.profile(session);
}
},
function(session, results) {
session.send('Ok... Changed your name to %s', session.userData.name);
}
]).triggerAction({
matches: 'change name',
intentThreshold: .50
});

Resources