Alexa Recursive Intent Delegation when using Confirmations - node.js

I am writing an Alexa dialog with intent confirmation. When confirmation is denied I want to restart the same dialog again by delegating to this very dialog. I am proceeding like described in this stack overflow question. As described in the solution to this question I do the delegation when the dialogState is still IN_PROGRESS. In my case Alexa always responds with the not very meaningful message There was a problem with the requested skill's response. No error message in the application log.
My skill model and the lambda code are as follows:
{
"interactionModel": {
"languageModel": {
"invocationName": "hello",
"intents": [
{
"name": "UserIntent",
"slots": [
{
"name": "UserName",
"type": "AMAZON.FirstName",
"samples": [
"My name is {UserName}",
"I am {UserName}",
"{UserName}"
]
}
],
"samples": [
"My name is {UserName}",
"I am {UserName}"
]
}
],
"types": []
},
"dialog": {
"delegationStrategy": "SKILL_RESPONSE",
"intents": [
{
"name": "UserIntent",
"confirmationRequired": true,
"prompts": {
"confirmation": "Confirm.Intent.UserName"
},
"slots": [
{
"name": "UserName",
"type": "AMAZON.FirstName",
"confirmationRequired": false,
"elicitationRequired": true,
"prompts": {
"elicitation": "Elicit.Slot.UserName"
}
}
]
}
]
},
"prompts": [
{
"id": "Elicit.Slot.UserName",
"variations": [
{
"type": "PlainText",
"value": "What is your name?"
}
]
},
{
"id": "Confirm.Intent.UserName",
"variations": [
{
"type": "PlainText",
"value": "You are {UserName}. Is this right?"
}
]
}
]
}
}
const DeniedUserIntentHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest' &&
request.intent.name === 'UserIntent' &&
request.dialogState === 'IN_PROGRESS' &&
request.intent.confirmationStatus === 'DENIED';
},
async handle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
const currentIntent = request.intent;
const userName = Alexa.getSlotValue(handlerInput.requestEnvelope, 'UserName');
console.log(`DeniedUserIntentHandler:
request.dialogState=${request.dialogState}, request.intent.confirmationStatus=${request.intent.confirmationStatus}, userName=${userName}`);
return handlerInput.responseBuilder
.speak('Username was not confirmed. Please try again.')
.addDelegateDirective({
name: 'UserIntent',
confirmationStatus: 'NONE',
slots: {}
})
.getResponse();
}
};
What I am missing?

You did not specified whether you DeniedUserIntentHandler get triggered or not. if the error is generated inside DeniedUserIntentHandler then it is due to the wrong format of Delegate Directive.
your return response should be like :
return handlerInput.responseBuilder
.speak('Username was not confirmed. Please try again.')
.addDelegateDirective({
"type": "Dialog.Delegate",
"updatedIntent": {
name:"UserIntent",
confirmationStatus:"NONE",
slots: {
UserName: {
name:"UserName",
confirmationStatus:"NONE",
source:"USER"
}
}
}
})
.getResponse();
The reason you are deleting the intent's previous state because you want your intent action to start from beginning.
You can also use your code like this: reference https://forums.developer.amazon.com/questions/92334/answering-no-to-intent-confirmation.html?childToView=206243#comment-206243
currentIntent.confirmationStatus = "NONE";
Object.keys(currentIntent.slots).forEach(
(slotName) => {
var slot = intent.slots[slotName];
delete slot.value;
slot.confirmationStatus = "NONE";
}
);
var delegatedirective = {"type": "Dialog.Delegate",
"updatedIntent": currentIntent};

