Context Management in Bot Framework - Node JS - 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!

Related

Variable lifetime in Dialogflow's fulfillment editor?

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.)

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.

Discord <#!userid> vs <#userid>

so I'm creating a bot using Node.JS / Discord.JS and I have a question.
On some servers, when you mention a user, it returns in the console as <#!userid> and on other it returns as <#userid>.
My bot has a simple points / level system, and it saves in a JSON file as <#!userid>, so on some servers when trying to look at a users points by mentioning them will work, and on others it won't.
Does anyone have any idea how to fix this? I've tried to find an answer many times, and I don't want to have it save twice, once as <#!userid> and then <#userid>. If this is the only way to fix it then I understand.
Thanks for your help!
The exclamation mark in the <#!userID> means they have a nickname set in that server. Using it without the exclamation mark is more reliable as it works anywhere. Furthermore, you should save users with their id, not the whole mention (the "<#userid>"). Parse out the extra symbols using regex.
var user = "<#!123456789>" //Just assuming that's their user id.
var userID = user.replace(/[<#!>]/g, '');
Which would give us 123456789. Their user id. Of course, you can easily obtain the user object (you most likely would to get their username) in two ways, if they're in the server where you're using the command, you can just
var member = message.guild.member(userID);
OR if they're not in the server and you still want to access their user object, then;
client.fetchUser(userID)
.then(user => {
//Do some stuff with the user object.
}, rejection => {
//Handle the error in case one happens (that is, it could not find the user.)
});
You can ALSO simply access the member object directly from the tag (if they tagged them in the message).
var member = message.mentions.members.first();
And just like that, without any regex, you can get the full member object and save their id.
var memberID = member.id;

wit.ai runActions how to handle context in follow-up message

I'm using node-wit to develop a chatbot application.
This is working fine mostly, but I've run into a problem with the use of the context.
I'm using the runActions api :
this.witClient.runActions(customer._key, messageText, witContext).then((newContext => {}
)).catch(reject);
I have defined a number of actions, which set the context.
This is working fine, as long the context is taking place over one message.
For example, if I were to call an action called addProduct :
addProduct({sessionId, context, text, entities}) {
return new Promise((resolve, reject) => {
context.product = `myNewProduct';
resolve(context);
});
},
It will then show a message using the 'product' context key.
However, when I try to use it over 2 messages, it seems to have lost the context ( for example, when asking a multiple choice question, and then handling that response ).
If I understand how it's working correctly, then node-wit doesn't keep the context beyond messages ( I assumed this at first because I'm passing a session key ).
A solution I see is to store the resulting context ( newContext in this case) in a session/user specific way, and then restore it and pass it again when the user is sending his new message.
Meaning, something like this :
witContext = getContextFromSession();
this.witClient.runActions(customer._key, messageText, witContext).then((newContext => { setContextInSession(newContext) }
)).catch(reject);
Would this be the correct way of handling it ?
Off course you have to store your context state, you decide how to store it. But, take into account what is the most efficient way if you're gonna have a lot of users, and your reasources available.
As you can see in the official example for nodeJs, there's a method named findOrCreateSession on https://github.com/wit-ai/node-wit/blob/master/examples/messenger.js they get the session before the wit actions are called.
In my particular case, I am storing it in the database, so I get the session before the action is called, so I can send the context, then in the actions I query the session again to modify the resulting context and store it again, try the best implementation for your needs.

Resources