I'm writing an Alexa application in node.js to answer product spec questions. For example, I'd like to be able to ask "What is the maximum capacity of (product)?" Currently, I have separate intents for each question for each product, which is resulting in incredibly messy code with a ton of handlers. What I'd like to do is just have one MaxCapacity intent for example, that uses slots (containing the different products) to assign the fact. I'm still relatively new at node.js, so I apologize if this is really sloppy. I know how to make new slots on the developer console, I just don't know how to code it on the backend. This is one of the handlers:
const GetNewFactHandler1 = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'LaunchRequest'
|| (request.type === 'IntentRequest'
&& request.intent.name === 'UMaxCapacityIntent');
*/ And if slot type == *product*, then: */
},
handle(handlerInput) {
const factArr = data;
const randomFact = factArr[1]; //Array with all the answers
const speechOutput = GET_FACT_MESSAGE + randomFact;
return handlerInput.responseBuilder
.speak(speechOutput)
.withSimpleCard(SKILL_NAME, randomFact)
.getResponse();
},
};
I hope that makes sense, but I'd be happy to explain further. Thank you!
If I understood correctly, you want to invoke this handler, when slot 'product' exists? If so, you can use 'getSlot' helper method. If slot exists in the request, it returns that slot, otherwise it results in null. So just add extra check to canHandle condition && getSlot(handlerInput.requestEnvelope, 'product') and that should be it.
Related
I'm creating a game in ASK (Alexa Skills Kit) where a Alexa will ask the product between two random numbers in a range specified by the user. When the user answers, Alexa should tell them if the answer is correct or incorrect (UserAnswerIntentHandler), and then ask a new question (GamePlayIntentHandler).
const GamePlayIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'GamePlayIntent';
},
async handle(handlerInput){
const attributesManager = handlerInput.attributesManager;
const attributes = await attributesManager.getSessionAttributes() || {};
var lowestNum = attributes.lowestNum
var highestNum = attributes.highestNum
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min)
}
var randomOne = getRandomIntInclusive(lowestNum, highestNum)
var randomTwo = getRandomIntInclusive(lowestNum, highestNum)
attributes.randomOne = randomOne
attributes.randomTwo = randomTwo
const speakOutput = `What is ${randomOne} times ${randomTwo}?` //Here, alexa is asking for the product between two random numbers in a range the user previously specified.
return handlerInput.responseBuilder
.addElicitSlotDirective('answer', {
name: 'UserAnswer',
confirmationStatus: 'NONE'
})
.speak(speakOutput)
.reprompt(`Sorry, I didn't get that. What is ${randomOne} times ${randomTwo}?`)
.getResponse();
}
}
const UserAnswerIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'UserAnswerIntent';
},
async handle(handlerInput) {
const {requestEnvelope} = handlerInput
const {intent} = requestEnvelope.request
const attributesManager = handlerInput.attributesManager;
const attributes = await attributesManager.getSessionAttributes() || {};
var userAnswer = parseInt(Alexa.getSlotValue(requestEnvelope, 'answer'))
const randomOne = attributes.randomOne
const randomTwo = attributes.randomTwo
var correctAnswer = randomOne * randomTwo
var points = 0
var speakOutput = ""
if (userAnswer > 0){
if (userAnswer === correctAnswer){
points +=1
speakOutput = `That's correct! You now have ${points} points.`
return handlerInput.responseBuilder
.speak(speakOutput)
.addDelegateDirective({
name: 'GamePlayIntent',
confirmationStatus: 'NONE'
})
.getResponse();
}//If the answer is correct, the user gets a point and Alexa should immediatley ask the next question
else{
speakOutput = `Your answer is incorrect. The correct answer is ${correctAnswer}. You currently have ${points} points.`
return handlerInput.responseBuilder
.speak(speakOutput)
.addDelegateDirective({
name: 'GamePlayIntent',
confirmationStatus: 'NONE'
})
.getResponse();
}//If the answer is incorrect, Alexa will give the correct answer and total points and immediatley go to the next question
}
}
}
What's happening right now is that Alexa isn't telling the user if their answer is correct or incorrect, and is immediately asking the next question.
Is there any way to fix this or create a loop such that as soon as the user answers, Alexa can evaluate the answer, and then immediately ask the next question?
When the GamePlayIntent is triggered, Alexa will ask a question then get the answer provided by the user.
The answer should then trigger UserAnswerIntent.
At this point, UserAnswerIntentHandler is triggered and goes to the handle(handlerInput) where you have your logic to verify the answer.
Once done, Alexa gives the correct answer then chain with the new question in the same message string.
You can build you string message by having one part for verifying the answer and a second part for the next question, you concatenate the strings together and return it in the same responseBuilder .speak(), followed by a repromt to make sure the user is giving an answer.
Once the user's input is captured, it will again triggered the corresponding intent to verify the new answer.
Another way would be to ask the user if they would like to have a new question through YesIntent/NoIntent.
If the answer is yes then you redirect them to the GamePlayIntent using
return GamePlayIntentHandler.handle(handlerInput);
If the answer is no then you add a goodbye message and close the session or so depending on the skill functionality/logic.
Here are some sample skills (1, 2) I found that you can get guided by, one includes questions/answers handled in a different way.
Hi currently thinking about a good design to manage multiple output chains in an Alexa skill. For example if I start with one intent called "yesterday" and another one called "today". I want to pass this information (is it "yesterday" I started the chain with or "today") to a NextIntent chain.
Whats the best way to pass information between intents?
Just found out here https://developer.amazon.com/de/blogs/alexa/post/f167aa0f-8abe-4602-b985-65118b3032ca/code-deep-dive-slots-and-session-attributes-in-the-ask-sdk-for-node-js how to do it.
I was searching for session attributes
These can be used like this
1) The first intent I called to start the chain:
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'BesterAutorIntent';
},
handle(handlerInput) {
try {
const speechOutput = "Ich könnte dir sagen wer der beste Autor ist, aber ich muss erst HR fragen ob ich darf";
const attributes = handlerInput.attributesManager.getSessionAttributes();
attributes.kaesebrot = "kaesebrot"
handlerInput.attributesManager.setSessionAttributes(attributes)
return handlerInput.responseBuilder
.speak(speechOutput)
.reprompt()
.withSimpleCard(defaulttext.SKILL_NAME, speechOutput)
.getResponse();
} catch (error) {
console.error(error);
}
},
};
In there you can I set an attribute called kaesebrot to be a session attribute:
const attributes = handlerInput.attributesManager.getSessionAttributes();
attributes.kaesebrot = "kaesebrot"
handlerInput.attributesManager.setSessionAttributes(attributes)
Later in another function you can get it like this
let counter = 0;
const NextIntentHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest'
&& request.intent.name === 'AMAZON.NextIntent';
},
handle(handlerInput) {
try {
counter = counter + 1;
const attributes = handlerInput.attributesManager.getSessionAttributes();
speechOutput = counter + " TEST " + attributes.kaesebrot;
return handlerInput.responseBuilder
.speak(speechOutput)
.reprompt()
.withSimpleCard(defaulttext.SKILL_NAME, speechOutput)
.getResponse();
} catch (error) {
console.error(error);
}
},
};
Tobi's answer is good.
I am using sdk v1 and the code to keep attributes on dynamoDB is like this:
exports.handler = function( event, context, callback ) {
const alexa = Alexa.handler( event, context );
alexa.dynamoDBTableName = "alexaTable";
alexa.registerHandlers( handlers );
alexa.execute();
};
...
const handlers = {
...
"AMAZON.NextIntent": function () {
console.log( "AMAZON.NextIntent: " + this.attributes.index );
}
...
}
A dynamodb table "alexaTable" will be created the first time the lambda function is invoked and will store and retrieve the attributes automatically.
In the above example "index" is an attribute carried over from a previous intent.
Table schema consists of a key and a field.
Key: userId, alexa skill user id
Field: mapAttr, JSON structured attributes
but it is self-managed by the alexa-sdk.
This post can help further
https://developer.amazon.com/blogs/alexa/post/648c46a1-b491-49bc-902d-d05ecf5c65b4/tips-on-state-management-at-three-different-levels
SessionAttributes is a way to persist state across intents but there's a new feature jsut out of the oven to specifically chain intents and pass slot values.
It's called Intent Chaining and you can see more here:
https://developer.amazon.com/blogs/alexa/post/9ffdbddb-948a-4eff-8408-7e210282ed38/intent-chaining-for-alexa-skill
Intent chaining allows your skill code to start dialog management from any intent, including the LaunchRequest. You can chain into any of your custom intents as long as your interaction model has a dialog model.
For example, from YesterdayIntent, in the response builder you would do:
handlerInput.responseBuilder.addDelegateDirective({
name: 'TodayIntent',
confirmationStatus: 'NONE',
slots: {} // the slot values to pass go here
})
I want to create a simple multi-turn dialog with the Alexa Skill model. My intent consists of 3 slots, each of which are required to fulfill the intent. I prompt every slot and defined all of the needed utterances.
Now I want to handle the request with a Lambda function. This is my function for this specific Intent:
function getData(intentRequest, session, callback) {
if (intentRequest.dialogState != "COMPLETED"){
// return a Dialog.Delegate directive with no updatedIntent property.
} else {
// do my thing
}
}
So how would I go on to build my response with the Dialog.Delegate directive, as mentioned in the Alexa documentation?
https://developer.amazon.com/docs/custom-skills/dialog-interface-reference.html#scenario-delegate
Thank you in advance.
With Dialog.Delegate directive you cannot send outputSpeech or reprompt from your code. Instead those defined in interaction model will be used.
Do not include outputSpeech or reprompt with the Dialog.Directive.
Alexa uses the prompts defined in the dialog model to ask the user for
the slot values and confirmations.
What this means is that you cannot delegate and provide your own response, but instead you can use any other Dialog directive to provide your outputSpeech and reprompt.
Ex: Dialog.ElicitSlot, Dialog.ConfirmSlot and Dialog.ConfirmIntent.
At any point, you can take over the dialog rather than continuing to delegate to Alexa.
...
const updatedIntent = handlerInput.requestEnvelope.request.intent;
if (intentRequest.dialogState != "COMPLETED"){
return handlerInput.responseBuilder
.addDelegateDirective(updatedIntent)
.getResponse();
} else {
// Once dialoState is completed, do your thing.
return handlerInput.responseBuilder
.speak(speechOutput)
.reprompt(reprompt)
.getResponse();
}
...
The updatedIntent parameter in addDelegateDirective() is optional.
It is an intent object representing the intent sent to your skill. You can use this property set or change slot values and confirmation status if necessary.
More on Dialog directives here
In nodejs you can use
if (this.event.request.dialogState != 'COMPLETED'){
//your logic
this.emit(':delegate');
} else {
this.response.speak(message);
this.emit(':responseReady');
}
In nodeJS we can check the dialogState and act accordingly.
if (this.event.request.dialogState === 'STARTED') {
let updatedIntent = this.event.request.intent;
this.emit(':delegate', updatedIntent);
} else if (this.event.request.dialogState != 'COMPLETED'){
if(//logic to elicit slot if any){
this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
} else {
this.emit(':delegate');
}
} else {
if(this.event.request.intent.confirmationStatus == 'CONFIRMED'){
//logic for message
this.response.speak(message);
this.emit(':responseReady');
}else{
this.response.speak("Sure, I will not create any new service request");
this.emit(':responseReady');
}
}
i feel a bit embarrassed, can you please kindly explain parts of the code?
For example, I have no idea, what is this part? where can I read more about it?
function parsePostStory(data) {
return {
name : data.name
}
}
What is req.body? Is it json req body?
Why do we declare empty array and why do we return it? Just for the clarity?
Is Story.create just a mongoose method?
The rest of the code is here:
router.post('/stories', function(req, res) {
var validation = validatePostStory(req.body);
if(validation.length > 0) {
return res.badRequestError(validation);
}
var story = parsePostStory(req.body);
Story.create(story, function(err, story) {
if(err) {
console.log(err.message);
return res.internalServerError();
} res.send(story);
});
});
function validatePostStory(data) {
var array = [];
if (!data.name || typeof data.name !== 'String') {
return array.push('name');
}
return array;
}
function parsePostStory(data) {
return {
name : data.name
}
}
Sorry once more for that kind of a question and thanks a ton.
I'm assuming you know how the request-response cycle works with HTTP requests and the client-server interactions with it. If not, Wikipedia Request-Response and Client-Server (Two link limit, otherwise I would have posted them as links)
A request sends a lot of information to the server. If you console.log the request in NodeJS, you will see that it contains a lot of information that isn't entirely relevant to what you need.
You're using Express as your web framework. In this case, req.body is the information that you are sending to the server from the client. Using req.body will make sure that you're not using the extra information passed in to the server from the client. Req.body is your code that you want. (Note: Req.body isn't natively supported by Express v4, you'll have to use something like body-parser) See Express docs for more details
Now, let's break up this code a bit. You essentially have 3 separate functions. Let's take a look at validatePostStory.
function validatePostStory(data) {
var array = [];
if (!data.name || typeof data.name !== 'String') {
return array.push('name');
}
return array;
}
This function is a validation function. It takes one argument - an object and returns an array. Effectively, what this is doing is checking if the name is a string or not - if not, return an array that has a length of 1. The following conditional checks length and returns a 400 if greater than 0
if(validation.length > 0) {
return res.badRequestError(validation);
}
I'm not entirely sure why this needs to be a separate function. Looks like you can probably just do this instead.
if (!req.body.name || typeof req.body.name !== 'String') {
return res.badRequestError(validation);
}
The following function function essentially converts the data so that mongodb/mongoose can store it in the proper format
function parsePostStory(data) {
return {
name : data.name
}
}
It's the same as saying:
var story = {name: req.body.name}
I would assume Story.create is a custom mongoose method yes.
I've been reading about node.js recently (like many others). I find interesting for some use cases, but am a bit struggling to understand the inner workings, specifically the interaction between closure functions and the process flow of the code.
Let's say I have a function which accepts a key-value array. The function must check that the values follow certain data-quality guidelines (for example some keys must have a value, other keys must have numbers as values etc) before storing the data somewhere (for the purpose of this question let's assume data validation has to be done in the application itself).
In "regular" developments models I'd write something like this:
resultName = validateName(data.name)
resultAddress = validateAddress(data.address)
resultID = validateID(data.id)
if (resultName && resultAddress && resultID) {
store(data)
else {
sendErrorToUser(data)
}
Get the results of the validations, and either explain the error(s) to the user or store data and return some kind of confirmation. The flow is very clear.
The way I understand node.js, the way to do this would be to delegate the validations to a different function (to avoid waiting for each validation to finish), and supply two callback functions to the functions which validate the chunks of data:
* a callback to call when validation is successful
* a callback to call when validation fails
It's easy to now return to the user with a "please wait" message, but I have to wait for all validations to clear (or fail) before storing the data or explaining the problem to the user. As a simple way to figure out if all the validations are done I thought of using a variable that counts the number of functions that called the callback, and emitting a "validation complete" event to store the validated data (or get back to the user with any errors). Or, alternatively, emit an event after each validation is complete and in that event's code check if all validations are complete before emitting the "store" / "error" events.
My question is -- am I approaching this correctly? Or is there a more suitable way to do these kinds of things with node.js (or similar event-based systems).
Thank you!
Alon
Are your validations asynchronous? If they are not you can use the code you posted, the "regular" one.
If the validations are asynchronous (checking uniqueness of an email for instance), you need to provide callbacks:
var validateUniqueEmail = function (data, callback) {
db.find({email: data.email}, function (err, result) {
callback(err, result === null);
})
};
var validateAndStore = function (data, callback) {
asyncValidation(data, function (err, is_valid) {
if (err) {
callback(err, null);
} else if (!is_valid) {
callback('Email is not unique', null);
} else {
db.store(data, callback);
}
});
}
The code above can be simplified a lot by using some validator or ORM modules already existing
example: mongolia validator module.
Let's go. Basically, what you want to do is something along the lines of :
var validate(data, cb){
var allOk = true;
for(var key in data){
allOk = allOk && validate[key](data.key); // validator depends on the key
}
if (allOk) cb(null, data); else cb(new Error "bleh");
}
This could be done the following way (note how we pass the failed keys as the first (error) argument to the callback):
var validate(data, cb){
var status = {true:[], false:[]},
total = Object.keys(data).length,
done = 0;
for (var key in data)
(function(key){
validate[key](data[key], function(ok){
status[ok].push(key);
if (++done == total){
status[false].length ? cb(status[false]) : cb(null);
}
});
})(key);
}
Which you can use this way :
validate(data, function(failures){
if (failures){
// tell the user the input does not validate on the keys stored in failures
} else {
// all ok
store(data);
}
});
Correct me if I'm wrong, but I think what you're asking is how to handle the response from multiple asynchronous calls.
Here's how I do it (using your validation example):
var result = {};
function isAllDataAvailable() {
return result.name !== undefined
&& result.address !== undefined
&& result.id !== undefined;
}
function callback(error) {
if (error) {
showError(error);
// terminate here (?)
return;
}
if (isAllDataAvailable()) {
showOutput();
}
}
validateName(data, callback);
validateAddress(data, callback);
validateEmail(data, callback);
The key here is the result object, which starts out as empty. As each field gets validated, it gets added to the result object (by the validation functions, which I've left out in the above snippet). I've used a single callback method, but you could have multiple, say callbackName, callbackAddress, etc. The validation results are processed only if and when the result object has been fully populated, which is checked in isAllDataAvailable.
Hope this helps.
Consider using: https://github.com/asaf/nodejs-model
It will make your life much easier when dealing with validators.