How to map multiple intents to one handler in dialogflow-fulfillment? - dialogflow-es

There are multiple intents in my bot that can use a single handler.
In v1, this was done by
function handler (agent){
//handler code
}
let intentMap = new Map();
intentMap.set('Intent1', handler);
intentMap.set('Intent2', handler);
How can the same be achieved in v2?
Thanks in advance

I found one way to do this
app.intent('Intent1', (conv) => {
//handler code
});
app.intent('Intent2', 'Intent1');

Related

Typescript Handling promise errors

In a controller I have multiple calls to some methods that return a promise.
I'm going to use the await/async statement and I have something like this:
try {
let foo = await myFirstMethod();
let bar = await mySecondMethod();
}catch(e => {
// which method fails between the both?
});
Yes, I know that I could split the call in two separate try/catch statement, but I have to handle also this scenario and in addiction I'd like to understand which is the better way to have a specific type of error response for each methods.
Any helps or suggestions are appreciated and welcome.
Thanks
What you're looking for is Promise.allSettled
let promises = [myFirstMethod() ,mySecondMethod()];
let allResults = Promise.allSettled(promises).then((results) => {
// results is an array of objects representing the promises' final state
// result.status is either "fulfilled" or "rejected"
results.forEach(result => {
console.log(result.status);
});
});

Creating a slow reply from Dialogflow

