I have LUIS app created, with 1 intent(Order Pizza) and 9 entities(Order,Kind, Signature, Size, Toppings, address, Time, Duration, Range).
I need to create a bot in Azure Bot Framework. I'm unable to understand and use the waterfall for all the entities. Please let me know how to go about it
Code :
dialog.matches('OrderPizza', [
function (session, args, next) {
var order = builder.EntityRecognizer.findEntity(args.entities, 'order');
var kind = builder.EntityRecognizer.findEntity(args.entities, 'Kind');
session.dialogData.intentScore = args.score;
if (!order) {
builder.Prompts.text(session, 'welcome please order pizza');
} else {
session.dialogData.order = order;
next();
}
},
function (session, results) {
var order = session.dialogData.order;
if (results.response) {
session.send('what kind?');
}else{
session.send(`we don't have that kind of pizza`);
}
}
]);
How to go further for other entities?
I'm not sure what you mean by only being able to write 2 functions; but if you're trying to call LUIS inside of your dialog you can follow this example. You'll call a LuisRecognizer inside your waterfall step on session.message.text. The snippet from the example is below:
builder.LuisRecognizer.recognize(session.message.text, '<model url>', function (err, intents, entities) {
if (entities) {
var entity = builder.EntityRecognizer.findEntity(entities, 'TYPE');
// do something with entity...
}
});
This would allow you to call LUIS inside a waterfall for recognizing your user's messages.
Regarding your code there is a number of issues:
// Original Code
dialog.matches('OrderPizza', [
function (session, args, next) {
var order = builder.EntityRecognizer.findEntity(args.entities, 'order');
var kind = builder.EntityRecognizer.findEntity(args.entities, 'Kind');
session.dialogData.intentScore = args.score;
if (!order) { // What about kind?
builder.Prompts.text(session, 'welcome please order pizza');
} else {
session.dialogData.order = order; // What about kind?
next();
}
},
function (session, results) {
var order = session.dialogData.order;
if (results.response) {
session.send('what kind?');
}else{
session.send(`we don't have that kind of pizza`);
}
}
]);
In your first waterfall step you're not saving the "Kind" entity to your dialogData.
function (session, args, next) {
var order = builder.EntityRecognizer.findEntity(args.entities, 'order');
var kind = builder.EntityRecognizer.findEntity(args.entities, 'Kind');
session.dialogData.intentScore = args.score;
if (kind) {
session.dialogData.kind = kind;
}
if (!order) {
builder.Prompts.text(session, 'welcome please order pizza');
} else {
session.dialogData.order = order;
next();
}
}
In your second waterfall step you are not calling next, nor are you sending a Prompt to the user, which results in you being stuck after two functions.
function (session, results, next) {
var order = session.dialogData.order ? session.dialogData.order : results.response;
var kind = session.dialogData.kind;
if (results.response && !kind) {
builder.Prompts.text(session, 'what kind?');
} else {
session.send('we don\'t have that kind of pizza');
next();
}
}
Related
I am trying to call a dialog in the bot which has to go to specific function, Here is the scenario...
I have a dialog bot as shown below
bot.dialog('/Welcome', [
function(session){
builder.Prompts.text(session, "Welcome to the Bot");
},
function (session, args) {
// Has some code
},
function (session, args){
//has some code
}......
When ever i do replace dialog ie.
bot.replaceDialog('/Welcome')
it should not go to the first function ie. Welcome to the Bot It should skip this and go to next function.
Is there any way to accomplish this in azure bot?
This is quite simple if you look at their article
https://learn.microsoft.com/en-us/azure/bot-service/nodejs/bot-builder-nodejs-dialog-replace
Their example is like below
// This dialog prompts the user for a phone number.
// It will re-prompt the user if the input does not match a pattern for phone number.
bot.dialog('phonePrompt', [
function (session, args) {
if (args && args.reprompt) {
builder.Prompts.text(session, "Enter the number using a format of either: '(555) 123-4567' or '555-123-4567' or '5551234567'")
} else {
builder.Prompts.text(session, "What's your phone number?");
}
},
function (session, results) {
var matched = results.response.match(/\d+/g);
var number = matched ? matched.join('') : '';
if (number.length == 10 || number.length == 11) {
session.userData.phoneNumber = number; // Save the number.
session.endDialogWithResult({ response: number });
} else {
// Repeat the dialog
session.replaceDialog('phonePrompt', { reprompt: true });
}
}
]);
So yours would be something like below
bot.dialog('/Welcome', [
function(session, args, next){
if (!args || args.prompt)
builder.Prompts.text(session, "Welcome to the Bot");
else
next();
},
function (session, args) {
// Has some code
},
function (session, args){
//has some code
}......
and you will call it like below
bot.replaceDialog('/Welcome', {prompt: false}))
I'm playing a bit with chatbots using Bot Framework from MS.
I created one supposed to book a flight, and it has LUIS integrated.
My question is: once I'm in the flight booking dialog triggered by LUIS, I want to check if all the informations regarding the flight are given by the user (departure city, arrival city, date, airline...). So if I'm missing an info, for example the city of departure, the bot will ask 'can you give me the departure city?' qnd if I write 'from London', LUIS detects that as a new flight reservation and triggers another dialog. but I want it to stay in the dialog obviously!
Here is the dialog code so far, just in case if departure city is missing:
// Main dialog with LUIS
bot.dialog('FlightBookingDialog', [
function (session, args, next) {
// Resolve and store any entity passed from LUIS.
var intent = args.intent;
session.dialogData.airline = builder.EntityRecognizer.findEntity(intent.entities, 'Airline');
session.dialogData.class = builder.EntityRecognizer.findEntity(intent.entities, 'Class');
session.dialogData.date_time = builder.EntityRecognizer.findEntity(intent.entities, 'builtin.datetimeV2');
session.dialogData.departure = builder.EntityRecognizer.findEntity(intent.entities, 'Departure');
session.dialogData.destination = builder.EntityRecognizer.findEntity(intent.entities, 'Destination');
session.dialogData.number_tickets = builder.EntityRecognizer.findEntity(intent.entities, 'number');
session.send("I see you want to travel, great !");
if(!session.dialogData.departure) {
builder.Prompts.text(session, "Can you specify me a departure city please ?");
} else {
next();
}
},
function (session, args, results, next) {
if (results.response) {
builder.LuisRecognizer.recognize(session.message.text, luisModelUrl,
function(err, intents, entities) {
if(entities) {
var departure = builder.EntityRecognizer.findEntity(intents.entities, 'Departure');
if (departure) {
session.dialogData.departure = departure;
}
}
}
);
};
session.send("Good !");
},
]).triggerAction({
matches: 'FlightBooking'
});
If I understand correctly, I think you can update the code to have multiple matches. From this documentation page the code may be updated to look like this:
var intents = new builder.IntentDialog({ recognizers: [recognizer] })
.matches('Greeting', (session) => {
session.send('You reached Greeting intent, you said \'%s\'.', session.message.text);
})
.matches('Note.Create', [(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,
};
// Prompt for title
if (!note.title) {
builder.Prompts.text(session, 'What would you like to call your note?');
} else {
next();
}
},
(session, results, next) => {
var note = session.dialogData.note;
if (results.response) {
note.title = results.response;
}
// Prompt for the text of the note
if (!note.text) {
builder.Prompts.text(session, 'What would you like to say in your note?');
} else {
next();
}
},
(session, results) => {
var note = session.dialogData.note;
if (results.response) {
note.text = results.response;
}
// If the object for storing notes in session.userData doesn't exist yet, initialize it
if (!session.userData.notes) {
session.userData.notes = {};
console.log("initializing session.userData.notes in CreateNote dialog");
}
// Save notes in the notes object
session.userData.notes[note.title] = note;
// Send confirmation to user
session.endDialog('Creating note named "%s" with text "%s"',
note.title, note.text);
}])
Note that there is no endDialog method until the end when the bot has all the information it needs to process the request.
Hope that helps or at least gets you on the right path!
I found the solution here:
https://github.com/Microsoft/BotFramework-Samples/tree/master/docs-samples/Node/basics-naturalLanguage
They are exactly talking about my problem, I post it here so people in the same situation can see it.
You have to go to the IntentDialog part, Ctrl+F it.
I have a single dialog which has multiple Prompts(Prompts.text, Prompts.number, Prompts.Choice, Prompts.confirm). Though Prompts.choice and Prompts.confirm seems to have inbuilt validations but how to validate Prompts.text?
I have gone through this thread How to handle wrong input from the user? but it was rectified by converting text into choice.
Also I do not want to restart the whole dialog as it ask the questions form beginning then as shown in create custom prompts to validate input
Here is shorter version for my dialog:
bot.dialog('/getDetails', [
function (session, args, next) {
let options = {
retryPrompt: 'The response id invalid'
}
builder.Prompts.text(session, 'What is your full name?', options);
//passing options as argument works for Prompts.choice, which seems an inbuilt validation
},
function (session, results, next){
var name = session.dialogData.name;
//How to to reprompt if user does not enters its full name?
if (results.response) {
name.fullname = results.response;
}
builder.Prompts.text(session, 'Can you please provide your country name?');
},
function (session, results) {
var name = session.dialogData.name;
//How to reprompt only last Prompts.text if user enter an invlid value?
if (results.response) {
name.text = results.response;
}
}
}]).triggerAction({
matches: 'GetDetails',
})
Here is how i solved it through DialogAction.validatedPrompt
bot.dialog('/getDetail', [
function (session) {
session.beginDialog('/validateAge', { prompt: "What's your age?" });
//if false response, then prmopts "I did not understand {age}""
},
function (session, results) {
if (results.response) {
session.send("Thank you for adding your age");
}
}
]).triggerAction({
matches: /^lets validate$/i
})
bot.dialog('/validateAge', builder.DialogAction.validatedPrompt(builder.PromptType.text, function (response) {
if(response> 0 && response < 70){
return response;
}
}));
One of the advantages of NodeJS is its async and non-blocking I/O, which in my case is great on the one hand, but breaks my neck every day on the other hand.
I consider myself a NodeJS / Async novice and I often end up having such code:
function(req, res) {
req.assert("name", "Lobbyname is required").notEmpty();
req.assert("name", "Lobbyname length should be between 4 and 64 characters").len(4, 64);
req.assert("game", "Game not found").isInt();
req.sanitize("game").toInt();
var userId = req.user.id;
var errors = req.validationErrors();
var pg_errors = [];
var games = null;
if (errors) {
console.log(errors);
client.query("SELECT * FROM games", function(err, result) {
if (!err) {
games = result.rows;
res.render("lobby/create", {
title: "Create a new lobby",
games: games,
errors: errors.toString()
});
}
else {
res.send("error");
}
});
}
else {
errors = null;
client.query("SELECT COUNT(*) as in_lobbies FROM users u RIGHT JOIN lobby_userlist ul ON ul.user_id = u.id WHERE u.id = $1", [userId], function(err, result) {
if (!err) {
console.log(result.rows[0]);
if (result.rows[0].in_lobbies < 1) {
client.query("SELECT COUNT(*) as hosting_lobbies FROM lobbies WHERE owner = $1", [userId], function(err, result) {
if (!err) {
if (result.rows[0].hosting_lobbies < 1) {
client.query("INSERT INTO lobbies(name, game, owner) VALUES($1, $2, $3)", [req.param("name"), req.param("game"), userId], function(err, result) {
if (!err) {
res.redirect("/lobby");
}
else {
pg_errors.push(err);
console.log(err);
}
});
}
else {
errors = "You can only host one lobby at a time";
}
}
else {
pg_errors.push(err);
client.query("SELECT * FROM games", function(err, result) {
if (!err) {
games = result.rows;
res.render("lobby/create", {
title: "Create a new lobby",
games: games,
errors: errors
});
}
else {
pg_errors.push(err);
}
});
}
});
}
else {
pg_errors.push(err);
}
}
});
console.log("pg_errors");
console.log(pg_errors);
console.log("pg_errors _end");
if (pg_errors.length < 1) {
console.log("no errors");
}
else {
console.log(pg_errors);
res.send("error service operation failed");
}
}
}
This an example I have written using the following npm packages:
pg (native)
express
express-validator (middleware of node-validator)
passport (auth middleware)
Checking whether the input given by the user is valid or not is the least problem, I have this checks where I assert the variables and give back a rendered version of the page printing out the errors to the user.
BUT if we pass the validation errors in the first place we assume the "lobby" is ready to be inserted into the database, before I want to ensure that the user has no other lobby open and is not member of another lobby.
Well now I end up putting one query into another and theoretically I would have to put my view render function (res.render()) into every query callback if the query encounters an error or returns a result which inidicates that the user is not allowed to create a lobby.
I don't want that and it doesn't seem very practicable.
I tried removing the render logic and every other logic from the query callbacks and instead let the query callbacks set error arrays or variables which would indicate a success or a failure and below my query code I would check if(errors) renderPageWithErrors.
This lead to strange errors due to the async behaviour of nodejs in which case res.redirect() was called after res.render() and stuff like that.
I had to move my res.render back into the query callbacks.
Is there a proper way of doing this?
You might want to look into an async library such as https://github.com/caolan/async. It helps structure async code so that it doesn't turn into a mess like this. There are different methods depending on your requirements from simple series and parallel execution to things like waterfall to auto which does dependency tracking.
async.auto({
get_data: function(callback){
// async code to get some data
},
make_folder: function(callback){
// async code to create a directory to store a file in
// this is run at the same time as getting the data
},
write_file: ['get_data', 'make_folder', function(callback){
// once there is some data and the directory exists,
// write the data to a file in the directory
callback(null, filename);
}],
email_link: ['write_file', function(callback, results){
// once the file is written let's email a link to it...
// results.write_file contains the filename returned by write_file.
}]
}, function(err) {
// everything is done or an error occurred
});
The other nice thing it does is consolidate all errors into a single callback. That way you only have to handle errors in one place instead of them sprinkled throughout your code.
You might want to check for https://github.com/0ctave/node-sync library as well. It's a syntax sugar for nodejs Fibers, a way to write asynchronous code in a traditional way without breaking nodejs event loop model. There are a lot of discussions about pros and cons of using Fibers, but I prefer code readability and ease of development over potential small resource usage increase.
I don't know all of your code logic, but function above can look something like this:
function(req, res) {
Sync(function() {
req.assert("name", "Lobbyname is required").notEmpty();
req.assert("name", "Lobbyname length should be between 4 and 64 characters").len(4, 64);
req.assert("game", "Game not found").isInt();
req.sanitize("game").toInt();
var userId = req.user.id;
var errors = req.validationErrors();
var pg_errors = [];
var games = null;
if (errors) {
console.log(errors);
var games = client.query.sync(client, "SELECT * FROM games").rows;
games = result;
res.render("lobby/create", {
title: "Create a new lobby",
games: games,
errors: errors.toString()
});
}
else {
errors = null;
var result = client.query.sync(client, "SELECT COUNT(*) as in_lobbies FROM users u RIGHT JOIN lobby_userlist ul ON ul.user_id = u.id WHERE u.id = $1", [userId]);
console.log(result.rows[0]);
if (result.rows[0].in_lobbies < 1) {
var result = client.query.sync(client, "SELECT COUNT(*) as hosting_lobbies FROM lobbies WHERE owner = $1", [userId]);
if (result.rows[0].hosting_lobbies < 1) {
var res = client.query.sync(clien, "INSERT INTO lobbies(name, game, owner) VALUES($1, $2, $3)", [req.param("name"), req.param("game"), userId]);
res.redirect("/lobby");
}
else {
errors = "You can only host one lobby at a time";
}
}
else {
var games = client.query.sync(client, "SELECT * FROM games").rows;
res.render("lobby/create", {
title: "Create a new lobby",
games: games,
errors: errors
});
};
}
}, function(err) {
if(err) {
// do your error handling here
}
});
}
Check below algorithm...
users = getAllUsers();
for(i=0;i<users.length;i++)
{
contacts = getContactsOfUser(users[i].userId);
contactslength = contacts.length;
for(j=o;j<contactsLength;j++)
{
phones = getPhonesOfContacts(contacts[j].contactId);
contacts[j].phones = phones;
}
users[i].contacts = contacts;
}
return users;
I want to develop such same logic using node.js.
I have tried using async with foreach and concat and foreachseries functions. But all fail in the second level.
While pointer is getting contacts of one user, a value of i increases and the process is getting started for next users.
It is not waiting for the process of getting contacts & phones to complete for one user. and only after that starting the next user. I want to achieve this.
Actually, I want to get the users to object with proper
Means all the sequences are getting ruined, can anyone give me general idea how can I achieve such a series process. I am open to change my algorithm also.
In node.js you need to use asynchronous way. Your code should look something like:
var processUsesrs = function(callback) {
getAllUsers(function(err, users) {
async.forEach(users, function(user, callback) {
getContactsOfUser(users.userId, function(err, contacts) {
async.forEach(contacts, function(contact, callback) {
getPhonesOfContacts(contacts.contactId, function(err, phones) {
contact.phones = phones;
callback();
});
}, function(err) {
// All contacts are processed
user.contacts = contacts;
callback();
});
});
}, function(err) {
// All users are processed
// Here the finished result
callback(undefined, users);
});
});
};
processUsers(function(err, users) {
// users here
});
You could try this method without using async:
function getAllUserContacts(users, callback){
var index = 0;
var results = [];
var getUserContacts = function(){
getContactsOfUser(users[index].userId, function(contacts){
var index2 = 0;
var getContactsPhones = function(){
getPhonesOfContacts(contacts[index2].contactId, function(phones){
contacts[index2].phones = phones;
if(index2 === (contacts.length - 1)){
users[index].contacts = contacts;
if(index === (users.length - 1)){
callback(users)
} else {
index++;
getUserContacts();
}
}else{
index2++;
getContactsPhones();
}
});
}
getContactsPhones();
});
}
getUserContacts();
}
//calling the function
getAllUsers(function(users){
getAllUsersWithTheirContacts(users, function(usersWithContacts){
console.log(usersWithContacts);
})
})
//Asynchronous nested loop
async.eachSeries(allContact,function(item, cb){
async.eachSeries(item,function(secondItem,secondCb){
console.log(secondItem);
return secondCb();
}
return cb();
},function(){
console.log('after all process message');
});