Variable lifetime in Dialogflow's fulfillment editor? - node.js

I'm helping create a chatbot that pulls real estate data from a google sheet, and displays that information to a user. The user inputs an address, then the pulled information is assigned to a houseData variable that has been declared outside of the function with the http request. The houseData object has keys like bedrooms, bathrooms, monthlyPayment, address, etc, which is then referenced in the agent.add Payload for displaying the values for the keys to the user in the chat widget. This is all mapped to one intent.
Later on I try to access monthlyPayment of the houseData object in a function mapped to a different intent to see how a user's reported income compares to the payment for the house. The houseData variable is undefined, even though in firebase I can see the information is received from the google sheet in a console.log during the first intent.
Do variable values reset in the fulfillment editor after the intent which specific functions are mapped to is finished? How can I store persistent information in the editor that can be accessed throughout the conversation flow? Is this a situation where I should set a new context?
Thank you.
edit: I added a quick mockup of the code to help visualize the problem:
let houseData;
function sendHouseData(agent) {
let address = userInput
return getHouseData(address).then(agent.add(new Payload(
`Your house has ${houseData.bedrooms} bedrooms and ${houseData.bathrooms} bathrooms.`
))).catch(err);
}
function getHouseData(address) {
https.get(apiURL, (res) => {
res.on('end', () => {
houseData = parsedJSONData
})
})
}
// ------- LATER ------ //
let userData = {
income: '',
qualified: true
}
function qualify(agent) {
// user self reports their monthly income thru some quick reply options
if (houseData.monthlyPayment / userData.income < 0.39) { // houseData is undefined at this point
userData.qualified = false
}
}
intentMap.set(enterAddress, sendHouseData);
intentMap.set(qualify, qualify)

Global variables might be maintained across your Dialogflow requests, but it is not guaranteed. From the Cloud Functions Tips & Tricks:
There is no guarantee that the state of a Cloud Function will be preserved for future invocations. However, Cloud Functions often recycles the execution environment of a previous invocation. If you declare a variable in global scope, its value can be reused in subsequent invocations without having to be recomputed.
Reference: https://cloud.google.com/functions/docs/bestpractices/tips#use_global_variables_to_reuse_objects_in_future_invocations
Typically (to cache data for performance reasons) you would store all your data in Firestore/RTDB and if houseData is undefined then pull it out of the database, else you can use houseData directly for a cached copy. In this case though you probably want to keep a record of how old it is and refresh it accordingly.
However, if houseData is going to be different for each user/session then you may be forced to pull it from the database on every request. (Note that if you are running functions and database on Firebase it is going to be fast anyway.)

Related

How to handle Firebase Cloud Functions infinite loops?

I have a Firebase Cloud functions which is triggered by an update to some data in a Firebase Realtime Database. When the data is updated, I want to read the data, perform some calculations on that data, and then save the results of the calculations back to the Realtime Database. It looks like this:
exports.onUpdate = functions.database.ref("/some/path").onUpdate((change) => {
const values = change.after.val();
const newValues = performCalculations(value);
return change.after.ref.update(newValues);
});
My concern is that this may create an indefinite loop of updates. I saw a note on the Cloud Firestore Triggers that says:
"Any time you write to the same document that triggered a function,
you are at risk of creating an infinite loop. Use caution and ensure
that you safely exit the function when no change is needed."
So my first question is: Does this same problem apply to the Firebase Realtime Database?
If it does, what is the best way to prevent the infinite looping?
Should I be comparing before/after snapshots, the key/value pairs, etc.?
My idea so far:
exports.onUpdate = functions.database.ref("/some/path").onUpdate((change) => {
// Get old values
const beforeValues = change.before.val();
// Get current values
const afterValues = change.after.val();
// Something like this???
if (beforeValues === afterValues) return null;
const newValues = performCalculations(afterValues);
return change.after.ref.update(newValues);
});
Thanks
Does this same problem apply to the Firebase Realtime Database?
Yes, the chance of infinite loops occurs whenever you write back to the same location that triggered your Cloud Function to run, no matter what trigger type was used.
To prevent an infinite loop, you have to detect its condition in the code. You can:
either flag the node/document after processing it by writing a value into it, and check for that flag at the start of the Cloud Function.
or you can detect whether the Cloud Function code made any effective change/improvement to the data, and not write it back to the database when there was no change/improvement.
Either of these can work, and which one to use depends on your use-case. Your if (beforeValues === afterValues) return null is a form of the second approach, and can indeed work - but that depends on details about the data that you haven't shared.

Context Management in Bot Framework - Node JS

