Alexa SDK V2 StateHandler - Multiple intents sharing the same slot value - alexa-skills-kit

I am writing an Alexa skill that captures age from one intent and captures weight from different intent. and Basically, these two are of type number.
When I am trying to enter a number for weight, it is being captured in the first Intent's slot. Here is my intent schema.
{
"intents": [
{
"slots": [
{
"name": "AGE",
"type": "AMAZON.NUMBER"
}
],
"intent": "AgeIntent"
},
{
"slots": [
{
"name": "WEIGHT",
"type": "AMAZON.NUMBER"
}
],
"intent": "WeightIntent"
}
]
}
And my sample utterances are
AgeIntent
My age is {AGE}
{AGE}
WeightIntent
My weight is {WEIGHT}
{WEIGHT}
Conversation
User : open my test skill
Alexa: what is your age
User: 28
Alexa: what is your weight
User: 68
Here when user give his weight input 68, instead of matching WeightIntent, it is matching with AgeIntent. I am getting AgeIntent in my request.intent.name.
I know it will work with my weight is 68; and Also I can make it working with StateHandler feature of Alexa-SDK-V1, But I am using Alexa-SDK-V2
So the problem here is: Skill is always sending intentName of first matching intent (i.e. AgeIntent) from interaction model, and I am expecting to get second matching IntentName (i.e. WeightIntent) for my second question.

The solution is simple either you make only 1 intent and use dialog management to get all the slot values you need OR If you need to make them separate intents then use a state variable in session of the skill and make sure you to set and update state in both intents. You can do it as:
const AgeIntentHandler = {
canHandle(handlerInput) {
let sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'AgeIntent'
&& sessionAttributes.state !== 'WEIGHT';
},
handle(handlerInput) {
let sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
sessionAttributes.state = 'WEIGHT';
handlerInput.attributesManager.setSessionAttributes(sessionAttributes);
const speechText = 'Age intent is called!';
return handlerInput.responseBuilder
.speak(speechText)
.withSimpleCard('Hello World', speechText)
.getResponse();
},
};
const WeightIntentHandler = {
canHandle(handlerInput) {
let sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'WeightIntent'
&& sessionAttributes.state === 'WEIGHT';
},
handle(handlerInput) {
let sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
sessionAttributes.state = '';
handlerInput.attributesManager.setSessionAttributes(sessionAttributes);
const speechText = 'Weight intent is called!';
return handlerInput.responseBuilder
.speak(speechText)
.withSimpleCard('Hello World', speechText)
.getResponse();
},
};
Although it is better to use Dialogs to have no confusion in states and you can add more slots later easily.

Related

Alexa skill, custom slot - date and time

