Use multiple QnA services in one bot - node.js

I have multiple QnA services running, each with it's own knowledgeBaseId and subscriptionKey. I want to use them both in a single chatbot and I've written a piece of code which takes the user input and assigns the correct Knowledgebase details. However, I'm unable to get the 2nd QnA service to become active and the bot is linking only to the first service. What might be going wrong here?
Sample code:
var knowledgeId = "FIRST_QNA_SERVICE_KNOWLEDGE_ID";
var subscriptionId = "FIRST_QNA_SERVICE_SUBSCRIPTION_ID";
var bot = new builder.UniversalBot(connector);
var qnarecognizer = new cognitiveservices.QnAMakerRecognizer({
knowledgeBaseId: knowledgeId,
subscriptionKey: subscriptionId,
qnaThreshold:0.3,
top:1});
var intentrecognizer = new builder.IntentDialog();
var intents = new builder.IntentDialog({ recognizers: [intentrecognizer, qnarecognizer] });
bot.dialog('/', intents);
intents.matches('qna', [
function (session, args, next) {
args.entities.forEach(function(element) {
session.send(element.entity);
}, this);
}
]);
intents.matchesAny([/hi/i, /main menu/i], [
function (session) {
builder.Prompts.choice(session, "Hi, What would you like to ask me about?", ["Service1","Service2"],{ listStyle: builder.ListStyle.button});
},
function (session, result) {
var selection = result.response.entity;
switch (selection) {
case "Service1":
knowledgeId = "FIRST_QNA_SERVICE_KNOWLEDGE_ID";
subscriptionId = "FIRST_QNA_SERVICE_SUBSCRIPTION_ID";
session.send('You can now ask me anything about '+selection+'. Type \'main menu\' anytime to choose a different topic.')
session.endConversation();
return
case "Service2":
knowledgeId = "SECOND_QNA_SERVICE_KNOWLEDGE_ID";
subscriptionId = "SECOND_QNA_SERVICE_SUBSCRIPTION_ID";
session.send('You can now ask me anything about '+selection+'. Type \'main menu\' anytime to choose a different topic.')
session.endConversation();
return
}
}
])

You never updated the knowledgeId/subscriptionId on the QnAMakerRecognizer... that's the reason you are always seeing the first QnA service being called.
See if there is a way to update those values in the QnAMakerRecognizer.

Check out if my answer is valid to your purpose on this question:
Multiple QnA Maker services for a single bot
Hope you find it useful.

Related

How to tell Alexa to jump to a specific intent from LaunchRequest based on user input

I am quite new in Alexa development so please excuse my ignorance. The Alexa skill I am developing requires the following:
Users will awake the skill along with a question, e.g.
Alexa, ask marketing platform about result of last campaign
I am referring to https://developer.amazon.com/docs/custom-skills/understanding-how-users-invoke-custom-skills.html#cert-invoke-specific-request but not quite understand how to jump to a specific intent from LaunchRequest.
Where marketing platform is the skill invocation and result of last campaign is the utterance for skill intent named CampaignIntent.
There are more intents like this, which I want to call based on user's question, e.g.
Alexa, ask marketing platform to give me messaging details
I am using Lambda for the skill. At the moment it looks like the following:
exports.handler = (event, context, callback) => {
try {
if (event.request.type === 'LaunchRequest') {
var welcomeMessage = '<speak>';
welcomeMessage = welcomeMessage + 'Welcome to XYZ agency.';
welcomeMessage = welcomeMessage + '</speak>';
callback(null, buildResponse(welcomeMessage, false));
//How can I tell Alexa to jump to CampaignIntent?
}
else if (event.request.type === 'IntentRequest') {
const intentName = event.request.intent.name;
if (intentName === 'CampaignIntent') {
var ssmlConfirm = "<speak>";
ssmlConfirm = ssmlConfirm + 'Hello Auto.';
ssmlConfirm = ssmlConfirm + "</speak>";
callback(null, buildResponse(ssmlConfirm, true));
}
}
}
catch (e) {
context.fail(`Exception: ${e}`);
}
};
function buildResponse(response, shouldEndSession) {
return {
version: '1.0',
response: {
outputSpeech: {
type: 'SSML',
ssml: response,
},
shouldEndSession: shouldEndSession,
},
sessionAttributes: {},
};
}
CampaignIntent does not have any slot. It simply fetches records from a third party platform API.
I also referred https://stackoverflow.com/a/48032367/1496518 but did not understand how to achieve ...has a WHEN slot to elicit part.
The documentation you linked say, "Users can combine your invocation name with an action, command or question. This sends the service for your skill an IntentRequest with the specific intent that corresponds to the user's request."
If a user invokes your skill in this way, the intent you find in the first request of that user's session will be CampaignIntent (the IntentRequest you've defined) instead of LaunchRequest. There isn't any "jumping" you need to do on your end. The behavior will be the same with or without slot values.

disable qna recognizer when inside a dialog

