Dialogflow node.js fulfilment with multiple entities - dialogflow-es

I hope someone can help me with this.
I have built an application that uses node.js to fulfil my intent in dialogflow.
For example, I have an intent with one required action:
It goes to my fulfilment:
// Handle the Dialogflow intent named 'Default Welcome Intent'.
app.intent(DEFAULT_INTENT, (conv, params) => {
let categoryId = params.category;
let options = {
'method': 'GET',
'url': apiUrl + 'questions/categories/' + categoryId + '/scenario',
'json': true
};
return request(options).then(response => {
// TODO: What happens if there is more than one question?
let question = response[0];
conv.ask(question.text);
}, error => {
conv.ask('There was an issue with the request: ' + options.url);
});
});
As you can see, this asks a question based on the category sent to the fulfilment.
The problem I have is that the response I want from the user is different for each question.
Once they have responded, it will also have a fulfilment that will ask another question.
Is it possible to do it this way and if so, can someone give me an example of how? If not, can someone help me work out what the alternative is?

The approach you're using makes sense. The key thing to remember is that Intents capture what the user says, not how you handle what they say. You can influence which Intent gets triggered by setting an Input Context, and making sure you have previously set an Output Context for it.
One possible approach would be that for each question you're asking, you set a corresponding Output Context for that question. You can then have one or more Intents that take this as the Input Context. These are otherwise regular Intents, so you'd handle them normally. You might want to clear the context (by setting its lifespan to 0) after it matches, so you don't accidentally match it later.
For example, if your question contains, not only the text of the question, but also the context name of the question, the code might look something like this:
conv.ask( question.text );
conv.contexts.set( question.contextName, 5 );
Let's say that the question object looks something like this
{
text: "What is your favorite color?",
contextName: "color-favorite"
}
You might have a Dialogflow Intent that handles this that looks something like this
Note that the Output Context has explicitly set it to 0, which will remove it. You can also do this in your fulfillment code with something like
conv.contexts.delete( 'color-favorite' );

Related

How to use Parameters to modify Context in Google Dialogflow

