I have a scenario where the first question is "How many people are in 2018" and returns 2. I then want to continue the conversation by asking "How many male and female are there".
It should return 1 male and 1 female without asking the year inferring form previous question.
How can Dialogflow and my fulfillment code infer the year?
I have tried to get the total counts, and can also get the male and female counts, but I'm not sure how to continue the conversation in a natural way.
function countresponders2018(agent){
const year = agent.parameters.year;
return admin.database().ref('data').once('value')
.then((snapshot) => {
let totalCount=0;
snapshot.forEach( childSnapshot => {
var value = childSnapshot.val();
if (value.SurveyYear==year){
totalCount+=1;
}
});
agent.add(`The total responders are ${totalCount}`);
});
This is a great question, and important to think about when designing good conversations.
I'm going to ignore the database queries, since it sounds like you may have this part under control, and it isn't directly related to the conversational parts.
Restating the problem
To rephrase what you want to do, you'd like to handle all the following phrases the same way:
How many people are in 2018?
In 2019, how many people are there?
How many people are there?
Similarly, these should all be the same:
How many male and female are there in 2018?
In 2017, how many male and female are there?
How many male and female are there?
Can you break that down by men and women?
ie - users should be able to ask questions, both specifying the year, and possibly without.
During the conversation, if they specify the year, this value should be the one assumed for further questions unless they specify a different year. This is pretty natural - humans build up short-term context in our conversations.
These also have an issue you didn't raise in your question - how to handle a question if they haven't yet specified a year. Good conversation design would suggest that you should ask for the year, and then execute the question that was originally asked. So the conversation might be something like this:
User: How many people are there?
Agent: What year would you like that for?
User: 2018
Agent: There were 2 people in 2018
Overall approach: Contexts
Fortunately - Dialogflow supports this concept directly with Contexts. This lets you save parameters in between statements from the user, so this can be a good way to store the year for later questions.
You an also make Intents that can only be triggered when specific Contexts are active. This can be useful to make sure which Intents make sense at parts of the conversation.
There are two good approaches to using Contexts for these kinds of questions. Although each has trade-offs, which one you use is largely a matter of personal style. You can also use contexts to support the scenario where you need to ask for the year if we don't already have it.
Approach 1: Single Intent per question
With this scheme, you would have a single Intent that responds to each question from the user. Your fulfillment would see if the year parameter is set and, if so, use that parameter as the year and set it in a Context's parameters. If it isn't set - then use the value from the parameters in the Context.
So our "askPeople" Intent might have the phrases we talked about above:
How many people are in [year]?
In [year], how many people are there?
How many people are there?
And we define "year" as a parameter of the #sys.number-integer for example. (Other entity types are possible, but this will do for now.)
If you're using the dialogflow-fulfillment library, our handler function for this might look something like this:
function askPeople( agent ){
// Try to get the year from the context first
let context = agent.context.get('RememberYear');
let year = context && context.parameters && context.parameters.year;
// If we don't have a year, get it from the phrase parameters
year = year || agent.parameters.year;
if( year ){
// If we do have a value, get the result, reply,
// and save the year in the context.
return getPeople( year )
.then( count => replyPeople( agent, year, count ) );
} else {
// We don't have a value
// FIXME: We'll discuss what to do about this below
}
}
You'll need to write the getPeople() function to return a Promise that resolves to the results from your database. I also pulled out the replyPeople() function intentionally. It might look something like this
function replyPeople( agent, year, count ){
agent.context.set({
name: 'RememberYear',
lifespan: 99,
parameters:{
year: year
}
});
agent.add( `The total count for ${year} was ${count}.` );
}
Approach 2: Multiple Intents per question
With this, we will have two different Intents that handle the question. One accepts it with the year, while the other handles the phrase without the year. The big difference is that the one without the year in the training phrase will require the "RememberYear" Context to be set in order to trigger.
The base Intent ("askPeopleYear") is the fairly familiar one with training phrases such as
How many people are in [year]?
In [year], how many people are there?
And the "year" parameter defined the same way as above.
Our other Intent ("askPeopleNoYear") would have the Input Context of "RememberYear" set and have a training phrase such as
How many people are there?
And would not have any parameters.
We'll likely need a third Intent, or at least additional way to deal with, what happens if the "RememberYear" Context isn't set, but they say that phrase. We'll discuss this below.
The fulfillment code would require two different handler functions that might look something like this
function askPeopleYear( agent ){
// We know the year is in the parameter
let year = agent.parameters.year;
// Get the result, reply, and set the context
return getPeople( year )
.then( count => replyPeople( agent, year, count ) );
}
function askPeopleNoYear( agent ){
// We know the year is in the context
let context = agent.context.get('RememberYear');
let year = context && context.parameters && context.parameters.year;
// Get the result, reply, and set the context
return getPeople( year )
.then( count => replyPeople( agent, year, count ) );
}
The getPeople() and replyPeople() functions would be the same as in our previous approach.
Dealing with no year set
So what happens if they say "How many people are there", but we don't have the "RememberYear" Context set with a value?
In the first approach, this gets to the else condition which we marked with a "FIXME". In the second approach, a Fallback Intent would be triggered if we don't put something else in place, and this doesn't really help the user.
In either case, what we should do is ask the user for the year they want and create an Intent to capture the year. Since we may need to do this for different question types, we should also store which function to execute in a... you guessed it... Context. So let's assume that we set a "NeedsYear" Context with a parameter named "functionName" to track which function we'll need to call.
This Intent (let's name it "provideYear") would require the "NeedsYear" Input Context and might have training phrases such as:
[year]
Get it for [year]
And take a "year" parameter, same as we've defined above. (We might even mark this as required.)
The handler for this might look something like
function provideYear( agent ){
// We know we have the parmeter
let year = agent.parameters.year;
// We also know we have the context with the function name
let context = agent.context.get('NeedsYear');
let functionName = context && context.parameters && context.parameters.functionName;
// We should clear this context, since we no longer need the year
agent.context.set({
name: 'NeedsYear',
lifespan: 0
};
// And then call whichever function is appropriate
if( functionName === 'people' ){
return getPeople( year )
.then( count => replyPeople( agent, year, count ) );
} else if( functionName === 'gender' ){
// ...
}
}
Summary
Use Contexts.
Contexts serve as good gatekeepers for which Intents can be triggered, and also a good way to store values between turns of a conversation.
Contexts are powerful.
Related
Working with flutter and firestore. I have a collection of widgets. I query these widgets and displaying them to the user one at a time so they can tell me whether they have ever seen this widget in real life before. The query:
firestore.collection('widgets').where('docID', whereNotIn: seenBeforeList).get()
Where 'seenBeforeList' is a array in the user document to keep track of the widgets they have marked as seen.
Let's say I have 20 widgets. The user has marked the first 11 widgets as seen and closes the app. Next launch the query says, give me all the widgets that the user hasn't already seen (the last 9). Essentially, I don't want to show the widgets the user has already marked as seen. However firestore has the, 'value.length <= 10' - filters support a maximum of 10 elements in the value [List], rule so after the user marks the first 10 widgets (seenBeforeList) this query fails.
I can't seem to think of a different solution. Maybe a different data structure...I'm not sure. Am I missing something? Any thoughts would be much appreciated! Thanks in advance!
It sounds lame but I think the usual suggested solution is to just loop for 10/n times and aggregate the gets into one list you then await. This is also the solution I would personally go with
// Note, haven't tested this code at all
Future<List<QuerySnapshot<T>>> getNotInList(
List<String> seenBeforeList,
) async {
final snapshotsFutures = <Future<QuerySnapshot<T>>>[];
for (var i = 0, limit = 10; i < seenBeforeList.length; i++) {
final ids = seenBeforeList
.getRange(i * limit, max((i + 1) * limit, seenBeforeList.length))
.toList();
// final seenBeforeList = ids.slice(i, i * limit); ?
snapshotsFutures.add(
firestore
.collection('widgets')
.where('docID', whereNotIn: ids)
.get(),
);
}
return await Future.wait(snapshotsFutures);
}
For a similar question asked in context of react native, there's this SO answer as well. noSQL definitely has some weird limitations but what can you do :\
Edit
This will not work as some futures will have results that you don't need for the reason their exclude ids are different from others
really appreciate the helps
I have been following this video with this code.
My code looks like this
function angerEmotionCapture(agent) {
const angryTo = agent.parameters.angryDirectedTo;
return admin.database().ref('directedTo').transaction((directedTo)=>{
let target = directedTo;
agent.add(`previous entry was ${target}`);
target = angryTo;
agent.add(`new entry is ${target}`);
return directedTo;
});
}
The purpose of this is to capture a conversation topic and store it in the database.
I'm planning to use it for multiple purposes that's why I don't use context.
This code is only the first step to see if I can capture it properly.
When doing this, the agent response always look like this
previous entry was null
new entry is boss
previous entry was friends
new entry is boss
Here "friends" and "boss" are expected. However, the first repetition is not expected and it always gives null. Despite of that, this correctly update the database
I want to understand why is there a repetition here
Thanks, really appreciate the time
I want to build a realtime quiz game which randomly matches two players (according to their winning rate if they are logged in). I've read through the book Discover Meteor and have a basic understanding of the framework, but I just have no idea of how to implement the matching part. Anyone know how to do that?
if you want to match users who have scores close to each other, you can do something like this : mongodb - Find document with closest integer value
The Meteor code for those Mongo queries is very similar, but there are some subtle differences that are kind of tricky. In Meteor, it would look something like this :
SP // "selected player" = the User you want to match someone up with
var score = SP.score; // selected player's score
var queryLow = {score: {$lte:score},_id:{$ne:SP._id}};
var queryHigh = {score:{$gte:score},_id:{$ne:SP._id}};
// "L" is the player with the closest lower score
var L=Players.findOne(queryLow,{sort:{score:-1},limit:1});
// "H" is the player with the closest higher score
var H=Players.findOne(queryHigh,{sort:{score:1},limit:1});
so, now you have references to the players with scores right above and right below the 'selected player'. In terms of making it random, perhaps start with a simple algorithm like "match me with the next available player who's score is closest" , then if it's too predictable and boring you can throw some randomness into the algorithm.
you can view the above Meteor code working live here http://meteorpad.com/pad/4umMP4iY8AkB9ct2d/ClosestScore
and you can Fork it and mess about with the queries to see how it works.
good luck! Meteor is great, I really like it.
If you add the package peppelg:random-opponent-matcher to your application, you can match together opponents like this:
On the server, you need to have an instance of RandomOpponentMatcher like this:
new RandomOpponentMatcher('my-matcher', {name: 'fifo'}, function(user1, user2){
// Create the match/game they should play.
})
The function you pass to RandomOpponentMatcher will get called when two users been matched to play against each other. In it, you'll probably want to create the match the users should play against each other (this package does only match opponents together, it does not contain any functionality for playing games/matches).
On the client, you need to create an instance of RandomOpponentMatcher as well, but you only pass the name to it (the same name as you used on the server):
myMatcher = new RandomOpponentMatcher('my-matcher')
Then when the users is logged in and which to be matched with a random opponent, all you need to do is to call the add method. For example:
<template name="myTemplate">
<button class="clickMatchesWithOpponent">Match me with someone!</button>
</template>
Template.myTemplate.events({
'click .clickMatchesWithOpponent': function(event, template){
myMatcher.add()
}
})
When two different logged in users has clicked on the button, the function you passed to RandomOpponentMatcher on the server will get called.
One implementation might be as follows:
A user somehow triggers a 'looking for game' event that sets an attribute on user.profile.lookingForGame to true. The event then makes a call to a server side Meteor method which queries for all other online users looking for games.
From there you it really depends on how you want to handle users once they 'match'.
To determine all online users, try using the User Status package:
https://github.com/mizzao/meteor-user-status
Once added, any online user will have an attribute in the profile object of 'online'. You can use this to query for all online users.
I'm creating an angular app with firebase back end as an API.
People will be able to book appointments on a calendar but 2 persons CANNOT share the same hour slot. Furthermore, since appointments can start every half and hour, checking becomes a bit more complex.
Can I make firebase perform some more complex validation like that? It pretty much covers everything else I need and I'd hate to create something custom, only because of that feature!
Thanks!
If I am correct, you said you don't want to do the checking client-side. Sounds good. Here's what I would do:
When storing the appointments, I would name them based on their times. Client-side code:
var dataRef = new Firebase('https://example.firebaseio.com/');
dataRef.child('2014-6-9-0500').set('name'); // 5:00 6/9/2014 converted to a string
So the appointments will all be named based on their time, and their values will be equal to the name of the person being scheduled at that time. This will make it impossible for two people to be scheduled at the same time (because in Firebase, there cannot be two children with the same name).
In your security tab, check that the data doesn't already exist (to prevent over-writing existing appointments). Then, for the validate, you could check the appointment time and make sure it ends with either "00" or "30", and is 12 digits long. The rules would look something like this:
"rules": {
".read": true,
"$time": {
".write": "!data.exists()",
".validate": "$time.endsWith('00') || $time.endsWith('30')"
}
}
Although it's possible for a nonvalid time to be accepted (such as "0000-0-0-0030" or even "qt00"), a valid appointment which is submitted will not be scheduled at the same time as another appointment, and the appointments will be at times ending at ":00" or ":30" (half-hour intervals).
The only problem is if a person has an hour-long appointment starting at noon. You would have to schedule multiple appointments: one at 12:00, and another at 12:30. That could get a little annoying, but I don't know of any other way to do it.
In my understanding, it is possible with Firebase. However, Firebase does not have the dynamic querying capabilities like Mongo does. I believe you have to take a pretty primitive approach to solving this issue by doing something like:
db.child('calendar').once('value', function(snapshot){
var hours = snapshot.val();
// for each hour
// do validation here
})
For a web application, I need to get a list or collection of all SalesOrders that meet the folowing criteria:
Have a WarehouseKey.ID equal to "test", "lucmo" or "Inno"
Have Lines that have a QuantityToBackorder greater than 0
Have Lines that have a RequestedShipDate greater than current day.
I've succesfully used these two methods to retrieve documents, but I can't figure out how return only the ones that meet above criteria.
http://msdn.microsoft.com/en-us/library/cc508527.aspx
http://msdn.microsoft.com/en-us/library/cc508537.aspx
Please help!
Short answer: your query isn't possible through the GP Web Services. Even your warehouse key isn't an accepted criteria for GetSalesOrderList. To do what you want, you'll need to drop to eConnect or direct table access. eConnect has come a long way in .Net if you use the Microsoft.Dynamics.GP.eConnect and Microsoft.Dynamics.GP.eConnect.Serialization libraries (which I highly recommend). Even in eConnect, you're stuck with querying based on the document header rather than line item values, though, so direct table access may be the only way you're going to make it work.
In eConnect, the key piece you'll need is generating a valid RQeConnectOutType. Note the "ForList = 1" part. That's important. Since I've done something similar, here's what it might start out as (you'd need to experiment with the capabilities of the WhereClause, I've never done more than a straightforward equal):
private RQeConnectOutType getRequest(string warehouseId)
{
eConnectOut outDoc = new eConnectOut()
{
DOCTYPE = "Sales_Transaction",
OUTPUTTYPE = 1,
FORLIST = 1,
INDEX1FROM = "A001",
INDEX1TO = "Z001",
WhereClause = string.Format("WarehouseId = '{0}'", warehouseId)
};
RQeConnectOutType outType = new RQeConnectOutType()
{
eConnectOut = outDoc
};
return outType;
}
If you have to drop to direct table access, I recommend going through one of the built-in views. In this case, it looks like ReqSOLineView has the fields you need (LOCNCODE for the warehouseIds, QTYBAOR for backordered quantity, and ReqShipDate for requested ship date). Pull the SOPNUMBE and use them in a call to GetSalesOrderByKey.
And yes, hybrid solutions kinda suck rocks, but I've found you really have to adapt if you're going to use GP Web Services for anything with any complexity to it. Personally, I isolate my libraries by access type and then use libraries specific to whatever process I'm using to coordinate them. So I have Integration.GPWebServices, Integration.eConnect, and Integration.Data libraries that I use practically everywhere and then my individual process libraries coordinate on top of those.