I am using Luis and QnA maker, qna maker is now interupting a waterfall prompt. I have disabled the Luis prompt with code below, how can I to do same for the qna recognizer?
var recognizer = new
builder.LuisRecognizer(LuisModelUrl).onEnabled(function (context,
callback) {
var enabled = context.dialogStack().length == 0;
callback(null, enabled);
});
bot.recognizer(recognizer);
bot.recognizer(qnaRecognizer);
console.log(recognizer);
eg: What part of the toilet is broken? (1. Cistern, 2. Pipe, or 3. Seat)
Anything except an exact match gets picked up by qna sentiment which replaces the dialog stack
Thanks
You shouldn't have to disable either one for the code to work. I suspect the problem is in your dialog flow. Below is an example of how to construct the dialog portion of your bot. When I ran this, the logger middleware shows QnA is matching on the inputs I feed, but the bot is dictating the conversation because of the code.
var luisrecognizer = new builder.LuisRecognizer(LuisModelUrl);
var qnarecognizer = new cognitiveservices.QnAMakerRecognizer({
knowledgeBaseId: process.env.QnAKnowledgebaseId,
authKey: process.env.QnAAuthKey || process.env.QnASubscriptionKey,
endpointHostName: process.env.QnAEndpointHostName
});
var basicQnAMakerDialog = new cognitiveservices.QnAMakerDialog({
recognizers: [qnarecognizer],
defaultMessage: 'No match! Try changing the query terms!',
qnaThreshold: 0.3
});
bot.recognizer(luisrecognizer);
bot.recognizer(basicQnAMakerDialog);
bot.dialog('/', basicQnAMakerDialog);
bot.dialog('GreetingDialog',[
(session) => {
session.send('You reached the Greeting intent. You said \'%s\'.',
session.message.text);
builder.Prompts.text(session, "What is your name?");
},
(session, results) => {
session.userData.name = results.response;
session.send("Glad you could make it, " + session.userData.name);
builder.Prompts.text(session, "Ask me something!");
},
(session, results) => {
session.conversationData.question = results.response;
session.send(session.conversationData.question + " is an interesting topic!")
session.endDialog();
}
]).triggerAction({
matches: 'Greeting'
})
In the following image, LUIS brings me to the Greeting intent when I type "I'm happy [to be here]" which I have trained in the LUIS app. The bot dialog takes over asking me questions and storing the answers. Even though I'm making statements that QnA or LUIS should respond to neither is doing so. The conversation follows the code.
Had Qna taken over it would have responded to "What are you" with some text about QnA Maker. Similarly, "help" would have produced responses from either QnA or LUIS as I have topics/intents for both, respectively.

How to integrate LUIS and QnA Maker services in single Node.js bot?

