我正在使用nodejs'alexa-sdk'为亚马逊Alexa开发养老金计算器技能。我遇到的问题是,当从一个状态切换到另一个状态时,它不会保留会话属性。我正在存储它们
this.attributes['ATTRIBUTE_NAME'] = 'some_value';
但是当我得到答案(Alexa.CreateStateHandler(states.ANSWER[...]
)时,所有属性都是“未定义的”。任何人都可以告诉我在存储或传递会话属性时遇到了什么问题吗?
var Alexa = require('alexa-sdk');
var moment = require('moment'); // deals with dates and date formatting, for instance converts AMAZON.DATE to timestamp
// import pension calculator
var calculator = require('./pensionCalculator');
var GENDERCUTOFFDATE = new Date(1953, 12, 6);
// States are required for conversational skills.
// States assume a context. e.g. _DOB expects date of birth; providing a gender in this state would confuse Alexa.
// UX design principle have to be as unambiguous as possible in language (e.g. "are you male or female" vs "what gender are you?")
var states = {
START: '_STARTMODE', // Prompt the user to start or restart
DOB: '_DOB',
GENDER: '_GENDER',
ANSWER: '_ANSWER'
};
// Outbound messages spoken back to end user.
// alexa-nodejs-sdk wraps all strings in the advanced SSML speech markup (<speak>STRING HERE</speak>) that allows phonetic overrides etc.
var snippets = {
WELCOME: "<s>Welcome to the D W P Pension Age calculator.</s> " +
"<s>You can ask to calculate your pension age or for the U K pension eligibility criteria?</s>",
WELCOME_REPROMPT: "You can say, " +
"Calculate my pension age or, say what are the eligibility criteria in the U K.",
GENDER: "Thank you. Are you female or male?",
GENDER_REPROMPT: "In order to calculate your pension age, please tell me: Are you male or female?",
GENDER_INVALID: "Sorry I couldn't understand your gender, can you please tell me if you are you male or female?",
DATEOFBIRTH: "Ok, please tell me what is your date of birth?",
DATEOFBIRTH_REPROMPT: "In order to calculate your pension age please tell me your date of birth?",
DATEOFBIRTH_INVALID_FUTURE: "Nice you're from the future. Did you bring a hoverboard? Seriously, can you please say your actual date of birth please?",
DATEOFBIRTH_INVALID: "Please say your date of birth. For example you can say, my date of birth is the 23rd of April 1980",
STOP: "Thank you for using the D W P pension calculator.",
HELP: "You can ask things like: What is my pension age or what are the eligibility criteria.",
HELP_REPROMPT: "Simply say: calculate pension or eligibility criteria.",
UNHANDLED: "I'm sorry I couldn't understand what you meant. Can you please say it again?"
};
// You define a set of state handlers for every state PLUS the new session / launch event.
var newSessionHandlers = {
// session variables stored in this.attributes
// session state is stored in this.handler.state
// handler.state vs Intent vs
'LaunchRequest': function() {
// Initialise State
this.handler.state = states.START;
// emitWithState should be called executeStateHandler("Start").
// As such this will call a handler "Start" in the startStateHandlers object.
// Maybe this line and the previous line could be more coherently wrapped into a single
// function:
// this.stateTransition( states.START, "Start" )
this.emit("Start")
},
// It's unclear whether this can ever happen as it's triggered by Alexa itself.
"Unhandled": function () {
var speechText = "I wasn't launched yet";
this.emit(":ask", speechText);
}
};
// This is the beginning of our skill.
// This is a list of accepted intents Alexa is listening for
// when the skill has just started.
// In this specific version, a user can't provide things like date of birth
// or gender as part of the initial skill invocation because we've not included in this set of start state handlers.
// We could but haven't in this particular scenario.
var startStateHandlers = Alexa.CreateStateHandler(states.START, {
'Start': function() {
this.attributes['dob'] = '';
this.attributes['gender'] = '';
this.handler.state = states.START;
var speechText = snippets.WELCOME;
var repromptText = snippets.WELCOME_REPROMPT;
// emit is the exit point to instruct Alexa how to and what to communicate with end user.
// e.g. do we want further information? (:ask) / no further information, skill terminates (:tell)
// do we provide a voice response with or without a card on the mobile device (:ask vs :askWithCard)
// https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs
// as we've said :ask we are expecting the user to provide more information.
// maybe this function could be called this.respond()
// this is going to speak the snippets.WELCOME which implicitly asks a question (hence :ask).
// reprompt text is automatically spoken after a few seconds. This is a feature of the NodeJS SDK.
// See Unhandled for the fallback / unrecognised utteranes.
this.emit(':ask', speechText, repromptText);
},
// the intent text is defined in the
// Alexa interaction model web page at developer.amazon.com/ask
// represented as sample utterances.
'StartCalculationIntent': function () {
var speechText = snippets.DATEOFBIRTH;
var repromptText = snippets.DATEOFBIRTH_REPROMPT;
// Change State to calculation
this.handler.state = states.DOB;
this.emit(':ask', speechText, repromptText);
},
// a predefined Utterance that you don't need to define in your interaction model
// We are choosing to provide this help function but equally you don't need to.
"AMAZON.HelpIntent": function () {
var speechText = snippets.HELP;
var repromptText = snippets.HELP_REPROMPT;
this.emit(':ask', speechText, repromptText);
},
"Unhandled": function () {
var speechText = snippets.UNHANDLED;
this.emit(":ask", speechText);
},
// User says stop. Stops even in the middle of a response.
"AMAZON.StopIntent": function () {
var speechText = snippets.STOP;
this.emit(":tell", speechText);
},
// unclear really what the difference is; default working practice is
// to do the same thing
// in a production system we'd probably dedupe this function.
"AMAZON.CancelIntent": function () {
var speechText = snippets.STOP;
this.emit(":tell", speechText);
},
"AMAZON.StartOverIntent": function () {
this.emit("Start")
},
// TODO determine when this is requested and what initiates it
// Implement handler to save state if state should be stored persistently e.g. to DynamoDB
// 'SessionEndedRequest': function () {
// console.log('session ended!');
// this.emit(':saveState', true);
// }
// TODO add 'AMAZON.RepeatIntent' that repeats the last question.
});
var dobStateHandlers = Alexa.CreateStateHandler(states.DOB, {
'DateOfBirthIntent': function () {
var speechText = "",
repromptText = "";
var date_string = this.event.request.intent.slots.dob.value;
var date = moment(date_string);
if (date.isValid()) {
if (!isFutureDate(date)) {
// ALL GOOD – dob not in the future
speechText = snippets.GENDER;
repromptText = snippets.GENDER_REPROMPT;
this.attributes['dob'] = date_string;
if(isGenderNeeded(date)) {
// Transition to next state
this.handler.state = states.GENDER;
// this.emit(":saveState", false);
this.emit(':ask', speechText, repromptText);
} else {
// gender not needed
// this.attributes['gender'] = "unisex";
this.handler.state = states.ANSWER;
// this.emit(":saveState", false);
this.emit("Answer")
}
} else {
// dob in the future
speechText = snippets.DATEOFBIRTH_INVALID_FUTURE;
// this.emit(":saveState", false);
repromptText = snippets.DATEOFBIRTH_INVALID_FUTURE; // could be improved by using alternative prompt text
this.emit(':ask', speechText, repromptText);
}
} else {
// not a valid Date
speechText = snippets.DATEOFBIRTH_INVALID;
// this.emit(':saveState', false);
repromptText = snippets.DATEOFBIRTH_INVALID; // could be improved by using alternative prompt text
this.emit(':ask', speechText, repromptText);
}
},
"AMAZON.HelpIntent": function () {
var speechText = snippets.HELP;
var repromptText = snippets.HELP_REPROMPT;
this.emit(':ask', speechText, repromptText);
},
"Unhandled": function () {
var speechText = snippets.UNHANDLED;
this.emit(":ask", speechText);
},
"AMAZON.StopIntent": function () {
var speechText = snippets.STOP;
this.emit(":tell", speechText);
},
"AMAZON.CancelIntent": function () {
var speechText = snippets.STOP;
this.emit(":tell", speechText);
},
"AMAZON.StartOverIntent": function () {
this.emit("Start")
},
'SessionEndedRequest': function () {
// this.emit(':saveState', false);
// this.attributes['dob'] = date_string;
// this.attributes['dob'] = date_string;
console.log('session ended!');
}
});
var genderStateHandlers = Alexa.CreateStateHandler(states.GENDER, {
'GenderIntent': function () {
var speechText = "",
repromptText = "";
var gender = this.event.request.intent.slots.gender.value;
if (isGenderSlotValid(gender)) {
// valid gender
this.attributes['gender'] = gender;
this.handler.state = states.ANSWER;
this.emit(':saveState', false);
this.emit("Answer");
} else {
// not a valid gender
speechText = snippets.GENDER_INVALID;
repromptText = snippets.GENDER_INVALID; // could be improved by using alternative prompt text
this.emit(':saveState', false);
this.emit(':ask', speechText, repromptText);
}
},
"AMAZON.HelpIntent": function () {
var speechText = snippets.HELP;
var repromptText = snippets.HELP_REPROMPT;
this.emit(':ask', speechText, repromptText);
},
"Unhandled": function () {
var speechText = snippets.UNHANDLED;
this.emit(":ask", speechText);
},
"AMAZON.StopIntent": function () {
var speechText = snippets.STOP;
this.emit(":tell", speechText);
},
"AMAZON.CancelIntent": function () {
var speechText = snippets.STOP;
this.emit(":tell", speechText);
},
"AMAZON.StartOverIntent": function () {
this.emitWithState("Start")
},
'SessionEndedRequest': function () {
this.emit(':saveState', false);
console.log('session ended!');
}
});
var answerStateHandlers = Alexa.CreateStateHandler(states.ANSWER, {
'Answer': function () {
console.log(`##### START INPUT SNIPPETS #####`);
// var dob =
// var gender = ;
console.log(`this.attributes.dob: "${this.attributes.dob}"`);
console.log(`this.attributes.gender: "${this.attributes.gender}"`);
var pensionDate = calculator.calculatePension(this.attributes.dob, this.attributes.gender);
console.log(`pensionDate: "${pensionDate}"`);
var speechText = calculator.createPensionSnippet(pensionDate);
// Change State to Start again
this.handler.state = states.START;
this.emit(':tell', speechText);
console.log(`##### STOP SNIPPET #####`);
console.log(``);
},
"Unhandled": function () {
var speechText = snippets.UNHANDLED;
this.emit(":ask", speechText);
}
});
function isGenderSlotValid(gender) {
if (gender == "male" || gender == "female") {
return true
} else {
return false
}
}
function isFutureDate(dob) {
var today = moment();
if (dob > today) {
return true
} else {
return false
}
}
function isGenderNeeded(dob) {
return dob < GENDERCUTOFFDATE;
}
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
alexa.appId = process.env.appId;
// alexa.appId = "your skill ID"; // better store it as ENV variable at AWS Lambda
// alexa.resources = languageStrings;
// register intent handlers for each state + new session.
// Each state constrains possible intents Alexa is listening for.
// If you only have one handler you are context-free and cannot have state.
alexa.registerHandlers(newSessionHandlers, startStateHandlers, dobStateHandlers, genderStateHandlers, answerStateHandlers);
alexa.execute();
};
答案 0 :(得分:0)
;
之后你遗漏了一些“this.emit("...")
”,它们可能是可选的,但我会添加它们。
尝试启用"strict mode"
以便更好地检测错误
另外你为什么要使用
this.emit(':saveState', false);
除了内部GENDER
最后,我将状态变量引用为
this.attributes['gender']
尝试并不是件坏事。
答案 1 :(得分:0)
可能性1:
this.emit(':saveState', false);
用于将数据保存到DynamoDB数据库。由于您似乎没有保存到数据库,因此这行代码是不必要的。 Alexa将在整个用户会话中保留您的变量,然后当技能超时或用户结束技能时,他们将被遗忘。
可能存在混合尝试保存到不存在的数据库以及导致丢弃数据的原因。
如果您对保存到数据库感兴趣,请按照Alexa Node SDK中的说明进行操作 https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs
这就像添加一行代码一样简单:
alexa.dynamoDBTableName = 'YourTableName'; // That's it!
建议:删除所有保存状态指令
可能性2:
您有多个状态以奇怪的方式链接在一起。
if (isGenderSlotValid(gender)) {
// valid gender
this.attributes['gender'] = gender;
this.handler.state = states.ANSWER;
this.emit(':saveState', false);
this.emit("Answer");
}
在此代码中,您的用户已调用'GenderIntent'
您已确定性别是什么,然后您调用另一个意图"Answer"
。我看到你&#34;尝试保存数据&#34;切换意图之间但如上所述this.emit(':saveState', false);
保存到数据库而不是本地。因此,在发出:ask
或:tell
时,很可能会保存会话属性,因为在返回{{1}之前切换到新的意图处理程序Answer
或者:ask
您可能正在编写或放弃之前的事件处理程序:tell
的操作。
建议:从gender
移动代码并将其放在Answer
内。一旦掌握了所需的所有信息,就无需跳转到不同的处理程序。
gender
PS你在DOB做同样的事情:
if (isGenderSlotValid(gender)) {
// valid gender
this.attributes['gender'] = gender;
var pensionDate = calculator.calculatePension(this.attributes.dob, this.attributes.gender);
console.log(`pensionDate: "${pensionDate}"`);
var speechText = calculator.createPensionSnippet(pensionDate);
// Change State to Start again
this.handler.state = states.START;
this.emit(':tell', speechText);
}
删除您的回答状态,并将其功能移至这两个函数中,其中显示 } else {
// gender not needed
// this.attributes['gender'] = "unisex";
this.handler.state = states.ANSWER;
// this.emit(":saveState", false);
this.emit("Answer")
}
答案 2 :(得分:0)
只需添加:
'SessionEndedRequest': function () {
console.log('session ended!'); // Optional
this.emit(':saveState', true);
}
在其他意图旁边的处理函数中,只要用户意外结束会话,它就会保存数据。
答案 3 :(得分:-1)
您是否尝试过像这样访问它们:
this.attributes['gender'].