I've created a skill and i want to be able to call upon a machine state at a certain date and time from my dynamo db table.
my first column is date and my sort key is time.
Would i need to create a custom slot for date with all 365 daysof the year or is there a quicker way to do this? Also would i need to create a custom slot for every minute of the day.
Code:
var AWSregion = 'us-east-1'; // us-east-1
var AWS = require('aws-sdk');
var dbClient = new AWS.DynamoDB.DocumentClient();
AWS.config.update({
region: "'us-east-1'"
});
let GetMachineStateIntent = (context, callback) => {
var params = {
TableName: "updatedincident",
Key: {
date: "2018-03-28",
time: "04:23",
}
};
dbClient.get(params, function (err, data) {
if (err) {
// failed to read from table for some reason..
console.log('failed to load data item:\n' + JSON.stringify(err, null, 2));
// let skill tell the user that it couldn't find the data
sendResponse(context, callback, {
output: "the data could not be loaded from your database",
endSession: false
});
} else {
console.log('loaded data item:\n' + JSON.stringify(data.Item, null, 2));
// assuming the item has an attribute called "incident"..
sendResponse(context, callback, {
output: data.Item.incident,
endSession: false
});
}
});
};
function sendResponse(context, callback, responseOptions) {
if(typeof callback === 'undefined') {
context.succeed(buildResponse(responseOptions));
} else {
callback(null, buildResponse(responseOptions));
}
}
function buildResponse(options) {
var alexaResponse = {
version: "1.0",
response: {
outputSpeech: {
"type": "SSML",
"ssml": `<speak><prosody rate="slow">${options.output}</prosody></speak>`
},
shouldEndSession: options.endSession
}
};
if (options.repromptText) {
alexaResponse.response.reprompt = {
outputSpeech: {
"type": "SSML",
"ssml": `<speak><prosody rate="slow">${options.reprompt}</prosody></speak>`
}
};
}
return alexaResponse;
}
exports.handler = (event, context, callback) => {
try {
var request = event.request;
if (request.type === "LaunchRequest") {
sendResponse(context, callback, {
output: "welcome to my skill. what data are you looking for?",
endSession: false
});
}
else if (request.type === "IntentRequest") {
let options = {};
if (request.intent.name === "GetMachineStateIntent") {
GetMachineStateIntent(context, callback);
} else if (request.intent.name === "AMAZON.StopIntent" || request.intent.name === "AMAZON.CancelIntent") {
sendResponse(context, callback, {
output: "ok. good bye!",
endSession: true
});
}
else if (request.intent.name === "AMAZON.HelpIntent") {
sendResponse(context, callback, {
output: "you can ask me about incidents that have happened",
reprompt: "what can I help you with?",
endSession: false
});
}
else {
sendResponse(context, callback, {
output: "I don't know that one! please try again!",
endSession: false
});
}
}
else if (request.type === "SessionEndedRequest") {
sendResponse(context, callback, ""); // no response needed
}
else {
// an unexpected request type received.. just say I don't know..
sendResponse(context, callback, {
output: "I don't know that one! please try again!",
endSession: false
});
}
} catch (e) {
// handle the error by logging it and sending back an failure
console.log('Unexpected error occurred in the skill handler!', e);
if(typeof callback === 'undefined') {
context.fail("Unexpected error");
} else {
callback("Unexpected error");
}
}
};
The short answer is no
In your interaction model you can supply the following built in slot types for your date and time slots:
built-in Date slot: https://developer.amazon.com/docs/custom-skills/slot-type-reference.html#date
built-in Time slot: https://developer.amazon.com/docs/custom-skills/slot-type-reference.html#time
The docs explain what type of utterances map to each.
For instance, you could create an interaction model where you set up an intent, let's call it GetMachineStateIntent and then map the following utterances to this model:
what was the machine state at {Time} on {Date}
what was the state of the machine at {Time} on {Date}
what was the machine state at {Time} {Date}
what was the state of the machine at {Time} {Date}
what was the machine state on {Date} at {Time}
what was the state of the machine on {Date} {Time}
what was the machine state {Date} at {Time}
what was the state of the machine {Date} {Time}
In your skill, you would handle the GetMachineStateIntent and in the request you will receive the filled in values for each of the two slots.
As a first step, while building the interaction model, it would be good to have Alexa simply respond back with speech confirming that it received the date and time slot values from your request.
For example, you might include something like:
if (request.type === "IntentRequest" && request.intent.name == "GetMachineStateIntent") {
var dateSlot = request.intent.slots.Date != null ?
request.intent.slots.Date.value : "unknown date";
var timeSlot = request.intent.slots.Time != null ?
request.intent.slots.Time.value : "unknown time";
// respond with speech saying back what the skill thinks the user requested
sendResponse(context, callback, {
output: "You wanted the machine state at "
+ timeSlot + " on " + dateSlot,
endSession: true
});
}

botkit middleware - How to use sendToWatson to update context?

I use https://github.com/watson-developer-cloud/botkit-middleware#implementing-app-actions as my reference.
The context in my conversation does not update.
Here is my bot-facebook.js.
function checkBalance(context, callback) {
var contextDelta = {
user_name: 'Henrietta',
fname: 'Pewdiepie'
};
callback(null, context);
}
var checkBalanceAsync = Promise.promisify(checkBalance);
var processWatsonResponse = function (bot, message) {
if (message.watsonError) {
console.log(message.watsonError);
return bot.reply(message, "I'm sorry, but for technical reasons I can't respond to your message");
}
if (typeof message.watsonData.output !== 'undefined') {
//send "Please wait" to users
bot.reply(message, message.watsonData.output.text.join('\n'));
if (message.watsonData.output.action === 'check_balance') {
var newMessage = clone(message);
newMessage.text = 'check new name';
checkBalanceAsync(message.watsonData.context).then(function (contextDelta) {
console.log("contextDelta: " + JSON.stringify(contextDelta));
return watsonMiddleware.sendToWatsonAsync(bot, newMessage, contextDelta);
}).catch(function (error) {
newMessage.watsonError = error;
}).then(function () {
return processWatsonResponse(bot, newMessage);
});
}
}
};
controller.on('message_received', processWatsonResponse);
The JSON editor of welcome node in my watson conversation.
{
"context": {
"fname": "",
"user_name": ""
},
"output": {
"text": {
"values": [
"Good day :) My name is Doug and I am a chatbot."
],
"selection_policy": "random"
},
"action": "check_balance"
}
}
I have tried multiple ways I could imagine.
Do I need to do something like fname: <?contextDelta.fname?> in the json editor?
You aren't checking context in your dialog.
Context object in JSON editor is used to store captured data in context,
so your node actually empties context variable.
Probably you need to remove that context initialization from your dialog,
To see value of context variable, you have to use it in the output
"Good day, $fname :) My name is Doug and I am a chatbot."