I want to create a Dialogflow webhook that responds to the user slowly, so it more feels like someone is on the other end and takes a few seconds to reply.
I'm using the built-in code editor, and can create an Intent handler (see code), but I just don't know how to get it to reply slower.
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function welcome (agent) {
agent.add(`I'm replying too quickly!`);
}
function fallback (agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
agent.handleRequest(intentMap);
});
Best way to handle this is to add delay in the UI code.
Keep the Dialogflow Intent as it is, and once the bot response is received on the frontend, show it with a delay.
Below is an example of how we are handling it at Kommunicate
Dialogflow response comes without any delay, then on Javascript code, we show a typing indicator animation, add some delay using Javascript before displaying it.
Needing to reply slower is rarely a desired thing, to be honest. But the easiest way to do so is to use setTimeout() and delay for a little bit. (Don't delay too long - more than 5 or 10 seconds and Dialogflow will timeout.)
The catch with using setTimeout(), however, is that the handler will need to return a Promise. So you'll need to wrap the call to setTimeout() and the agent.add() in a Promise handler. A function that does this might look something like:
function respondSlowly( agent, msg, ms ){
return new Promise( resolve => {
setTimeout( () => {
agent.add( msg );
resolve();
}, ms );
});
}
You would then call this from your handler, providing the agent, the message, and how many milliseconds to wait to reply:
function welcome( agent ){
return respondSlowly( agent, `Hi there, slowly`, 2000 ); // Wait 2 seconds to reply
}

Unable to get helper function to work in AWS lambda nodejs

I'm new to nodejs and learning, but can't find out why my helper function won't work.
Essentially this is part of an example alexa lambda function that generally works.
The MQTT operation works if I leave the MQTTcode within the Intent handler, but I need to move it out into the main body of code so I can call the MQTT operation from other code functions.
There are several 'test' functions in this snippet that fail to work, probably because I don't appreciate the correct way to move the code out of the Intent function.
I'm also pretty unclear on handlers.. ( multiple handlers actually ) There are two handlers in the code snippet.. it doesn't cause a problem, but I was hoping to have two lambda triggers ( ask-sdk & smart home) with each calling their own handler - not sure if that's possible.
var APP_ID = "amzn1.ask.skill.xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // input the axela skill ID
var AWS = require('aws-sdk');
var Alexa = require("alexa-sdk");
AWS.config.region = "us-east-1";
var iotData = new AWS.IotData({endpoint: "xxxxxxxxxxx.iot.us-east-1.amazonaws.com"}); // input the AWS thing end point
var topic = "esp32/sub"; //input the topic that the device is subscribed to
// Handler for Generic Event handling accepts both SmartHomeand ask-sdk events
// But only works when the handler below is removed.
exports.handler = async function (event, context) {
// Dump the request for logging - check the CloudWatch logs
console.log("index.handler request -----");
console.log(JSON.stringify(event));
if (context !== undefined) {
console.log("index.handler context -----");
console.log(JSON.stringify(context));
}
switchon(); // test call of standalone MQTTfunction ( doesn't work)
};
// Remove this function and the Smarthome Test works.
// But is needed for the ask-sdk events ( Smarthome events fail )
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
console.log("index.handler comment -----");
};
//*********************************
// Helper code examples to functionalise the MQTT switch on
// NONE OF THESE WORK WHEN CALLED
function switchon3(){
var dataObj = {
topic: topic,
payload: "on",
qos:0
};
iotData.publish(dataObj, function (err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
}
function switchon (error, data){
var params = {
topic: topic,
payload: "on",
qos:0
};
iotData.publish(params, (error, data)=>{
if (!error){this.emit(':tell', 'Robert, well done its Switched On');
}else{this.emit(':tell', 'Oh dear MQTT returned a switch on error')}
});
}
// End of helper examples
//*********************************
//********* THE PROPER CODE ************************
var handlers = {
'LaunchRequest': function () {
this.emit(':tell', 'Hello. Skill four here. How may I help you?');
},
'SwitchOnIntent': function () {
// None of the example function calls work here
// switchon3();
// this.emit(':tell', 'Test Switch On'); // needs this line to work
// The following original example code DOES work
var params = {
topic: topic,
payload: "on",
qos:0
};
iotData.publish(params, (error, data)=>{
if (!error){this.emit(':tell', 'Robert, well done its Switched On');
}else{this.emit(':tell', 'Oh dear MQTT returned a switch on error')}
});
},
Edited...
No, Tommy, it's not too basic, thanks for the help. I'm actually trying to get the lambda to accept inputs from two AWS triggers.
1. The ASK-API from custom skills
2. The Smarthome trigger.
I'm unsure if the two triggers need separate handler functions, or, if as I suspect, using the smarthome trigger voids the use of the ask-api methods that somehow call the registered Intent functions,
The json that arrives is clearly formatted differently from both trigger types, and I appreciate that it's possible to do all the alexa custom skill parsing manually within the lambda.
My question is then.. if starting out with a custom skill, registering all the function calls with the ask-api becomes void if I then add a smarthome trigger because the one handler that dealt with the ask-api event cannot also deal with the smarthome directive.
Subsequent to sorting that out, is trying to 'bring out' the MQTT call, that works within the Intent functions as originally coded, but fails if I try to put them into separate function calls.
Bear with me ... I know what I want to do.. just don't know this language well at all yet.
I think what you're not grasping here is that your actually overwriting the same variable.
exports is an object (variable) and it can have multiple properties. Forgive me if this is too basic, but a property is basically a variable attached to another variable.
In your code, you first assign the value of this property to a function.
exports.handler = async function (event, context) {
// Dump the request for logging - check the CloudWatch logs
console.log("index.handler request -----");
console.log(JSON.stringify(event));
if (context !== undefined) {
console.log("index.handler context -----");
console.log(JSON.stringify(context));
}
switchon(); // test call of standalone MQTTfunction ( doesn't work)
};
so if you then ran exports.handler() it would run that function. However, you then reassign this variable a few lines down.
So it's now the below:
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
console.log("index.handler comment -----");
};
You are replacing the first function with the second one, which is why commenting out the second assignment to exports.handler causes the first bit to work. I'm not 100% clear on what you're asking, but you either need to combine the contents of both functions if you can (or have one handler that checks the event and calls a separate function), or move them into separate lambdas.
For instance:
exports.handler = function(event,context,callback) {
if(event.EventType === "YourGenericEvent") { // replace YourGenericEvent with whatever the eventName is for the first function
genericEvent(event,context)
} else if(event.EventType === "SecondEvent") { // again replace "SecondEvent" with whatever the event is for your second function
secondEvent(event,context,callback)
}
}
function genericEvent (event, context) {
// Dump the request for logging - check the CloudWatch logs
console.log("index.handler request -----");
console.log(JSON.stringify(event));
if (context !== undefined) {
console.log("index.handler context -----");
console.log(JSON.stringify(context));
}
switchon(); // test call of standalone MQTTfunction ( doesn't work)
};
function secondEvent(event,context,callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
console.log("index.handler comment -----");
}
your console.log(event) statements should hopefully give you an indication of what the value of the EventType property should be for the IF statements.
You can see another post related to Python here
How to have more than one handler in AWS Lambda Function?

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);
});

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.

Resources