I'm developing a chatbot using Microsoft Bot Framework with Node.js SDK. I've integrated LUIS and QnA maker but I want to create this scenario if it's possible. Taking in example the following link and in particular this section:
There are a few ways that a bot may implement a hybrid of LUIS and QnA Maker:
Call LUIS first, and if no intent meets a specific threshold score, i.e., "None" intent is triggered, then call QnA Maker. Alternatively, create a LUIS intent for QnA Maker, feeding your LUIS model with example QnA questions that map to "QnAIntent."
Just an example:
I have my QnA KB in which I have a pair : " who are you?" / "Hi I'm your bot! " . Then I have my Luis app that recognize this intent called "common" .
So, if I write to my bot: " who are you?" it will answer "Hi I'm your bot! "
Instead, if I write " tell me who you are" it recognize the LUIS intent related to the question but it will not answer "Hi I'm your bot! " like I imagine.
So what I imagine is: I ask the question "Tell me who you are" --> the bot triggers the intent common (LUIS) --> then I want that the bot will answer me looking into the QnA KB --> "Hi I'm your bot! "
Is it possible?
Hope this code could help:
var intents = new builder.IntentDialog({ recognizers[luisRecognizer,qnarecognizer] });
bot.dialog('/', intents);
intents.matches('common_question', [
function (session, args, next) {
session.send('Intent common');
qnarecognizer.recognize(session, function (error, result) {
session.send('answerEntity.entity');
});
}
]);
You will have to forward the user message to QnaMaker from the method/dialog associated to the intent detected by LUIS. Take a look to this article (https://blog.botframework.com/2017/11/17/qna-maker-node-js-bots/) to find how implement QnAMaker in Node.js
Something like:
var recognizer = new cognitiveservices.QnAMakerRecognizer({
knowledgeBaseId: 'set your kbid here',
subscriptionKey: 'set your subscription key here'});
var context = session.toRecognizeContext();
recognizer.recognize(context, function (error, result) { // your code... }
You should explore the samples also and try to understand how everything works: https://github.com/Microsoft/BotBuilder-CognitiveServices/tree/master/Node/samples/QnAMaker
If you vary a lot your question; could be possible that QnA won't detect the question you expected and in that case you will have to train your KB more (like you do in LUIS with the utterances/intents)
I wrote this because I want more practice with node and this was an excuse to use node, but what Ezequiel is telling you is completely correct. I'll also post on your GitHub issue. This is a functioning node app that does what you need
var builder = require('botbuilder');
var restify = require('restify');
var cog = require('botbuilder-cognitiveservices');
var server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log('%s listening to %s', server.name, server.url);
});
var connector = new builder.ChatConnector({
appId: "APP ID",
appPassword: "APP PASSWORD"
});
server.post('/api/messages', connector.listen());
var bot = new builder.UniversalBot(connector, function(session) {
session.send('Sorry, I did not understand \'%s\'. Type \'help\' if you need assistance.', session.message.text);
});
var recognizer = new builder.LuisRecognizer("https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/{LUIS APP ID}?subscription-key={LUIS KEY}&verbose=true&timezoneOffset=0&q=");
bot.recognizer(recognizer);
var qnaRecognizer = new cog.QnAMakerRecognizer({
knowledgeBaseId: 'QNA APP ID',
subscriptionKey: 'QNA SUBSCRIPTION KEY'
});
bot.dialog('Common', function(session) {
var query = session.message.text;
cog.QnAMakerRecognizer.recognize(query, 'https://westus.api.cognitive.microsoft.com/qnamaker/v2.0/knowledgebases/{QNA APP ID}}/generateAnswer', '{QNA SUBSCRIPTION KEY}', 1, 'intentName', (error, results) => {
session.send(results.answers[0].answer)
})
}).triggerAction({
matches: 'Common'
});
As of 2018 (BotBuilder V4)
You can now use the Dispatch Command Line tool to dispatch intent across multiple bot modules such as LUIS models and QnA.
So first you will get to LUIS app that will decide based on the score to redirect to another LUIS app or to a QnA.
Dispatch Tool
Example using LUIS as dispatch service

what does `'CreateNote'`represent in this section?

Referring to this link https://learn.microsoft.com/en-us/bot-framework/nodejs/bot-builder-nodejs-recognize-intent-luis
I took this code section :
// CreateNote dialog
bot.dialog('CreateNote', [
function (session, args, next) {
// Resolve and store any Note.Title entity passed from LUIS.
var intent = args.intent;
var title = builder.EntityRecognizer.findEntity(intent.entities, 'Note.Title');
var note = session.dialogData.note = {
title: title ? title.entity : null,
};
What i don't understand is what does 'CreateNote'represent in this section?
And referring to this line :
var title = builder.EntityRecognizer.findEntity(intent.entities, 'Note.Title');
Assuming my intent name is calendar.add and my entity name calendar.location
will the intent.entities calendar.add.calendar.location create any confusion.
That's the internal identifier of the dialog, and can be referenced where necessary.
Regarding the second part, I don't think it will create confusion, but if you'll get back to this code two weeks later, you will scratch your head thinking why is it named that way, so it's more of a logistics kind of thing, in my opinion.
Taken from the official https://learn.microsoft.com/en-us/bot-framework/nodejs/bot-builder-nodejs-dialog-manage-conversation-flow . The 'CreateNote' is an identifier of your dialog and it can be used like this:
var inMemoryStorage = new builder.MemoryBotStorage();
var bot = new builder.UniversalBot(connector,
function (session) {
session.send("Welcome");
session.beginDialog('perroDialog'); //Use beginDialog with the
//dialog identifier for starting perroDialog
}
).set('storage', inMemoryStorage); // Register in-memory storage
//--------------------------DIALOGS WATERFALL------------------------
bot.dialog('perroDialog',
function (session) {
session.send('You started perroDialog');
session.endDialog(); //Back to / dialog (UniversalBot callback)
});

Trigger a specific dialog in Bot via Directline API

I'm working on breaking my bot repo into 2 separate repos
A repo to purely handle bot logic
A repo to handle custom chat via directline
Currently , we have a feature where we can trigger the bot to start a specific dialog if its mentioned as a parameter in the URL. So something like
https://foo.com/?param=bar
would trigger the bar dialog
This is the code that handles it
function(userId, conversationId, params, token){
return new Promise((resolve, reject)=>{
var _directlineAddress = {
bot: {"id":config.BOT.ID, "name": config.BOT.HANDLE},
channelId: "directline",
serviceUrl: config.BOT.DIRECTLINE_URL,
useAuth: true,
user:{"id": userId},
"conversation": {"id": conversationId}
}
if(params.options){
var _re = /^\?(\w+)*=(\w+)*/
var _programType = _re.exec(params.options);
if (_programType[1] === "foo") {
var _dialogId = "*:/foo";
}
else {
var _dialogId = "*:/" + _programType[1];
}
} else {
var _dialogId = "*:/";
var _specialParams = {"sessionId":token};
}
bot.beginDialog(_directlineAddress, _dialogId, _specialParams, function(err){
else{
resolve();
}
});
})
};
Since i'm splitting the directline from the bot logic , i will no longer be having access to the bot object. therefore bot.beginDialog would not work here
Is there a way i can trigger the dialog by posting to the Directline API?
No. With Direct Line you will be able to send messages to the bot. I guess that a way to go here will be to define a convention message that you will send via Direct Line and that the bot logic will know that it will have to start a dialog based on it.

Resources