Task timed out after 3.00 seconds - Lambda application with nodeJS

I am trying to put a hard-coded data item to DynamoDB. I am using AWS SDK object to perform this update. And all the debug "Console.log" in the below code is getting printed but eventually it prints Task timed out after 3.00 seconds
With no update to the DynamoDB
function updatedb(intent, session, callback) {
let country;
const repromptText = null;
const sessionAttributes = {};
let shouldEndSession = false;
console.log("In the function");
const AWS = require("aws-sdk");
const docClient = new AWS.DynamoDB.DocumentClient({ region: 'eu-west-1' });
var params = {
TableName: "Location",
Item: {
"LocationID": { "S": "11" },
"Country": { "S": "10" },
"Description": { "S": "10" },
"Name": { "S": "10" }
}
};
console.log("Param loaded & executing the DocClient Put");
docClient.put(params, function (err, data) {
if (err) {
speechOutput = 'Update failed';
console.error("Unable to create table. Error JSON:", JSON.stringify(err, null, 2));
callback(sessionAttributes,
buildSpeechletResponse(intent.name, speechOutput, repromptText, shouldEndSession));
} else {
console.log("Created table. Table description JSON:", JSON.stringify(data, null, 2));
speechOutput = 'Update successful';
callback(sessionAttributes,
buildSpeechletResponse(intent.name, speechOutput, repromptText, shouldEndSession));
}
});
}
The following items are already checked
1) There is a table named "Location" in DynamoDB
2) Both DynamoDB and this lambda function are in ue-west-1 (Ireland)
3) The role assigned for this Lambda function can do all operation on this table. See the policy details below
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1510603004000",
"Effect": "Allow",
"Action": [
"dynamodb:*"
],
"Resource": [
"arn:aws:dynamodb:eu-west-1:752546663632:table/Location"
]
}
]
}
How does my Lambda function locate the table "location" just with the region?- the code does not appear to have end-point, etc.? - just developed based on a tutorial.
Is that what I am missing?
Please can you help?
I had a similar issue, try putting require statements in the beginning of your function.
const AWS = require("aws-sdk");
const docClient = new AWS.DynamoDB.DocumentClient({ region: 'eu-west-1' });
I believe that AWS locates the table based on your identity, in combination with the region and the table name.
I was able to successfully post to a table using this code:
const AWS = require('aws-sdk');
const dynamoDB = new AWS.DynamoDB({region: 'us-west-2'});
var params = {
TableName: "my-table",
Item: {
"LocationID": { S: "11" },
"Country": { S: "10" },
"Description": { S: "10" },
"Name": { S: "10" }
}
};
dynamoDB.putItem(params, (err, data) => {
if (err){
console.error(err.stack);
} else {
console.log(data);
}
});
If you can in fact post to the table from the CLI, then there is still at least one remaining issue: it appears that you are using the DocumentClient class incorrectly. It looks like you're mixing up the syntax for DynamoDB.putItem with the syntax for DynamoDB.DocumentClient.put.
If you notice, my code uses the DynamoDB class directly-- based on what you're doing, I see no reason why you couldn't do the same. Otherwise, you should change your Item object:
var params = {
TableName: "my-table",
Item: {
"LocationID": "11",
"Country": "10",
"Description": "10",
"Name": "10"
}
};
My guess is your code is currently erroring out because you are trying to insert Maps where you want to insert Strings. If you have Cloudwatch configured you could check the logs.
Finally, I don't see you using callback in your code. If your intention is to respond to a client calling the lambda you should do that. Depending on your NodeJS version, the lambda can simply time out without returning a useful response.

Firebase .indexOn warning