in the main dialog, I have used this to get the details of the user activity and session ID stepContext.context._activity
and I am able to store the data in JSON format,
I want to know how to use the previous text of the user for the next query if there is no entity present in that and it should work for multiple users, not for a single user.
The location of the user's input varies depending on the option provided to them. Simple text input is available in the stepContext.context.activity.text field. However, the user's returned value could, for example, exist in:
stepContext.result, if a hero card is used
stepContext.options, if a value is forwarded on from a previous step or action
stepContext.context.activity.text, if a suggested action is used
stepContext.context.activity.value, if a postBack is returned
There are a few other possibilities. Referencing the docs is a good place to start as are the BotBuilder-Samples for getting a grasp on all the possibilities. Don't be averse to inspecting the stepContext to see how it is populated and with what.
async firstStep(stepContext) {
await stepContext.context.sendActivity('Type something...');
return { status: DialogTurnStatus.waiting };
}
async secondStep(stepContext) {
const result = stepContext.context.activity.text;
return await stepContext.context.sendActivity(`You said: ${ result }`);
Hope of help!

tableSvc.retrieveEntity retrieves previous version of Azure Table Storage data

I have a waterfall of dialogs in Bot Framework SDK3,
each dialog does something, until it switches to dialog with tableSvc.retrieveEntity which correctly identifies a required to be retrieved entity (according to given PartionKey & RowKey) from Azure Table...
...but the entity which is retreived (I check it with console.log('Result') is outdated (one step [a few seconds, which pass during conversation of user with a bot] behind the actual data stored in Azure Tables - the real data which needs to be retrieved in this dialog...)
The Conversation is not closed yet (it will be later) - it is important to store and retrieve actual data at this stage...
How to get actual data in this dialog?
Well, for those of you who had similar problem...
I guess, it has to do with event loop of Node.js...
I'm not sure whether it is a bullet-proof solution, or a temporary 'hack',
but I put it like this and it works (when I try to use setTimeout 0 ms - it does not work for me, when I set it to 500ms - it works, so I guess 1000 ms could be a safe temporary hack..before I find better solution)..
If someone knows a better, more robust, solution, please, update this thread.
setTimeout( () => {
tableSvc.retrieveEntity('table', pkey, rkey, funcdtion(error, result, response) {
if(!error) {
var res1 = result.Data._;
console.log(res1); // Now it prints actual data stored in 'table' - which I really need, and not its previous (outdated) version
} else {
console.log('Some error happened...');
};
});
}, 1000);

Firestore: get document back after adding it / updating it without additional network calls

Is it possible to get document back after adding it / updating it without additional network calls with Firestore, similar to MongoDB?
I find it stupid to first make a call to add / update a document and then make an additional call to get it.
As you have probably seen in the documentation of the Node.js (and Javascript) SDKs, this is not possible, neither with the methods of a DocumentReference nor with the one of a CollectionReference.
More precisely, the set() and update() methods of a DocumentReference both return a Promise containing void, while the CollectionReference's add() method returns a Promise containing a DocumentReference.
Side Note (in line with answer from darrinm below): It is interesting to note that with the Firestore REST API, when you create a document, you get back (i.e. through the API endpoint response) a Document object.
When you add a document to Cloud Firestore, the server can affect the data that is stored. A few ways this may happen:
If your data contains a marker for a server-side timestamp, the server will expand that marker into the actual timestamp.
Your data data is not permitted according to your server-side security rules, the server will reject the write operation.
Since the server affects the contents of the Document, the client can't simply return the data that it already has as the new document. If you just want to show the data that you sent to the server in your client, you can of course do so by simply reusing the object you passed into setData(...)/addDocument(data: ...).
This appears to be an arbitrary limitation of the the Firestore Javascript API. The Firestore REST API returns the updated document on the same call.
https://firebase.google.com/docs/firestore/reference/rest/v1beta1/projects.databases.documents/patch
I did this to get the ID of a new Document created, and then use it in something else.
Future<DocumentReference<Object>> addNewData() async {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final CollectionReference _userCollection = _firestore.collection('users');
return await _userCollection
.add({ 'data': 'value' })
.whenComplete(() => {
// Show good notification
})
.catchError((e) {
// Show Bad notification
});
}
And here I obtain the ID:
await addNewData()
.then((document) async {
// Get ID
print('ID Document Created ${document.id}');
});
I hope it helps.

How to think asynchronously with nodejs?

I just started developing nodejs. I'm confused to use async model. I believe there is a way to turn most of SYNC use cases into ASYNC way. Example, by SYNC, we load some data and wait until it returns then show them to user; by ASYNC, we load data and return, just tell the user data will be presented later. I can understand why ASYNC is used in this scenario.
But here I have a use case. I'm building an web app, allowing user to place a order (buying something). Before saving the order data into db, I want to put some user data together with order data (I'm using document NoSql db by the way). So I think by SYNC, after I get order data, I make a SYNC call to database and wait for its returned user data. After I get returned data, integrate them together and ingest into db.
I think there might be an issue if I make ASYNC call to db to query user data because user data may be returned after I save data to db. And that's not what I want.
So in this case, how can I do this thing ASYNCHRONOUSLY?
Couple of things here. First, if your application already has the user data (the user is already logged in), then this information should be stored in session so you don't have to access the DB. If you are allowing the user to register at the time of purchase, you would simply want to pass a callback function that handles saving the order into your call that saves the user data. Without knowing specifically what your code looks like, something like this is what you would be looking for.
function saveOrder(userData, orderData, callback) {
// save the user data to the DB
db.save(userData, function(rec) {
// if you need to add the user ID or something to the order...
orderData.userId = rec.id; // this would be dependent on your DB of choice
// save the order data to the DB
db.save(orderData, callback);
});
}
Sync code goes something like this. step by step - one after other. There can be ifs and loops (for) etc. all of us get it.
fetchUserDataFromDB();
integrateOrderDataAndUserData();
updateOrderData();
Think of async programming with nodejs as event driven. Like UI programming - code (function) is executed when an event occurs. E.g. On click event - framework calls back registered clickHandler.
nodejs async programming can also be thought on these lines. When db query (async) execution completes, your callback is called. When order data is updated, your callback is called. The above code goes something like this:
function nodejsOrderHandler(req,res)
{
var orderData;
db.queryAsync(..., onqueryasync);
function onqueryasync(userdata)
{
// integrate user data with order data
db.update(updateParams, onorderudpate);
}
function onorderupdate(e, r)
{
// handler error
write response.
}
}
javascript closure provides the way to keep state in variables across functions.
There is certainly much more to async programming and there are helper modules that help with basic constructs like chain, parallel, join etc as you write more involved async code. but this probably gives you a quick idea.

Resources