dialogflow fullfilment and firebase response time - node.js

I am trying to build a simple chatbot with DialogFlow.
My aim is to give information from user question, like : where can I slackline above water in croatia ? I have two parameters (croatia, waterline) and a list of slackline places.
So I need a data base to retrieve information from parameters. DialogFlow allows fulfillment with Firebase. I build a database with places (name, country, type of slack) and enable webhook call for my intent.
I use Inline Editor and index.js
const parameters = request.body.queryResult.parameters;
var country = parameters.country.toString();
function show(snap) {
console.log('snap');
agent.add(JSON.stringify(snap.val(),null,2));
}
function slkplc(agent) {
var testRef;
firebase.database().ref('slackplace').once('value',show);
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('slack place', slkplc);
agent.handleRequest(intentMap);
But I do not get the expected result while trying it on DialogFlow or Google Assistant. The function show is asynchronously called but too late and the response is not available for DialogFlow :
I see three way to deal with this problem :
use blocking call to database : another database ?
treat asynchronous message with DialogFlow ???
response to user that an error occured.
The third that I choose, but it is always on error.
After trying several things to wait data from database response, the only thing I managed is to freeze the response, therefore the timeout of DialogFlow - 5s -and Firebase - 60s - were reached.
A workaround
Another way to do it is to separate database acquisition and request/response from DialogFlow. The data of database is collected outside of the dialogflowFirebaseFulfillment
var data;
var inidata = firebase.database().ref().on('value',function(snap) {
console.log('snap');
data = snap.val();
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
...
function slkplc(agent) {
agent.add(JSON.stringify(data,null,2));
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('slack place', slkplc);
agent.handleRequest(intentMap);
}
Now I can do what I want with data, and I am able to find the place where I can practice waterline in croatia. But there is always something weird, the data of the database is duplicated ...

The "right" solution is option 2 that you suggest: since you're doing an asynchronous call, you need to handle this correctly when working with the dialogflow-fulfillment library.
Basically, if your handler makes an asynchronous call, it needs to be asynchronous as well. To indicate to the handleRequest() method that your handler is async, you need to return a Promise object.
Firebase's once() method returns a Promise if you don't pass it a callback function. You can take advantage of this, return that Promise, and also handle what you want it to do as part of a .then() clause. It might look something like this:
function slkplc(agent) {
var testRef;
return firebase.database().ref('slackplace').once('value')
.then( snap => {
var val = snap.val();
return agent.add( JSON.stringify( val, null, 2 ) );
});
}
The important part isn't just that you use a Promise, but also that you return that Promise.

Related

Slack delayed message integration with Node TypeScript and Lambda

I started implementing a slash-command which kept evolving and eventually might hit the 3-second slack response limit. I am using serverless-stack with Node and TypeScript. With sst (and the vscode launchfile) it hooks and attaches the debugger into the lambda function which is pretty neat for debugging.
When hitting the api endpoint I tried various methods to send back an acknowledgement to slack, do my thing and send a delayed message back without success. I didnt have much luck finding info on this but one good source was this SO Answer - unfortunetly it didn't work. I didn't use request-promise since it's deprecated and tried to implement it with vanilla methods (maybe that's where i failed?). But also invoking a second lambda function from within (like in the first example of the post) didn't seem to be within the 3s limitation.
I am wondering if I am doing something wrong or if attachinf the debugger is just taking to long etc.
However, before attempting to send a delayed message it was fine including accessing and scaning dynamodb records, manipulating the results and then responding back to slack while debugger attached without hitting the timeout.
Attempting to use a post
export const answer: APIGatewayProxyHandlerV2 = async (
event: APIGatewayProxyEventV2, context, callback
) => {
const slack = decodeQueryStringAs<SlackRequest>(event.body);
axios.post(slack.response_url, {
text: "completed",
response_type: "ephemeral",
replace_original: "true"
});
return { statusCode: 200, body: '' };
}
The promise never resolved, i guess that once hitting return on the function the lambda function gets disposed and so the promise?
Invoking 2nd Lambda function
export const v2: APIGatewayProxyHandlerV2 = async (
event: APIGatewayProxyEventV2, context, callback
): Promise<any> => {
//tried with CB here and without
//callback(null, { statusCode: 200, body: 'processing' });
const slack = decodeQueryStringAs<SlackRequest>(event.body);
const originalMessage = slack.text;
const responseInfo = url.parse(slack.response_url)
const data = JSON.stringify({
...slack,
})
const lambda = new AWS.Lambda()
const params = {
FunctionName: 'dev-****-FC******SmE7',
InvocationType: 'Event', // Ensures asynchronous execution
Payload: data
}
return lambda.invoke(params).promise()// Returns 200 immediately after invoking the second lambda, not waiting for the result
.then(() => callback(null, { statusCode: 200, body: 'working on it' }))
};
Looking at the debugger logs it does send the 200 code and invokes the new lambda function though slack still times out.
Nothing special happens logic wise ... the current non-delayed-message implementation does much more logic wise (accessing DB and manipulating result data) and manages not to timeout.
Any suggestions or help is welcome.
Quick side note, I used request-promise in the linked SO question's answer since the JS native Promise object was not yet available on AWS Lambda's containers at the time.
There's a fundamental difference between the orchestration of the functions in the linked question and your own from what I understand but I think you have the same goal:
> Invoke an asynchronous operation from Slack which posts back to slack once it has a result
Here's the problem with your current approach: Slack sends a request to your (1st) lambda function, which returns a response to slack, and then invokes the second lambda function.
The slack event is no longer accepting responses once your first lambda returns the 200. Here lies the difference between your approach and the linked SO question.
The desired approach would sequentially look like this:
Slack sends a request to Lambda no. 1
Lambda no. 1 returns a 200 response to Slack
Lambda no. 1 invokes Lambda no. 2
Lambda no. 2 sends a POST request to a slack URL (google incoming webhooks for slack)
Slack receives the POST requests and displays it in the channel you chose for your webhook.
Code wise this would look like the following (without request-promise lol):
Lambda 1
module.exports = async (event, context) => {
// Invoking the second lambda function
const AWS = require('aws-sdk')
const lambda = new AWS.Lambda()
const params = {
FunctionName: 'YOUR_SECOND_FUNCTION_NAME',
InvocationType: 'Event', // Ensures asynchronous execution
Payload: JSON.stringify({
... your payload for lambda 2 ...
})
}
await lambda.invoke(params).promise() // Starts Lambda 2
return {
text: "working...",
response_type: "ephemeral",
replace_original: "true"
}
}
Lambda 2
module.exports = async (event, context) => {
// Use event (payload sent from Lambda 1) and do what you need to do
return axios.post('YOUR_INCOMING_WEBHOOK_URL', {
text: 'this will be sent to slack'
});
}

How to use session entity in dialogflow's inline editor?

My problem is passing parameters between intents' hooks.
As example: at one hook I made a request for my own backend and receive a sessionId. I need pass the sessionId to an another intent for second request in it's hook.
How I can do it? I can't find any examples, documentations or best practices.
Thanks for your attention!
You can do this with contexts.
In your first intent you do something like this:
function firstIntentHandler(agent){
return axios.get(`https://yourserver.com`)
.then(function (response) {
agent.add(response.data.sessionId);
const context = {'name': 'yourcontextname', 'lifespan': 3, 'parameters':
{'sessionid': response.data.sessionId }};
agent.setContext(context);
}).catch(function (error) {
agent.add("An error occured.");
});
}
sessionId is the variable name in your json data sent by your server.
In your other intent you can access this data like this:
function otherIntentHandler(agent) {
const context = agent.getContext("yourcontextname");
const sessionid = context.parameters.sessionid;
// do something with this session id
});
}
Be careful with camelcase for your context name. I have the feeling that uppercase letters are stored as lowercase...