The problem you are facing now is real one. This issue is also mentioned in amazon forum.
However you can achieve similar behavior with slight modification. Activate slot value confirmation for UserName and remove confirmation for UserIntent.
Your interaction model would be similar like below:
{
"interactionModel": {
"languageModel": {
"invocationName": "demo app",
"intents": [
{
"name": "UserIntent",
"slots": [
{
"name": "UserName",
"type": "AMAZON.FirstName",
"samples": [
"My name is {UserName}",
"I am {UserName}",
"{UserName}"
]
}
],
"samples": [
"My name is {UserName}",
"I am {UserName}"
]
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
}
],
"types": []
},
"dialog": {
"intents": [
{
"name": "UserIntent",
"delegationStrategy": "SKILL_RESPONSE",
"confirmationRequired": false,
"prompts": {},
"slots": [
{
"name": "UserName",
"type": "AMAZON.FirstName",
"confirmationRequired": true,
"elicitationRequired": true,
"prompts": {
"confirmation": "Confirm.Slot.247378890994.1277345498514",
"elicitation": "Elicit.Slot.UserName"
}
}
]
}
],
"delegationStrategy": "ALWAYS"
},
"prompts": [
{
"id": "Elicit.Slot.UserName",
"variations": [
{
"type": "PlainText",
"value": "What is your name?"
}
]
},
{
"id": "Confirm.Slot.247378890994.1277345498514",
"variations": [
{
"type": "PlainText",
"value": "your name is {UserName} , right ?"
}
]
}
]
}
}
you can add this single code handler:
const UserIntenStartedHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest' &&
request.intent.name === 'UserIntent';
},
async handle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
const currentIntent = Alexa.getIntentName(handlerInput.requestEnvelope);
const slot = Alexa.getSlot(handlerInput.requestEnvelope, 'UserName');
if (slot.confirmationStatus !== 'CONFIRMED') {
return handlerInput.responseBuilder
.addDelegateDirective(request.intent)
.getResponse();
} else {
return handlerInput.responseBuilder
.speak('your Final name is ' + slot.value + '. cheers')
.withShouldEndSession(true)
.getResponse();
}
}
};

Thanks to the reply of #tahiat I was able to figure out my original problem. In the updated intent the slots object must contain the intent's slots (without a value). But his first code snippet contains an error. Ether use
.addDirective({
"type": "Dialog.Delegate",
"updatedIntent": {
name:"UserIntent",
...
}
})
or use
.addDelegateDirective({
name:"UserIntent",
...
})
since addDelegateDirective expects an intent as a parameter.
But now I am facing another problem. I use confirmation in my dialog. When I go back to the initial state of UserIntent after confirmation was denied I never get prompted with the confirmation message. This is because request.intent.confirmationStatus keeps its value, which is 'DENIED', although I have reset it in updateIntent to 'NONE'.

Related

Unable to send custom payload via DialogFlow Fulfilment npm for Slack

I am unable to send a custom payload back to dialogflow from my nodejs webhook code for SLACK platform.
const {WebhookClient, Payload, Platforms, Suggestion} = require('dialogflow-fulfillment');
let payloadObj = new Payload(Platforms.SLACK, questionStringToSend);
agent.add(payloadObj);
Here, questionStringToSend is the JSON payload that i want to send.
Any help would be appreciated.
Structure of my JSON is below:
{
"blocks":[
{
"type":"section",
"text":{
"type":"mrkdwn",
"text":"How do you rate the company?"
}
},
{
"type":"actions",
"elements":[
{
"type":"button",
"text":{
"type":"plain_text",
"text":0
},
"value":0
},
{
"type":"button",
"text":{
"type":"plain_text",
"text":1
},
"value":1
}
]
}
]
}
While sending a response from webhook the format of json is very important Link.
Custom payload response is a json file which has a specific structure and if the structure isn't followed we won't get the expected response.
So the json file can be edited as follows:
{
"fulfillmentMessages": [
{
"payload": {
"slack": {
"attachments": [
{"blocks":[
{
"type":"section",
"text":{
"type":"mrkdwn",
"text":"How do you rate the company?"
}
},
{
"type":"actions",
"elements":[
{
"type":"button",
"text":{
"type":"plain_text",
"text":"0"
},
"value":"0",
"action_id": "button"
},
{
"type":"button",
"text":{
"type":"plain_text",
"text":"1"
},
"value":"1"
}
]
}
]
}
]
}
}
}
]
}
try this
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "How do you rate the company?"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "0"
},
"value": "0"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "1"
},
"value": "1"
}
]
}
]
}

Swagger.json generating with incorrect case "type": "String"

