We are facing issues while managing multiple user interaction at the same time in Dialogflow.
How we can manage user unique session as we are using custom event which will process our 3rd party API and then return a response to the specific user.
To manage User unique session We try Dailogflow Set/Get Context method, to set Context with Unique Id (using this id will store API response to the Redis server) from the first intent when a user makes a first request then will traverse that Unique Id through the custom event.
Will get that Unique Id from Context and grab data from Redis server which we stored in first intent.
We used agent.set.context() to set unique id but this method is not working with "dialogflow-fulfillment" version ^0.5.0, To get this work we have updated the version with "^0.6.1". But this will provide other error like "UnhandledPromiseRejectionWarning: Error: No responses defined for platform: null".
Required Output: Context set with a unique id and will get a proper response.
Current Output: UnhandledPromiseRejectionWarning: Error: No responses defined for platform: null
async function searchFromAPI(agent){
axios.post('https://testApi.com', searchString.data, {headers: headers})
.then((resp) => {
response.data = resp;
redisClient.set(sessionId, JSON.stringify(response));
}
}).catch(error => {
response.error = true;
response.message = error.response.statusText;
redisClient.set(sessionId, JSON.stringify(response));
});
await customsleep(2000);
const sessionId = uuid.v4();
const contextData = {'name':'userSession','lifespan': 5,'parameters':{'sessionId':sessionId}};
agent.context.set(contextData);
console.log(contextData);
agent.add('We are processing your request, Could you please wait?');
agent.add(new Suggestion("Yes"));
agent.add(new Suggestion("No"));
}
// wait for 4.5sec and call custom event
async function followOne(agent){
await customsleep(4500);
agent.setFollowupEvent('followUpTwo');
}
// wait for 4.7sec then red API response from redis server and return
async function followUpTwo(agent){
await customsleep(4000);
sess = session;
//get context
const response = agent.context.get('userSession');
// get the sessionId, Get the data from redis server
agent.add(response);
}
async function PleaseWait(agent){
await customsleep(3500);
agent.setFollowupEvent('followUpOne');
}
I found the only workaround to reassign a new context object via context.set(ctx). Then it worked.
//update context to show full menu
let context = agent.context.get('user_data');
if (!context) throw "User_data context ius nod defined in PreferrenceAdd"
let context2 = new Object();
context2 = {'name': 'user_data', 'lifespan': 40,
'parameters': context.parameters};
console.log("ctx: = " + JSON.stringify(context2));
context2.parameters.new = false;
context2.parameters.refresh = true;
agent.context.set(context2);
Check this resource on how to set a new Dialogflow outgoing context
dialogflow webhook-client.
I hope this helps you?
Related
well as i mentioned in the title when i'm sending message through socket to the server then save it in database mongodb with mongoose, then i returned the the message and send it back in the socket,
now when i tried to print it on console in the server right before i send it to the client with socket,
i got the object i wanted to send, but when i checked the object i got in the client, then i got diffrent object seems related to mongo probably(pretty sure) and i'm not sure what should i do to fix it.this is what i get in the server right before i send it back to the client
this what i get in the client when i recieve new message
const addMessage = async (newMessage) => {
try {
if (newMessage.type === 2) {
const audioBlob = new Buffer.from(newMessage.message).toString("base64");
newMessage.message = new Binary(audioBlob, Binary.SUBTYPE_BYTE_ARRAY);
}
const newMsg = new Message(newMessage);
await newMsg.save();
newMsg.message = Buffer.from(newMsg.message.buffer, "base64")
return newMsg;
} catch (error) {
console.log(error);
errorHandler(error);
}
};
i expected to get the same object i see in the server so in the client too
you should always, provide code snippets while asking a question.
Th probable reason for the above problem is, you are printing the console.log(result._docs) and sending to the databse the complete result object. Try sending result._docs to your database as well.
P.S: result is just a variable to which your assigning the data. This variable may be different in your code.
(If this does not work, edit your question and attach code)
well I found the problem, though i'm not sure how to describe it but the problem was here:
const newMsg = new Message(newMessage);
await newMsg.save();
newMsg.type === 2
? Buffer.from(newMsg.message.buffer, "base64")
: newMsg.message;
return newMsg;
so i took the newMessage and inserted newMsg.toJSON() to it;
if (newMessage.type === 2) {
const audioBlob = new Buffer.from(newMessage.message).toString("base64");
newMessage.message = new Binary(audioBlob, Binary.SUBTYPE_BYTE_ARRAY);
}
const newMsg = new Message(newMessage);
await newMsg.save();
newMessage = newMsg.toJSON();
if (newMessage.type === 2) {
newMessage.message = Buffer.from(newMessage.message.buffer, "base64");
}
return newMessage;
and now it's working!
const contextlist = 'customer';
app.intent('Welcome', (conv) => {
const customer = { 'companyId': '11131' };
conv.contexts.set(contextlist, 5, customer);
});
app.intent('Delivery',(conv)=>{
let companycontext = conv.contexts.get(contextlist);
})
These are the logs regarding the context
I'm going to set the context when my welcome intent is called and getting the value from intent when the Delivery intent will call, But I'm getting undefined at the time of getting context value.
First try adding a conv.ask() call with some text, in the code example you showed you are not returning a response, this might affect the setting of the context.
If that doesn't work, try changing the way you are setting your variables in your context to the following:
conv.contexts.set(contextlist, 5, { customer });
The properties that you want to set need to be part of an object. If you want to retrieve the customer, you need to use the following code:
// Set context
conv.contexts.set(contextlist, 5, { customer });
// Retrieve context
const companycontext = conv.contexts.get(contextlist);
const customer = companycontext.parameters.customer;
I am using the Xero Api with Nodejs and the xero-node library.
I have completed the oAuth flow and saved the token to the database. The issue i am now having is continually getting a 403 forbidden error when attempting to get anything from Xero be that Contacts, Accounts or Users. Sample code is below
I can get tenants ok without an issue however anything else doesn't work. I have checked the scopes to make sure when I am setting up the client they are correct which they are.
var getStuff = async(tokenSet) => {
await xero.setTokenSet(tokenSet);
const tenants = await xero.updateTenants();
const xeroTenantId = tenants[0].id // {String} Xero identifier for Tenant
const ifModifiedSince = new Date("2020-02-06T12:17:43.202-08:00");
const where = 'IsSubscriber==true'; // {String} Filter by an any element
const order = 'LastName ASC'; // {String} Order by an any element
console.log(tenants);
try {
const response = await xero.accountingApi.getUsers(xeroTenantId, ifModifiedSince, where, order);
console.log(response.body || response.response.statusCode)
}
catch (err) {
/// console.log(err);
console.log(`There was an ERROR! \n Status Code: ${err.response.statusCode}.`);
console.log(`ERROR: \n ${JSON.stringify(err.response.body, null, 2)}`);
}
}
Which scopes have been added to the access token you are passing through? You can decode your token here https://jwt.io/
Also - you need to pass the ‘tenant.tenantId’ to the function. I believe the tenant.id actually relates to the connection id which is coming back from the /connections endpoint.
My hunch is that is the issue. Also curious how that’s working, as updateTenants() should have the empty function call on the end. Is that working for you?
I am working on entity and intents creation in my agent using v2 client library for node.js . And for that i am going through this sample which is on git. And it says something related to session id and context id. Can anyone explain me what is sessionId and contextId. And also provide me link where i can read those thing in details.
I am unable to create context by following those example. How can i create context while creating intent at the same time.
The following is code to create a context. You cannot create a context and an intent in a single API call, you first need to create the context and then create the intent that uses the context. The response to the create context API call will return a context ID you can use in your intent.
const dialogflow = require('dialogflow');
// Instantiates clients
const entityTypesClient = new dialogflow.EntityTypesClient();
// The path to the agent the created entity type belongs to.
const agentPath = entityTypesClient.projectAgentPath(projectId);
const createEntityTypeRequest = {
parent: agentPath,
entityType: {
displayName: displayName,
kind: kind,
},
};
entityTypesClient
.createEntityType(createEntityTypeRequest)
.then(responses => {
console.log(`Created ${responses[0].name} entity type`);
})
.catch(err => {
console.error('Failed to create size entity type:', err);
});
Source: https://github.com/googleapis/nodejs-dialogflow/blob/master/samples/resource.js
Contexts are very closely associated with SessionID. Say for eg, you have a chatbot that gets spun up on two computers serving two different user's. Each user will have a respective session_id (If you're coding in NODE, when a new user fires the chatbot, you need to ensure he/she will get a unique session_id).
Now, every unique session id will have unique contexts. From above example, let's say user 1 will initialize an intent that has input context named 'abc' with lifespan of 2 and user 2 will initialize another intent that has input context named 'xyz' with lifespan of 5, these respective contexts gets recorded against each of these user's individual session id's. You can programatically control (edit) contexts and its lifecycle. This is the biggest advantage of code facilitated Dialogflow as opposed to using GUI. Using services like Firebase, you can also preserve session id's and its associated contexts so, next time same user sign's in again, they can start from where they had last left.
I can share a snippet from one of my previous projects where I was managing contexts programatically. Initialization script is as follows:
/**
* #author Pruthvi Kumar
* #email pruthvikumar.123#gmail.com
* #create date 2018-08-15 04:42:22
* #modify date 2018-08-15 04:42:22
* #desc Dialogflow config for chatbot.
*/
const dialogflow_config = {
projectId: 'xxx',
sessionId: 'chatbot-session-id', //This is default assignment. This will hve to be overridden by sessionId as obtained from client in order to main context per sessionId.
languageCode: 'en-US'
};
exports.configStoreSingleton = (function () {
let instanceStacks;
let instanceSessionId;
let contextStack = {};
let intentsStack = {};
let successfulIntentResponseStack = {};
function init() {
contextStack[dialogflow_config['sessionId']] = [];
intentsStack[dialogflow_config['sessionId']] = [];
successfulIntentResponseStack[dialogflow_config['sessionId']] = [];
return {
contextStack: contextStack,
intentsStack: intentsStack,
successfulIntentResponseStack: successfulIntentResponseStack
};
}
return {
init: function () {
if (!instanceStacks || (instanceSessionId !== dialogflow_config['sessionId'] && (!intentsStack[dialogflow_config['sessionId']]))) {
console.log('[dialogflow_config]: Singleton is not instantiated previously or New userSession is triggered! Fresh instance stack will be provisioned');
instanceStacks = init();
instanceSessionId = dialogflow_config['sessionId'];
}
return instanceStacks;
}
};
})();
exports.updateSessionIdOfDialogflowConfig = function (sessionId) {
if (typeof (sessionId) === 'string') {
dialogflow_config['sessionId'] = sessionId;
return true;
} else {
console.warn('[dialogflow_config]: SessionId must be of type STRING!');
return;
}
};
exports.getDialogflowConfig = function () {
return dialogflow_config;
};
And then, to programmatically manage contexts:
/**
* #author Pruthvi Kumar
* #email pruthvikumar.123#gmail.com
* #create date 2018-08-15 04:37:15
* #modify date 2018-08-15 04:37:15
* #desc Operate on Dialogflow Contexts
*/
const dialogflow = require('dialogflow');
const dialogflowConfig = require('../modules/dialogflow_config');
const structjson = require('./dialogflow_structjson');
const util = require('util');
const contextsClient = new dialogflow.ContextsClient();
exports.setContextHistory = function (sessionId, intent_name, context_payload, preservedContext=false) {
/* maintain context stack per session */
/* context_payload = {input_contexts: [], output_contexts = []}
*/
const contextStack = dialogflowConfig.configStoreSingleton.init().contextStack;
if (intent_name) {
contextStack[sessionId].push({
intent: intent_name,
contexts: context_payload,
preserveContext: preservedContext
});
} else {
console.warn('[dialogflow_contexts]: Intent name is not provided OR Nothing in context_payload to add to history!');
}
};
exports.getContextHistory = function () {
const contextStack = dialogflowConfig.configStoreSingleton.init().contextStack;
return contextStack;
}
exports.preserveContext = function () {
const contextStack = dialogflowConfig.configStoreSingleton.init().contextStack;
//Traverse contextStack, get the last contexts.
let context_to_be_preserved = contextStack[dialogflowConfig.getDialogflowConfig()['sessionId']][contextStack[dialogflowConfig.getDialogflowConfig()['sessionId']].length - 1];
//console.log(`context to be preserved is: ${util.inspect(context_to_be_preserved)}`);
return context_to_be_preserved['contexts'].map((context, index) => {
let context_id = exports.getContextId(context);
return exports.updateContext(context_id, true)
});
}
From here, you can reference this github resource to build your own contexts - https://github.com/googleapis/nodejs-dialogflow/blob/master/samples/resource.js
Happy creating digital souls!
I'm building a cross-platform chatbot in Google's DialogFlow. I'd like to access the Facebook User Profile API to learn the user's first name.
I'm struggling to find advice on how (or if) I can make this happen.
https://developers.facebook.com/docs/messenger-platform/identity/user-profile/
Has anybody here achieved this?
I did that for one of my bots yesterday, you need 2 things, first the Page Token and second is the psid which is Page scope user ID.
On dialogflow, you will receive the request block with psid as sender id. You can find it at:
agent.originalRequest.payload.data.sender.id
This psid needs to be passed to api get request at
/$psid?fields=first_name with your page Token as accessToken to get the first name in response.
You need to make a call to Facebook Graph API in order to get user's profile.
Facebook offers some SDKs for this, but their official JavaScript SDK is more intended to be on a web client, not on a server. They mention some 3rd party Node.js libraries on that link. I'm particularly using fbgraph (at the time of writing, it's the only one that seems to be "kind of" maintained).
So, you need a Page Token to make the calls. While developing, you can get one from here:
https://developers.facebook.com/apps/<your app id>/messenger/settings/
Here's some example code:
const { promisify } = require('util');
let graph = require('fbgraph'); // facebook graph library
const fbGraph = {
get: promisify(graph.get)
}
graph.setAccessToken(FACEBOOK_PAGE_TOKEN); // <--- your facebook page token
graph.setVersion("3.2");
// gets profile from facebook
// user must have initiated contact for sender id to be available
// returns: facebook profile object, if any
async function getFacebookProfile(agent) {
let ctx = agent.context.get('generic');
let fbSenderID = ctx ? ctx.parameters.facebook_sender_id : undefined;
let payload;
console.log('FACEBOOK SENDER ID: ' + fbSenderID);
if ( fbSenderID ) {
try { payload = await fbGraph.get(fbSenderID) }
catch (err) { console.warn( err ) }
}
return payload;
}
Notice you don't always have access to the sender id, and in case you do, you don't always have access to the profile. For some fields like email, you need to request special permissions. Regular fields like name and profile picture are usually available if the user is the one who initiates the conversation. More info here.
Hope it helps.
Edit
Promise instead of async:
function getFacebookProfile(agent) {
return new Promise( (resolve, reject) => {
let ctx = agent.context.get('generic');
let fbSenderID = ctx ? ctx.parameters.facebook_sender_id : undefined;
console.log('FACEBOOK SENDER ID: ' + fbSenderID);
fbGraph.get( fbSenderID )
.then( payload => {
console.log('all fine: ' + payload);
resolve( payload );
})
.catch( err => {
console.warn( err );
reject( err );
});
});
}