Handling asynchronous calls in bot framework

We are using a Bot configured via Microsoft Bot Framework written in NodeJS. During the execution flow of a dialog, we present the user with certain information and then some server processing is done via SOAP and the result of this SOAP response would be needed before the next waterfall method starts.
In short, we have the below piece of code:
bot.dialog('changedefaultlogingroupDialog', [
async function (session, args, next) {
wargs[0] = 'change default login group';
var sourceFile = require('./fetchSharePointUserDetail.js');
session.privateConversationData.userSharepointEmail = global.DEVSharepointBotRequestorEmailID;
console.log('\nsession.privateConversationData.userSharepointEmail:'+session.privateConversationData.userSharepointEmail);
var get_SharepointUserId_args = ['get Sharepoint user id', session.privateConversationData.userSharepointEmail];
sourceFile.login(get_SharepointUserId_args);
setTimeout(() => {
global.DEVSharepointTeamcenterUserID = require('./fetchSharePointUserDetail.js').DEVTeamcenterUserId;
console.log('\nglobal.DEVSharepointTeamcenterUserID:'+global.DEVSharepointTeamcenterUserID+'\n');
console.log("Request has been made from directline channel by user id <"+global.DEVSharepointTeamcenterUserID+">");
session.privateConversationData.requestor_id = global.DEVSharepointTeamcenterUserID;
session.privateConversationData.create_ques = session.message.text;
next();
}, 3000);
},
async function (session, result, next) {
Do processing here that is dependent on session.privateConversationData.requestor_id
}
As you can see from the above example, the setTimeout method is waiting for 3 seconds to have the SOAP response retrieved. While this worked in DEV landscape, it failed in our PRD landscape. So I wanted to know what is the more appropriate way of doing this. Is 'await' a correct case for using in this context?. I am asking this as this is in BOT Framework Context and not sure if that has any side affects.
Please suggest.
Thanks,
Pavan.
Await is the correct way to look at this.
I'm not familiar with the bot framework, but I'm guessing that they asynchronous part of your code happens during the login.
await sourceFile.login(get_SharepointUserId_args);
Would be where the asynchronous call is. It could also be in the fetchSharePointUserDetail.js
There is likely a better way to load that file as a module so that you are calling functions on a returned object, rather than returning variables from some code that is obviously executing something.

DialogFlow if statement conv.ask in app.intent

I want to run this code in dialogflow ,basically i want to use if condition
for example.
if my intent "Done" reply response [Text Response] =>'Now add 2 more and say got it' then
it switch to my another intent called "alright" so the response should be "you no. is 1" and end the converstation.
similary
if my intent "Done" reply response [Text Response] =>'Now add 4 more and say got it' then
it switch to my another intent called "alright" so the response should be "you no. is 2" and end the converstation.
and goes on.
just look at the SS Below
enter image description here
enter image description here
'use strict';
// Import the Dialogflow module from the Actions on Google client library.
const {dialogflow} = require('actions-on-google');
// Import the firebase-functions package for deployment.
const functions = require('firebase-functions');
// Instantiate the Dialogflow client.
const app = dialogflow({debug: true});
var numr=0;
app.intent('done',(conv,output)=>{
if(conv.ask ==='Now add 2 more, and say got it'){
numr=1;
return numr;
}
else{
numr=2;
return numr;
}
});
app.intent('Alright',(conv)=>{
conv.close('your number is '+ numr);
});
// Set the DialogflowApp object to handle the HTTPS POST request.
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
There are a number of issues with your code that make it difficult to understand exactly what you're doing. However, there are a few things to note. I'm assuming you're using JavaScript here.
You have a syntax error in how you are using else. The else statement should be followed by an execution block. You look like you're following it with a condition. Perhaps you are trying to use else if there?
conv.ask is a function. You're then assigning a string to it which would remove it as a function. I don't think this is what you mean to do here.
In the Intent Handler for the "done" Intent, you're not sending anything back to the user using the conv.ask() function since you just removed that as a function.
Setting a global variable does not guarantee the value of it will be preserved in between Intent Handler calls. If you want a value saved during a conversation, you should use a Context or conv.data or other means.

Actions on Google context not being passed to next intent

I am trying to pass values from one intent to another.
This is how I set the context:
app.intent('welcome', async (conv) => {
const parameters = {
userId: "12345"
};
conv.contexts.set('userDoc', 5, parameters);
conv.followup('second', {})
})
This is how I try to retrieve the context parameters:
app.intent('second', (conv) => {
console.log(conv.userDoc); // returns undefined
})
However, conv.userDoc returns undefined.
This is my dialogflow setup:
Contexts are set as part of the reply that is sent back. Calling conv.followup() discards everything from the reply except for the event type and parameters that are sent.
In general - you probably don't want to actually use conv.followup() anyway. If you feel you need to return something from your webhook - just return it.

Resources