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.
Related
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);
});
I am facing one issue in this:
I have created 4 intents bookTable, bookingDate, bookingTime and people count intent.i have added intent chaining using input and output context.
Suppose I am on third intent right now which is “Booking Time” & when I will say “Book A Table” to google home, it will directly jump to the first intent.
I need solution to not jump to the first intent but if user will say any except required input, then it will call custom fallback intent and stuck on that particular intent.
Please provide me the solution for not jumping the intent to any other..
// welcome intent here
app.intent('DefaultWelcomeIntent', (conv) => {
conv.ask('What would you like me to do for you?. Book a table or Check your calendar?');
});
// Booktable intent
app.intent('BookTable', (conv) => {
conv.ask('Please provide a date?');
});
// bookingDate intent
app.intent('bookingDate', (conv, params) => {
var speech = new Speech();
const currentDate = moment().format("YYYY-MM-DD");
const bookingDate = moment(params.date).format("YYYY-MM-DD");
const displayBookingDate = moment(bookingDate).format("DD MMM YYYY");
let say = '';
if (bookingDate && moment(bookingDate).isSameOrAfter(currentDate)) {
speech.say(`OK, ${displayBookingDate}`)
.pause('300ms')
.say(`Please provide time?`);
const speechOutput = speech.toObject();
conv.ask(speechOutput.speech);
conv.data.restaurantData.date = bookingDate;
} else {
say = `Please provide today's or upcoming date.`;
conv.ask(say);
}
});
// bookingTime intent
app.intent('bookingTime', (conv, params) => {
var speech = new Speech();
const currentDateTime = moment().format("YYYY-MM-DD HH:mm");
const bookingDate = moment(conv.data.restaurantData.date).format("YYYY-MM-DD");
const bookingTime = moment(params.time).format("HH:mm");
const booking12HourTimeString = moment(bookingTime, ["HH:mm"]).format("hh:mm A");
const concateDateTime = moment(bookingDate + " " + bookingTime).format('YYYY-MM-DD HH:mm');
let say = '';
if(moment(concateDateTime).isSameOrAfter(currentDateTime)) {
speech.say(`OK, ${booking12HourTimeString}`)
.pause('300ms')
.say(`How many people?`);
const speechOutput = speech.toObject();
conv.ask(speechOutput.speech);
conv.data.restaurantData.time = bookingTime;
} else {
say = `please provide a valid booking time.`;
conv.ask(say);
}
});
// people count intent
app.intent('peopleCount', (conv, params) => {
let peopleCounts = parseInt(params.count);
let restaurantName = 'Dominos pizza';
let restaurantAddress = 'San Francisco, ca';
let bookingDate = conv.data.restaurantData.date;
let bookingTime = conv.data.restaurantData.time;
const booking12HourTimeString = moment(bookingTime, ["HH:mm"]).format("hh:mm A");
let bookingDisplayDate = moment(bookingDate).format("DD MMM YYYY");
if (peopleCounts > 0) {
conv.ask(`OK You would like to create a table for ${restaurantName} in ${restaurantAddress} on ${bookingDisplayDate} at ${booking12HourTimeString} for ${peopleCounts} people`);
} else {
conv.ask(`Please add people more than zero`);
}
});
app.intent('customFallback', (conv, params) => {
conv.ask(`I couldn't understand. Can you say that again?`);
});
As per my understanding from your questions which is really vague, the values from the previous intents in the current intent can be get with the help of the contexts.
If still there is any place of ambiguity from this answer, it would be helpful if you provide some kind of examples.
you can not directly get the previous intent name however you can identify intent based upon the "conversationToken" as follows.
function getFeedbackFunc(agent){
...
let convTokens= request.body.originalDetectIntentRequest.payload.conversation.conversationTo
ken;
console log("Original Intent :"+convTokens);
...
}
I got the output as:["awaiting_feedback","customer_phone","awaiting_contact","customer_name","phonepair-no-followup"]
so based on the output the original intent is related to the context "phonepair-no-followup" and you can select your intent accordingly.
Please provide your feedback if it helps.
Your use case is not very clear, but as per my understanding what you really need is contexts or follow-up intents. Using these you can chain the intents.
If you still want to get previous intents name, i think you should store that in cache so that you can get that. There is no provision in dialogflow to get that.
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.
I have the following function that gets a message from aws SQS, the problem is I get one at a time and I wish to get all of them, because I need to check the ID for each message:
function getSQSMessages() {
const params = {
QueueUrl: 'some url',
};
sqs.receiveMessage(params, (err, data) => {
if(err) {
console.log(err, err.stack)
return(err);
}
return data.Messages;
});
};
function sendMessagesBack() {
return new Promise((resolve, reject) => {
if(Array.isArray(getSQSMessages())) {
resolve(getSQSMessages());
} else {
reject(getSQSMessages());
};
});
};
The function sendMessagesBack() is used in another async/await function.
I am not sure how to get all of the messages, as I was looking on how to get them, people mention loops but I could not figure how to implement it in my case.
I assume I have to put sqs.receiveMessage() in a loop, but then I get confused on what do I need to check and when to stop the loop so I can get the ID of each message?
If anyone has any tips, please share.
Thank you.
I suggest you to use the Promise api, and it will give you the possibility to use async/await syntax right away.
const { Messages } = await sqs.receiveMessage(params).promise();
// Messages will contain all your needed info
await sqs.sendMessage(params).promise();
In this way, you will not need to wrap the callback API with Promises.
SQS doesn't return more than 10 messages in the response. To get all the available messages, you need to call the getSQSMessages function recursively.
If you return a promise from getSQSMessages, you can do something like this.
getSQSMessages()
.then(data => {
if(!data.Messages || data.Messages.length === 0){
// no messages are available. return
}
// continue processing for each message or push the messages into array and call
//getSQSMessages function again.
});
You can never be guaranteed to get all the messages in a queue, unless after you get some of them, you delete them from the queue - thus ensuring that the next requests returns a different selection of records.
Each request will return 'upto' 10 messages, if you don't delete them, then there is a good chance that the next request for 'upto' 10 messages will return a mix of messages you have already seen, and some new ones - so you will never really know when you have seen them all.
It maybe that a queue is not the right tool to use in your use case - but since I don't know your use case, its hard to say.
I know this is a bit of a necro but I landed here last night while trying to pull some all messages from a dead letter queue in SQS. While the accepted answer, "you cannot guarantee to get all messages" from the queue is absolutely correct I did want to drop an answer for anyone that may land here as well and needs to get around the 10 message limit per request from AWS.
Dependencies
In my case I have a few dependencies already in my project that I used to make life simpler.
lodash - This is something we use in our code for help making things functional. I don't think I used it below but I'm including it since it's in the file.
cli-progress - This gives you a nice little progress bar on your CLI.
Disclaimer
The below was thrown together during troubleshooting some production errors integrating with another system. Our DLQ messages contain some identifiers that I need in order to formulate cloud watch queries for troubleshooting. Given that these are two different GUIs in AWS switching back and forth is cumbersome given that our AWS session are via a form of federation and the session only lasts for one hour max.
The script
#!/usr/bin/env node
const _ = require('lodash');
const aswSdk = require('aws-sdk');
const cliProgress = require('cli-progress');
const queueUrl = 'https://[put-your-url-here]';
const queueRegion = 'us-west-1';
const getMessages = async (sqs) => {
const resp = await sqs.receiveMessage({
QueueUrl: queueUrl,
MaxNumberOfMessages: 10,
}).promise();
return resp.Messages;
};
const main = async () => {
const sqs = new aswSdk.SQS({ region: queueRegion });
// First thing we need to do is get the current number of messages in the DLQ.
const attributes = await sqs.getQueueAttributes({
QueueUrl: queueUrl,
AttributeNames: ['All'], // Probably could thin this down but its late
}).promise();
const numberOfMessage = Number(attributes.Attributes.ApproximateNumberOfMessages);
// Next we create a in-memory cache for the messages
const allMessages = {};
let running = true;
// Honesty here: The examples we have in existing code use the multi-bar. It was about 10PM and I had 28 DLQ messages I was looking into. I didn't feel it was worth converting the multi-bar to a single-bar. Look into the docs on the github page if this is really a sticking point for you.
const progress = new cliProgress.MultiBar({
format: ' {bar} | {name} | {value}/{total}',
hideCursor: true,
clearOnComplete: true,
stopOnComplete: true
}, cliProgress.Presets.shades_grey);
const progressBar = progress.create(numberOfMessage, 0, { name: 'Messages' });
// TODO: put in a time limit to avoid an infinite loop.
// NOTE: For 28 messages I managed to get them all with this approach in about 15 seconds. When/if I cleanup this script I plan to add the time based short-circuit at that point.
while (running) {
// Fetch all the messages we can from the queue. The number of messages is not guaranteed per the AWS documentation.
let messages = await getMessages(sqs);
for (let i = 0; i < messages.length; i++) {
// Loop though the existing messages and only copy messages we have not already cached.
let message = messages[i];
let data = allMessages[message.MessageId];
if (data === undefined) {
allMessages[message.MessageId] = message;
}
}
// Update our progress bar with the current progress
const discoveredMessageCount = Object.keys(allMessages).length;
progressBar.update(discoveredMessageCount);
// Give a quick pause just to make sure we don't get rate limited or something
await new Promise((resolve) => setTimeout(resolve, 1000));
running = discoveredMessageCount !== numberOfMessage;
}
// Now that we have all the messages I printed them to console so I could copy/paste the output into LibreCalc (excel-like tool). I split on the semicolon for rows out of habit since sometimes similar scripts deal with data that has commas in it.
const keys = Object.keys(allMessages);
console.log('Message ID;ID');
for (let i = 0; i < keys.length; i++) {
const message = allMessages[keys[i]];
const decodedBody = JSON.parse(message.Body);
console.log(`${message.MessageId};${decodedBody.id}`);
}
};
main();
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.