is there a way to activate a dialogflow intent dynamically? - dialogflow-es

I wonder if there's a way to activate dinamically an intent, a sort of
"it this condition is met, than goto intent XX".
I try to explain what I'd like to do:
when the user says a specific word, the intent looks up in the database what are the tasks to be done according to a priority algoritm. If for example task 1 is selected as topmost priority, then I should be able to activate "at runtime" task 1 intent, without asking the user to say "please say 001 to activate task 1" .
Thanks in advance

I've implemented your request for "if condition is met, got to X intent" in my webhook by moving my responses to different functions. In the case below, I want a different response and different output context based on an if condition. This way I can trigger different intents dynamically based on user input.
const game = SessionsService.getGame(userId);
if(game) {
logger.info("Found a gamein progress..")
const response = gameInProgress();
conv.contexts.set("_defaultwelcomeintent-followup", 1);
return conv.ask(response);
} else {
logger.info("No game in progress found..")
const response = welcome();
conv.contexts.set("_introduction", 1);
conv.contexts.set("_setup", 1);
return conv.ask(response);
};
An example of a response function:
function gameInProgress() {
const text = messages.gameInProgressText();
const ssml = messages.gameInProgressSsml();
const response = new BasicResponse(text, ssml);
return response;
};
Instead of the boardService, you could implement your priority algorithm.

Related

How to have delay between two steps in Bot Framework v4?