This is my data structure with query string.
Data structure
{
"root_firebase_DB": {
"users": [{
"meUID": {
"name": "ME",
"rooms": [{
"room_id_01": true
}]
}
}, {
"friendUID": {
"name": "FRIEND",
"rooms": [{
"room_id_01": true
}]
}
}],
"rooms": [{
"room_id_01": {
"meUID": true,
"friendUID": true
}
}],
"messages": [{
"room_id_01": {
"message_id_01": {
"text": "Hello world",
"user": {
"_id": "meUID",
"name": "ME"
}
},
"message_id_02": {
"text": "Xin Chao",
"user": {
"_id": "friendUID",
"name": "FRIEND"
}
}
}
}]
}
}
I have meUID and friendUID, i want to find a room that contain both us.
The query bellow is fine, but i got .indexOn warning
const meUID = 'abc';
const friendUID = 'abc';
/**
* Find room key
* filter by meUID and friendUID
*
* THIS WAY I GOT WARNING .indexON
*
*/
firebase.database().ref()
.child('rooms')
.orderByChild(meUID)
.equalTo(true)
.on('child_added', snap => {
if (snap.hasChild(friendUID)) {
console.log('Got roomKEY', snap.key);
}
});
In general way, i get array of rooms by meUID and loop, each loop i go to filter as bellow to find Is this room have my friendUID or not, this way i have no warning with index, but seem waste time to loop and check all rooms.
/**
* THIS WAY NOT GET ANY WARNING
* BUT IS THERE OTHER GOOD WAY TO GO?
*/
const ref = firebase.database().ref();
const meUID = 'aaa';
const friendUID = 'bbb';
const messages = [];
ref.child(`users/${meUID}/rooms`).once('value', snap => {
const roomKey = null;
// loop all room that belong to me
snap.forEach(room => {
// with each room, i must going to check is this room belong to friendUID or not
ref.child(`users/${friendUID}/rooms/${room.key}`).once('value', friendRoomSnap => {
if (friendRoomSnap.val()) {
// found room that belong to friendUID too
roomKey = room.key;
}
});
if (roomKey != null) {
//if found that room, break the loop
return true;
}
});
// goto fetching all messages in this room
ref.child(`messages/${roomKey}`).on('child_added', messageSnap => {
messages.push(messageSnap.val());
});
});
//Too much stuff, is there any way better?
Please help, i am newbie.
Does my data structure get problem?

How can I get records from Firebase where a certain field is empty

I'm building an app where I need to process 5k+ tasks in small batches. For that I have a queue of tasks that is stored in a Firebase. I'd like to be able to pull certain amount of tasks with empty status, update their status and write back.
Currently I don't see how I can pull data where a certain field is empty. Is it possible? If not, what would be the alternative solution?
UPDATED 02/12. Here is the data structure that I have:
{
"-KAMnc89C5Yi_ef18ewc" : {
"0": {
"url": "https://excample.com/url",
"status": "done"
},
"1": {
"url": "https://excample.com/url1"
},
"2": {
"url": "https://excample.com/ur2"
},
"3": {
"url": "https://excample.com/ur3"
}
}
And this is the query I'm using:
queueRef.orderByChild('status').equalTo(null).limitToFirst(1).once('value', function(snapshot) {
console.log(snapshot.val());
});
queueRef points to "-KAMnc89C5Yi_ef18ewc" from the data above.
I expect to get one object - "1", but instead I'm getting all of them. Is there something I'm missing?
Firebase doesn't allow you to store a property without a value. That simply means that the property doesn't exist.
Luckily this doesn't really matter too much, because this seems to work. Given this data structure:
{
"-KADbswYg3FiQF78mmUf": {
"name": "task1",
"status": "done"
},
"-KADbugr7QzTx0s93Fs0": {
"name": "task2"
},
"-KADbvKvBgiAXxnQvoBp": {
"name": "task3"
}
}
This works:
ref.orderByChild('status').equalTo(null).once('value', function(snapshot) {
console.log(snapshot.val());
})
This prints task2 and task3.
Use the DataSnapshot.exists()
This will returns true if this snapshot contains any data. If not it will return false. According to the documentation here. It is slightly more efficient than using snapshot.val() !== null.
With a data structure like this:
{
"girlfriend": {
"first": "Pamala",
"last": "Anderson"
}
}
And a firebase call like this:
var ref = new Firebase("https://myURL/girlfriend/Pamala");
ref.once("value", function(snapshot) {
var a = snapshot.exists();
// a === true
var b = snapshot.child("girlfriend").exists();
// b === true
var c = snapshot.child("girlfriend/first").exists();
// c === true
var d = snapshot.child("girlfriend/middle").exists();
// d === false (because there is no "name/middle" girlfriend in the data snapshot)
});

Resources