We are running an experiment, where I need to manipulate the dialog flow responses based on the participant's ID. My thought is there is a way to set the Output Context based on a parameter value.
For example, we have a prompt that asks for the participant's id. This matches an intent with a "participantID" parameter. Now what I would like to do is set the output context to be the value of the "participantID" parameter. Then I could set the input context to be a specific "participantID".
Any parameters set on an intent will be kept in the output context and recoverable in posterior follow-up intents within the lifespan of such context.
Through the Dialogflow UI you can reference them in the initial response as $<parameter-name>.
For example, assuming your parameter name is id, the response can be: Welcome player $id,....
For values in the context in posterior follow-up intents use #<context-name>.<parameter-name>.
For example, in a second follow-up which has parameter answer and input context id-followup use Your answer has been $answer, if you are not player #id-followup.id please let me know your actual id.
If you need to use the Fulfillment Webhook I recommend a structure like the one illustrated here, i.e. a structure like:
//The user inputs a participant ID (name of the parameter is id)
function answers(agent){
const id = agent.parameters.id;
agent.add(`So your id is ${id}`);
agent.add(`Any extra questions...`);
agent.setContext({
name: 'question',
lifespan: 5, // Amount of follow-up intents this context will be kept
parameters: {'cid': id},
});
}
// Next functions that use the participant id as input context
function answers2(agent){
// Get the context and from it extract the id value
const cont = agent.getContext('question');
const particular_id = cont.parameters.cid;
// The rest of your code
Some documentation you may find useful includes Input and output contexts, Parameter reference,...
Depending on what version of dialogflow-fulfillment you are using, I'm talking about 0.6.1 here. The syntax for setting context is:
agent.context.set({
name: "ctx_name",
lifespan: 5,
parameters: {'cid': id},
});

Add additional links response to knowledge base in DialogFlow

With knowledge bases in DialogFlow you can have a basic FAQ upload. First column is the question and second is the answer. Often we need to have the answer provide an additional link. e.g., "The answer to your question is no, pineapple does not go on pizza". More info
What I want to provide is the answer followed by a rich response link. Since you cannot have a 3rd column in the spreadsheet to add a link, how can I do this cleanly? Sure, I can just have the link as part of the answer text, but that's not as pretty.
I was able to duplicate the question and then provide a link as the second answer (i.e., $Knowledge.Answer(2)) but then some answers don't have links and I can't make this conditional.
I assume I can do this in Fulfilment but I'm not sure of the actual code that can return the answer ($Knowledge.Answer(1)) and then conditionally add a rich response with a link.
I finally worked it out.
First, I used some delimiter in my answer (here just 'Z$Z').
For example, in the csv file I put the following in a second column:
The answer to your question is no, pineapple does not go on pizza$Z$More Info$Z$https://www.mashed.com/183299/the-most-controversial-pizza-toppings-explained/
This way when I get to the fulfillment I can just parse the answer.
Here's a cut of the code:
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
// save off the answer to parse
let answer = JSON.stringify(request.body.queryResult.fulfillmentText);
function faqHandler(agent) {
let answerAlt = answer.slice(1,-1); // remove first and last double quote
let answerParse = answerAlt.split("Z$Z"); // parse the answer
if( answerParse.length == 1) {
// no links
agent.add(answerParse[0].toString());
} else {
// display the content with the text and links
// you can use different ways here to display but you have access to
// the answer, title, and url as follows
// answerParse[0].toString(), answerParse[1].toString(), answerParse[1].toString()
}
}
...
intentMap.set('Knowledge.KnowledgeBase.ID_OF_KNOWLEDGEBASE',faqHandler);

What is the best practice to avoid utterance conflicts in an Alexa Skill

In the screenshot below, I have got an utterance conflict, which is obvious because I am using similar patterns of samples in both the utterances.
My question is, the skill I am developing requires similar kind of patterns in multiple utterances and I cannot force users to say something like “Yes I want to continue”, or “I want to store…”, something like this.
In such a scenario what is the best practice to avoid utterance conflicts and that too having the multiple similar patterns?
I can use a single utterance and based on what a user says, I can decide what to do.
Here is an example of what I have in my mind:
User says something against {note}
In the skill I check this:
if(this$inputs.note.value === "no") {
// auto route to stop intent
} else if(this$inputs.note.value === "yes") {
// stays inside the same intent
} else {
// does the database stuff and saves the value.
// then asks the user whether he wants to continue
}
The above loop continues until the user says “no”.
But is this the right way to do it? If not, what is the best practice?
Please suggest.
The issue is really that for those two intents you have slots with no context around them. I'm also assuming you're using these slots as catch-all slots meaning you want to capture everything the person says.
From experience: this is very difficult/annoying to implement and will not result in a good user experience.
For the HaveMoreNotesIntent what you want to do is have a separate YesIntent and NoIntent and then route the user to the correct function/intent based on the intent history (aka context). You'll have to just enable this in your config file.
YesIntent() {
console.log(this.$user.$context.prev[0].request.intent);
// Check if last intent was either of the following
if (
['TutorialState.TutorialStartIntent', 'TutorialLearnIntent'].includes(
this.$user.$context.prev[0].request.intent
)
) {
return this.toStateIntent('TutorialState', 'TutorialTrainIntent');
} else {
return this.toStateIntent('TutorialState', 'TutorialLearnIntent');
}
}
OR if you are inside a state you can have yes and no intents inside that state that will only work in that state.
ISPBuyState: {
async _buySpecificPack() {
console.log('_buySpecificPack');
this.$speech.addText(
'Right now I have a "sports expansion pack". Would you like to hear more about it?'
);
return this.ask(this.$speech);
},
async YesIntent() {
console.log('ISPBuyState.YesIntent');
this.$session.$data.productReferenceName = 'sports';
return this.toStatelessIntent('buy_intent');
},
async NoIntent() {
console.log('ISPBuyState.NoIntent');
return this.toStatelessIntent('LAUNCH');
},
async CancelIntent() {
console.log('ISPBuyState.CancelIntent()');
return this.toStatelessIntent('LAUNCH');
}
}
I hope this helps!

Alexa voice with Jovo Framework: Is this possible to get IntentName by utterance? How can I decide which intent to jump to based on user utterance?

I have built an Alexa skill with the following flow:
LAUNCH -> AccountLinkingIntent -> CampaignIntent
In AccountLinkingIntent, presently I am routing to CampaignIntent if Account is already linked.
Up to this everything is working fine. Now I have to add another Intent ActiveContactIntent so that the flow becomes:
LAUNCH -> AccountLinkingIntent -> CampaignIntent / ActiveContactIntent
i.e, From AccountLinking I need to decide which Intent to route to.
The invocation goes like this (CampaignIntent):
Alexa, ask <invocation_name> to get my latest campaign result
OR (ActiveContactIntent)
Alexa, ask <invocation_name> who is my most active contact
Based on the utterance, I need to tell Alexa where to go. So far I have the following in AccountLinkingIntent
...
return this.toIntent("CampaignIntent");
...
But now I need to decide the same as this:
...
if( ... ) {
return this.toIntent("CampaignIntent");
} else {
return this.toIntent("ActiveContactIntent");
}
...
Is there any way to get the IntentName by the utterance so that I can check by the same such as:
...
if( intent_name_by_utterance === "CampaignIntent" ) {
return this.toIntent("CampaignIntent");
} else {
return this.toIntent("ActiveContactIntent");
}
...
Or probably, if it is possible to get intent_name_by_utterance I may also pass the value as the argument of toIntent method if it is allowed to pass a variable!
return this.toIntent(intent_name_by_utterance);
UPDATE:
I have tried the following to see whether the intent name is being returned:
LAUNCH() {
return this.toIntent("LinkAccountIntent");
},
async LinkAccountIntent() {
const intent_name = this.$request.getIntentName();
this.tell(Current intent is: ${intent_name});
},
Invoked the skill in following two fashions:
Alexa, ask <invocation-name> to give me my latest campaign results
Alexa, ask <invocation-name> who is my most active contact
give me my latest campaign results AND who is my most active contact are the utterances for respective intents.
I am using Alexa Test console for testing. In both cases, I was expecting the name of the intent (this.$request.getIntentName()), ending up with
Hmm, I don't know that one.
My intention is to call an intent by its utterance directly by waking up the skill using its invocation name.
Any suggestion?
I think you should treat both intends separately and not through the launch, because for example in the case
"Alexa, ask who is my most active contact"
Alexa skips the launch and jumps directly to resolve the intend ActiveContactIntent.
So just as you have the LAUNCH(){} function you must also have CampaignIntent(){} and ActiveContactIntent(){}.
That way you will avoid Alexa answering
Hmm, I don't know that one.
To verify that the user has already linked his account, you would have to enter a code like the one below:
if (!this.$request.getAccessToken()) {
this.$alexaSkill.showAccountLinkingCard();
this.tell('Please link you Account');
} else {
//code for your respective action
}
I recommend you to check the documentation about "routing with jovo" to have a little more clarity about this topic. You can review it at the following link:
https://www.jovo.tech/docs/routing

botframework choice invalid response typed

I am using nodejs SDK for creating my bot with MSFT botframework.
The code snippet is as follows:
function(session, args, next){
builder.Prompts.choice(session, "Please select one of the options:", ['AAA', 'BBB','CCC'], {retryPrompt: "Invalid choice, Please pick below listed choices",
listStyle: builder.ListStyle.button,
maxRetries: 1
});
},
function(session,results){
if (results.response) {
//Do something
}
}
I have 2 questions:
I would like to navigate to a different dialog Flow in case the user types anything other then the options("AAA","BBB","CCC"). Is that possible?
I would like to change the retryPrompt everytime lets say pick the utterances from a list. Is that possible?
I would like to navigate to a different dialog Flow in case the user types anything other then the options("AAA","BBB","CCC"). Is that possible?
Yes, it's possible. You can define several dialogs with required waterfall steps related to the choices. Like:
bot.dialog('AAA',[...])
And leverage replaceDialog to redirect your user to new dialog flows:
(session,result)=>{
//result.response.entity should be one of string `AAA`,`BBB`,`CCC`
session.replaceDialog(result.response.entity);
}
I would like to change the retryPrompt everytime lets say pick the utterances from a list. Is that possible?
Yes, it's possible. According the choice definition, we can set options extends IPromptChoiceOptions which extends [IPromptOptions][2], we can find that retryPrompt?: TextOrMessageType;, dig into the source code for the definition of TextOrMessageType:
/**
* Flexible range of possible prompts that can be sent to a user.
* * _{string}_ - A simple message to send the user.
* * _{string[]}_ - Array of possible messages to send the user. One will be chosen at random.
...
...
*/
export type TextOrMessageType = string|string[]|IMessage|IIsMessage;
So we can set a string list for retryPrompt, bot builder will pick one randomly. Please try following:
builder.Prompts.choice(session, "Please select one of the options:", ['AAA', 'BBB', 'CCC'], {
listStyle: builder.ListStyle.button,
maxRetries: 1,
retryPrompt:['utterance 1','utterance 2','utterance 3','utterance 4']
});
As you can call a function in retryPrompt you can do both of them:
builder.Prompts.choice(
session,
'This is just a question?',
'Yes|No',
{ retryPrompt: particularRetry() }
);
In the above, you can do what you want in particularRetry function.
For example for the second question, you can call the new propmt with new choices.
You can see more details in this post.

Resources