In troubleshooting this problem we found that swagger.json contained incorrect case for
"type": "String"
in the generated code
"/api/FrameLookUp": {
"post": {
"tags": [
"Frame"
],
"operationId": "FrameLookup",
"consumes": [
"application/json-patch+json",
"application/json",
"text/json",
"application/*+json"
],
"produces": [
"application/json"
],
"parameters": [
{
"in": "header",
"name": "Authorization",
"description": "access token",
"required": true,
"type": "String"
},
{
"in": "body",
"name": "body",
"schema": {
"$ref": "#/definitions/FrameRequest"
}
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/FrameResponse"
}
}
}
}
}
}
I have the following ISchemaFilter
public class SwaggerEnumFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (model == null)
throw new ArgumentNullException("model");
if (context == null)
throw new ArgumentNullException("context");
if (context.Type.IsEnum)
model.Extensions.Add(
"x-ms-enum",
new OpenApiObject
{
["name"] = new OpenApiString(context.Type.Name),
["modelAsString"] = new OpenApiBoolean(false)
}
);
}
}
What could be causing this?
It turned out to be that I was using
services.AddSwaggerGen(c =>
{
c.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
and inside the class I had
Schema = new OpenApiSchema() { Type = "String" },
it should have had "string" as lower case.

Dialogflow custom payload not posting message in facebook messenger

I have a custom payload message carousel as one of my intents response in Dialogflow. It looks like this.
{
"facebook": {
"attachment": {
"type": "template",
"payload": {
"template_type": "generic",
"elements": [
{
"title": "Welcome!",
"subtitle": "We have the right hat for everyone.We have the right hat for everyone.We have the right hat for everyone.",
"imageUrl": "https://www.stepforwardmichigan.org/wp-content/uploads/2017/03/step-foward-fb-1200x628-house.jpg",
"buttons": [
{
"postback": "https://f1948e04.ngrok.io",
"text": "View Website"
},
{
"text": "Start Chatting",
"postback": "PAYLOAD EXAMPLE"
}
]
},
{
"title": "Welcome!",
"imageUrl": "https://www.stepforwardmichigan.org/wp-content/uploads/2017/03/step-foward-fb-1200x628-house.jpg",
"subtitle": "We have the right hat for everyone.We have the right hat for everyone.We have the right hat for everyone.",
"buttons": [
{
"postback": "https://f1948e04.ngrok.io",
"text": "View Website"
},
{
"text": "Start Chatting",
"postback": "PAYLOAD EXAMPLE"
}
]
}
]
}
}
}
}
I use the following code inside my node.js webhook to handle messages.
function handleCardMessages(messages, sender) {
let elements = [];
for (var m = 0; m < messages.length; m++) {
let message = messages[m];
let buttons = [];
for (var b = 0; b < message.card.buttons.length; b++) {
let isLink = (message.card.buttons[b].postback.substring(0, 4) === 'http');
let button;
if (isLink) {
button = {
"type": "web_url",
"title": message.card.buttons[b].text,
"url": message.card.buttons[b].postback
}
} else {
button = {
"type": "postback",
"title": message.card.buttons[b].text,
"payload": message.card.buttons[b].postback
}
}
buttons.push(button);
}
let element = {
"title": message.card.title,
"image_url":message.card.imageUri,
"subtitle": message.card.subtitle,
"buttons": buttons
};
elements.push(element);
}
sendGenericMessage(sender, elements);
}
function sendGenericMessage(recipientId, elements) {
var messageData = {
recipient: {
id: recipientId
},
message: {
attachment: {
type: "template",
payload: {
template_type: "generic",
elements: elements
}
}
}
};
callSendAPI(messageData);
}
function callSendAPI(messageData) {
request({
uri: 'https://graph.facebook.com/v3.2/me/messages',
qs: {
access_token: config.FB_PAGE_TOKEN
},
method: 'POST',
json: messageData
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
var recipientId = body.recipient_id;
var messageId = body.message_id;
if (messageId) {
console.log("Successfully sent message with id %s to recipient %s",
messageId, recipientId);
} else {
console.log("Successfully called Send API for recipient %s",
recipientId);
}
} else {
console.error("Failed calling Send API", response.statusCode, response.statusMessage, body.error);
}
});
}
So, everytime the intent is called. Dialogflow is supposed to post the custom payload message to the user's messenger app. But, i don't know why the message is not being posted.
If I use Dialogflow's quickreply/card messages response option under it's facebook messenger response option. Everything works fine. But, if i want to send a quickreply/card message using custom payload, the message is not displayed in the user's messenger when the intent is called. Logs looks fine. Don't know what i am doing wrong. Help will be appreciated.
hi here is how it should be formated like if your are using a custom webhook
{
"fulfillmentMessages": [
{
"payload": {
"facebook": {
"attachment": {
"type": "template",
"payload": {
"template_type":"generic",
"elements":[
{
"title":"Welcome!",
"image_url":"https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png",
"subtitle":"We have the right hat for everyone.",
"default_action":{
"type":"web_url",
"url":"https://www.google.com/",
"webview_height_ratio":"tall"
},
"buttons":[
{
"type":"web_url",
"url":"https://www.google.com/",
"title":"View Website"
},
{
"type":"postback",
"title":"Start Chatting",
"payload":"DEVELOPER_DEFINED_PAYLOAD"
}
]
},
{
"title":"Welcome!",
"image_url":"https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png",
"subtitle":"We have the right hat for everyone.",
"default_action":{
"type":"web_url",
"url":"https://www.google.com/",
"webview_height_ratio":"tall"
},
"buttons":[
{
"type":"web_url",
"url":"https://www.google.com/",
"title":"View Website"
},
{
"type":"postback",
"title":"Start Chatting",
"payload":"DEVELOPER_DEFINED_PAYLOAD"
}
]
}
]}
}
}
}
}
]
}
if you want to use the build in one it should be like this
from other answer in here
{
"facebook": {
"attachment": {
"type": "template",
"payload": {
"template_type": "generic",
"elements": [
{
"title": "Welcome!",
"image_url": "https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png",
"subtitle": "We have the right hat for everyone.",
"default_action": {
"type": "web_url",
"url": "https://commons.wikimedia.org/wiki/File:Example.png",
"messenger_extensions": false,
"webview_height_ratio": "tall",
"fallback_url": "https://website.com/"
},
"buttons": [
{
"type": "web_url",
"url": "https://commons.wikimedia.org/wiki/File:Example.png",
"title": "View Website"
},
{
"type": "postback",
"title": "Start Chatting",
"payload": "DEVELOPER_DEFINED_PAYLOAD"
}
]
}
]
}
}
}
}

How to get custom intent slot values using handlerInput in ASK-SDK v2

I'm creating a basic calculator skill using ASK-SDK v2. I'm not sure how to get the slot values provided by the user into the Lambda code with the new version. I was able to make it work with the older version.
Conversation
User: Open calculate
Alexa: You can ask me to add, subtract, multiply and divide
User: Add two and three
Alexa: Sum of 2 and 3 is 5
Below is my IntentSchema
{
"interactionModel": {
"languageModel": {
"invocationName": "calculate",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AddIntent",
"slots": [
{
"name": "numA",
"type": "AMAZON.NUMBER"
},
{
"name": "numB",
"type": "AMAZON.NUMBER"
}
],
"samples": [
"Sum of {numA} and {numB}",
"add {numA} and {numB}"
]
},
{
"name": "SubIntent",
"slots": [
{
"name": "numA",
"type": "AMAZON.NUMBER"
},
{
"name": "numB",
"type": "AMAZON.NUMBER"
}
],
"samples": [
"difference between {numA} and {numB}",
"subtract {numA} from {numB}"
]
},
{
"name": "ProductIntent",
"slots": [
{
"name": "numA",
"type": "AMAZON.NUMBER"
},
{
"name": "numB",
"type": "AMAZON.NUMBER"
}
],
"samples": [
"multiply {numA} and {numB}",
"product of {numA} and {numB}"
]
},
{
"name": "DivideIntent",
"slots": [
{
"name": "numA",
"type": "AMAZON.NUMBER"
},
{
"name": "numB",
"type": "AMAZON.NUMBER"
}
],
"samples": [
"divide {numB} by {numA}",
"divide {numA} by {numB}"
]
},
{
"name": "ExponentialIntent",
"slots": [
{
"name": "numA",
"type": "AMAZON.NUMBER"
},
{
"name": "numB",
"type": "AMAZON.NUMBER"
},
{
"name": "numC",
"type": "AMAZON.NUMBER"
}
],
"samples": [
"{numA} raised to the power of {numB} by {numC}",
"{numA} raised to the power {numB}"
]
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
}
],
"types": []
}
}
}
I'm adding the addintenthandler here. Please tell me if the approach I'm using to get the slot values from the intent is correct or if I should use sessionattributes
const AddIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'AddIntent';
},
handle(handlerInput) {
var output1 = "";
var num1 = handlerInput.resuestEnvelope.request.intent.slots.numA.value;
var num2 = handlerInput.resuestEnvelope.request.intent.slots.numB.value;
if((num1)&&(num2)){
output1 = 'The sum of ' +num1+ ' and ' +num2+ ' is ' + (num1+num2);
}
else {
output1 = 'Enter valid number';
}
const speechText = output1;
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.getResponse();
}
};
Alexa responds with "Unable to process requested skill response"
Any help is welcome
Update: there are now built-in functions in the SDK for this:
Alexa.getSlotValue() (returns the string value) and getSlot() (returns Slot object)
Alexa.getSlotValue(handlerInput.requestEnvelope, "someSlotName")
Old answer:
You have a typo, resuestEnvelope should be requestEnvelope. In any case I have created exactly the same skill, a calculator (in Spanish but it's basically the same thing) and I use a helper function called getSlotValues() which I encourage you to reuse. It will also work great when you have to capture custom slots (which are processed differently because the entity resolution structure is different):
https://github.com/germanviscuso/skill-sample-nodejs-mycalculator

Mongoose Querying Sub-Document Properties

I am trying to expose this functionality through a WEB API. The way it is set up in this code is that someone does a GET on URL providing a querystring along with it in the form of:
?field=value&anotherfield.subproperty=value
But I can't seem to get querying based on sub-document properties to work. Below you will find my barebones code, record I'm trying to receive and both of my test cases.
Code:
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var config = require('../config');
var User = require('../models/user');
var functions = require('../functions');
router.get('/', function(req,res,next) {
//Check Permissions associated with UID *TODO
var parameters = req.query;
console.log(parameters);
User.find(parameters, function(err, users) {
if (err)
{
json = functions.generateOperationOutcome("exception","error",err,"exception");
res.status(500);
res.json(json);
}
else
{
//Check for blank result
if (users.length === 0)
{
json = functions.generateOperationOutcome("not-found","warning","Non-Existent Resource","warning");
res.status(404);
res.json(json);
}
else {
res.status(200);
res.json(users);
}
}
});
});
Record:
{
"_id": "5871d2e814946a941d8611fb",
"resourceType": "testResource",
"link": [],
"communication": [],
"animal": {
"genderStatus": {
"coding": []
},
"breed": {
"coding": []
},
"species": {
"coding": []
}
},
"contact": [],
"photo": [],
"maritalStatus": {
"coding": []
},
"address": [],
"gender": "unknown",
"telecom": [
{
"system": "phone",
"value": "2019196553",
"use": "home"
}
],
"name": {
"suffix": [],
"prefix": [],
"given": [],
"family": []
},
"identifier": [
{
"use": "official",
"type": {
"coding": {
"system": "kylec laptop",
"version": "0.01",
"code": "UDI",
"display": "Universal Device Identifier",
"userSelected": false
},
"text": "test"
},
"system": "test system",
"value": "test value",
"assigner": {
"reference": "test assigner reference"
},
"period": {
"start": "1992-12-31T09:59:59+00:00"
}
}
]
}
Successful Query:
GET http://{{LOCAL}}/api/user?resourceType=testResource
Returns this one model from MongoDB.
Unsuccessful Query (no documents matching query found):
GET http://{{LOCAL}}/api/user?telecom.system=phone
Returns no models back and results in a 404.
You are not properly using dot-notation, as the property you're seeking is within an array:
"telecom": [
{
"system": "phone",
"value": "2019196553",
"use": "home"
}
]
Querying array content typically would require you to do a join against the array (for DocumentDB queries), and not a simple find().
If you wanted the ability to use dot-notation here, you'd need to create a subdocument, like:
"telecom": {
"system": "phone",
"value": "2019196553",
"use": "home"
}
At this point, you'd be able to address properties such as telecom.system, telecom.value, and telecom.use.

Resources