I want to have dialog flow like this
step1 -> delay -> step2
async step1(stepContext) {
await stepContext.context.sendActivity('Give user some task to do');
return await stepContext.next();
}
async delay(stepContext) {
await stepContext.context.sendActivity({type: 'delay', value:5000});
return await stepContext.next();
}
async step2(stepContext) {}
The above code is not working as wanted. When I run the bot it waits for 5 seconds and then executes step1 and step2. I want the delay to be after step1.
Actually, I want the delay to be of 2 minutes. I was wondering that won't the bot go on sleep or something. I am sorry I am new to this.
I think the best way to accomplish this while using waterfall dialogs is going to be via the use of setTimeout and Proactive Messages. I'm going to make some assumptions here that you are familiar with things like Conversation State, Bot Framework Adapter, and Turn Handlers, but if you need further guidance on anything let me know.
First, foundationally you are going to need to capture the conversation reference in order to send the proactive message. I find this easiest to just do in your onTurn function and save it to conversation state every turn as follows:
const conversationData = await this.dialogState.get(context, {});
conversationData.conversationReference = TurnContext.getConversationReference(context.activity);
await this.conversationState.saveChanges(context);
Now, you can set up your dialog as follows. You could probably do this several different ways. I typically try to make each step a prompt, but you could probably switch these actions around and still make it work.
async step1(stepContext) {
// First give the user the task
await stepContext.context.sendActivity('Give user some task to do');
// Next set up a timeout interval. The action here is to send a proactive message.
const conversationData = await this.dialogState.get(stepContext.context, {});
this.inactivityTimer = setTimeout(async function(conversationReference) {
try {
const adapter = new BotFrameworkAdapter({
appId: process.env.microsoftAppID,
appPassword: process.env.microsoftAppPassword
});
await adapter.continueConversation(conversationReference, async turnContext => {
await turnContext.sendActivity('Were you able to successfully complete the task?');
});
} catch (error) {
//console.log('Bad Request. Please ensure your message contains the conversation reference and message text.');
console.log(error);
}
}, 120000, conversationData.conversationReference);
// Give the user a confirmation prompt
return await stepContext.prompt('choicePrompt', 'Please confirm the results of the task when you are finished',['Passed','Failed']);
}
async step2(stepContext) {
// Clear timeout to prevent the proactive message if user has already responded
clearTimeout(this.inactivityTimer);
/* REST OF YOUR DIALOG HERE */
return await stepContext.endDialog();
}
As mentioned, make sure you are importing your Bot Framework Adapter, setting up your prompt (I used choice but obviously it can be whatever), importing conversation state, etc. Also, one thing to be aware of is that if the user responds very close to the timer (2 minutes as I've set it up here), it is possible that the proactive message will be invoked before the bot reaches the clearTimeout statement. I think this would be an annoyance at most, but you would need to decide if that UX is ok based on how frequently someone would complete the task in near exactly 2 minutes.
One final note, you could probably put the proactive message invocation in a separate helper function, especially if you are going to use this in many different dialogs. That way you don't have to create multiple instances of the Adapter, in addition to making your code easier to update. That said, if you only need it in only place like I did, I find it much easier just to insert it into the dialog since it's not that much code.

conv.data lost in followup intent in DialogFlow

I'm using node.js 8 runtime in my Google Cloud function, attached to my DialogFlow app (V2 API).
I can use conv.data to store temporary data within the current conversation. Unfortunately, conv.data does not seem to retain data after a followup intent.
For example, in my intent the following code:
conv.data.result = "Hello!";
console.log("[DEBUG] conv.data.result = "+conv.data.result);
conv.followup("customEvent1");
produces the following log:
[DEBUG] conv.data.result = Hello!
This is my followup intent:
app.intent('CUSTOM_EVENT_INTENT', (conv) => {
console.log("[DEBUG] - CUSTOM_EVENT_INTENT");
console.log("[DEBUG] - conv.data.result = "+conv.data.result);
if(!conv.data.result) {
console.log("[DEBUG] - I give up");
conv.close("Nessuna risposta");
}
else conv.ask(conv.data.result);
});
which produces the following log:
[DEBUG] - conv.data.result = undefined
[DEBUG] - I give up
Looks like I'm missing something very important in followup intents...
Thanks,
Roberto
I would suggest using context instead of data. Context has few more advantages over data objects. It can be accessible in multiple dialog and intents.
function getParamFromContext(key){
// get the value set earlier in the context
let globalContext = agent.context.get('global_main_context');
return globalContext.parameters[key];
}
function updateGlobalContext(agent, key, value) {
// create a global method to maintain all required data within conversation
let globalContext = agent.context.get('global_main_context');
let param = globalContext ? globalContext.parameters : {};
param[key] = value;
agent.context.set({
name: 'global_main_context',
lifespan: 5,
parameters: param
});
}
Call the method within a conversation with agent object.
in app.js
intentMap.set("Size Intent", setSize);
function setSize(){
let size = agent.parameters.size;
updateShippingObjectContext(agent, 'size', size);
}
I think you're talking about Followup Events rather than Followup Intents. Followup Intents are set as Intents that may be triggered after an Intent has by the user doing some action - this is done by setting a Context. Followup Events are set during fulfillment and are intended to trigger an Intent that has this Event set.
In most cases, you don't need to use Followup Events.
Instead - just call a function that does the processing and replies how you want it to do. There is nothing saying that your Handler function has to do everything itself - it can call a function just like any other function, and can call it with parameters.
So it is perfectly reasonable to have Intent Handlers like
app.intent('intent.one', (conv) => {
reply( conv, "Hello!" );
});
app.intent('intent.two', (conv) => {
reply( conv, "How are you?" );
});
app.intent('intent.quit', (conv) => {
reply( conv );
});
function reply( conv, msg ){
if( !msg ){
conv.close( "I give up!" );
} else {
cov.ask( msg );
}
}
That still doesn't explain why it didn't work.
What you are "missing" with how you use Followup Events is that using conv.followup() does not send anything that you may have sent back to Dialogflow when it redirects to the Event. As the documentation says:
Triggers an intent of your choosing by sending a followup event from the webhook. [...] Dialogflow will not pass anything back to Google Assistant, therefore Google Assistant specific information, most notably conv.user.storage, is ignored.
What you can do, however, is send parameters to the new Intent that is detected from the Event. Something like this might work:
const params = {
result: "Hello!"
};
conv.followup("customEvent1", params);
and then in the handler for the event's Intent:
app.intent('CUSTOM_EVENT_INTENT', (conv) => {
console.log("[DEBUG] - CUSTOM_EVENT_INTENT");
console.log("[DEBUG] - conv.parameters.result = "+conv.parameters.result);
if(!conv.parameters.result) {
console.log("[DEBUG] - I give up");
conv.close("Nessuna risposta");
}
else conv.ask(conv.parameters.result);
});

"TypeError: Cannot read property 'get' of undefined" for contextual data

I'm running the code that Google/ dialogflow provided on their website (Link) and for some reason it keeps throwing this error everytime I trigger the intent I'm wanting to invoke:
TypeError: Cannot read property 'get' of undefined
Here's the code I have:
app.intent('Make Appointment' , (conv) => {
// This variable needs to hold an instance of Date object that specifies the start time of the appointment.
const dateTimeStart = convertTimestampToDate(conv.parameters.date,
conv.parameters.time);
// This variable holds the end time of the appointment, which is calculated by adding an hour to the start time.
const dateTimeEnd = addHours(dateTimeStart, 1);
// Convert the Date object into human-readable strings.
const appointmentTimeString = getLocaleTimeString(dateTimeStart);
const appointmentDateString = getLocaleDateString(dateTimeStart);
// The checkCalendarAvailablity() function checks the availability of the time slot.
return checkCalendarAvailablity(dateTimeStart, dateTimeEnd).then(() => {
// The time slot is available.
// The function returns a response that asks for the confirmation of the date and time.
conv.ask(`Okay, ${appointmentDateString} at ${appointmentTimeString}. Did I get that right?`);
}).catch(() => {
// The time slot is not available.
conv.add(`Sorry, we're booked on ${appointmentDateString} at ${appointmentTimeString}. Is there anything else I can do for you?`);
// Delete the context 'MakeAppointment-followup' to return the flow of conversation to the beginning.
conv.context.delete('MakeAppointment-followup');
});
});
app.intent('Make Appointment - yes', (conv) => {
// Get the context 'MakeAppointment-followup'
const context = conv.context.get('MakeAppointment-followup');
// This variable needs to hold an instance of Date object that specifies the start time of the appointment.
const dateTimeStart = convertTimestampToDate(context.parameters.date, context.parameters.time);
// This variable holds the end time of the appointment, which is calculated by adding an hour to the start time.
const dateTimeEnd = addHours(dateTimeStart, 1);
// Convert the Date object into human-readable strings.
const appointmentTimeString = getLocaleTimeString(dateTimeStart);
const appointmentDateString = getLocaleDateString(dateTimeStart);
// Delete the context 'MakeAppointment-followup'; this is the final step of the path.
conv.context.delete('MakeAppointment-followup');
// The createCalendarEvent() function checks the availability of the time slot and marks the time slot on Google Calendar if the slot is available.
return createCalendarEvent(dateTimeStart, dateTimeEnd).then(() => {
conv.add(`Got it. I have your reservation scheduled on ${appointmentDateString} at ${appointmentTimeString}!`);
}).catch(() => {
conv.add(`Sorry, something went wrong. I couldn't book the ${appointmentDateString} at ${appointmentTimeString}. Is there anything else I can help you with?`);
});
});
(sorry for the long code, but I feel it's necessary for the question)
All these two functions do is makes an appointment (or booking) and adds a confirmation step to it. So for example, it would look something like this if all went well:
User: I'd like to make an appointment
bot: Sure, what day works for you?
User: Tomorrow
bot: what time?
User: At 6 pm
bot: Okay, February 11 at 6pm, did I get that right?
User: Yes // Part where it stops working
Bot: Great, I have your appointment booked for February 11 at 6pm! // App crashes at this part
I'm suspect (although highly doubtful) that the reason could be that in the examples they were using "agent" and the old syntax, where as I'm using the AOG syntax.
Any suggestions/ help are much appreciated!
You're pretty close.
These should be the property contexts with an "s" at the end.
It looks like you're using the actions-on-google library, which typically has a conv object and uses the contexts property to access the contexts with get(), set(), and delete() methods.
If you used the dialogflow-fulfillment library (which you didn't), it tends to have an agent object and use the context property (without the "s") to access similar get(), set(), and delete() methods.

How to get current intent's name in Dialogflow fulfillment?

I want to get the name of the current intent in the fulfillment so I can deal with different response depending on different intent i'm at. But I cannot find a function for it.
function getDateAndTime(agent) {
date = agent.parameters.date;
time = agent.parameters.time;
// Is there any function like this to help me get current intent's name?
const intent = agent.getIntent();
}
// I have two intents are calling the same function getDateAndTime()
intentMap.set('Start Booking - get date and time', getDateAndTime);
intentMap.set('Start Cancelling - get date and time', getDateAndTime);
There is nothing magical or special about using the intentMap or creating a single Intent Handler per intent. All the handleRequest() function does is look at action.intent to get the Intent name, get the handler with that name from the map, call it, and possibly dealing with the Promise that it returns.
But if you're going to violate the convention, you should have a very good reason for doing so. Having a single Intent Handler per Intent makes it very clear what code is being executed for each matched Intent, and that makes your code easier to maintain.
It looks like your reason for wanting to do this is because there is significant duplicate code between the two handlers. In your example, this is getting the date and time parameters, but it could be many more things as well.
If this is true, do what programmers have been doing for decades: push these tasks to a function that can be called from each handler. So your examples might look something like this:
function getParameters( agent ){
return {
date: agent.parameters.date,
time: agent.parameters.time
}
}
function bookingHandler( agent ){
const {date, time} = getParameters( agent );
// Then do the stuff that uses the date and time to book the appointment
// and send an appropriate reply
}
function cancelHandler( agent ){
const {date, time} = getParameters( agent );
// Similarly, cancel things and reply as appropriate
}
intentMap.set( 'Start Booking', bookingHandler );
intentMap.set( 'Cancel Booking', cancelHandler );
request.body.queryResult.intent.displayName will give the the intent name.
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function getDateAndTime(agent) {
// here you will get intent name
const intent = request.body.queryResult.intent.displayName;
if (intent == 'Start Booking - get date and time') {
agent.add('booking intent');
} else if (intent == 'Start Cancelling - get date and time'){
agent.add('cancelling intent');
}
}
let intentMap = new Map();
intentMap.set('Start Booking - get date and time', getDateAndTime);
intentMap.set('Start Cancelling - get date and time', getDateAndTime);
agent.handleRequest(intentMap);
});
But it would made more sense if you use two different functions in intentMap.set
you can try using "agent.intent" but it doesn't make sense to use the same function for two different intents.

ChatBot back to previous dialog

I've created a chatbot using Node.js and the flow of dialogs works fine til the endDialog. Im having issues implementing a back option so it can jump back only to previous dialog. Can anyone please guide me how to solve that?
.reloadAction(
"restartBenefits", "Ok. Let's start over.",
{
matches: /^start over$|^restart$/i
}
)
.cancelAction(
"cancelRequest", "Thank you for reaching out, Good bye!",
{
matches: /^nevermind$|^cancel$|^cancel.*request/i,
confirmPrompt: "This will cancel your request. Are you sure?"
}
);
Use a customAction
In this case you can listen for whatever you want to be a keyword for "back" and then simply route the user back to that dialog using replaceDialog
bot.customAction({
matches: /back|last/gi, //whatever prompts you want to match.
onSelectAction: (session, args, next) => {
session.replaceDialog(PreviousDialog); //variable with the last dialog in it, set that somewhere, such as the end of your previous dialog
}
})
I think that at the final step of the dialog waterfall you need to add the last lines in this sample step:
/**
* This is the final step in the main waterfall dialog.
* It wraps up the sample "book a flight" interaction with a simple confirmation.
*/
async finalStep(stepContext) {
// If the child dialog ("bookingDialog") was cancelled or the user failed to confirm, the Result here will be null.
if (stepContext.result) {
const result = stepContext.result;
// Now we have all the booking details.
// This is where calls to the booking AOU service or database would go.
// If the call to the booking service was successful tell the user.
const timeProperty = new TimexProperty(result.travelDate);
const travelDateMsg = timeProperty.toNaturalLanguage(new Date(Date.now()));
await stepContext.context.sendActivity(ActivityFactory.fromObject(this.lgTemplates.evaluate('BookingConfirmation', {
Destination: result.destination,
Origin: result.origin,
DateMessage: travelDateMsg
})));
}
// =====> HERE: Restart the main dialog with a different message the second time around
return await stepContext.replaceDialog(this.initialDialogId, { restartMsg: 'What else can I do for you?' });
}
Just change the this.initialDialogId accordingly.

Resources