0

我正在使用nodejs'alexa-sdk'爲亞馬遜Alexa開發養老金計算器技能。我遇到的問題是,從一個狀態切換到另一個時,它不保留會話屬性。我存儲他們像亞馬遜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

你爲什麼要做'this.emit(「:saveState」,false)'?坦率地說,我不明白第二個參數是什麼(我發現[官方解釋](https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs)不清楚),但我認爲'虛假'的觀點基本上告訴技能不保存狀態。 – spicypumpkin

回答

-1

您是否嘗試過訪問他們像這樣:

this.attributes['gender']. 
0

你錯過了幾個「;」的this.emit("...")後,他們可能是可選的,但我想補充它們。

嘗試啓用"strict mode"更好的錯誤檢測

而且你爲什麼要使用

this.emit(':saveState', false); 

它註釋掉在許多地方,除了內部GENDER

最後,我引用我的狀態變量as

this.attributes['gender'] 

不是一件壞事要嘗試。

0

可能性1:

this.emit(':saveState', false);

用於將數據保存到數據庫DynamoDB。由於您看起來並沒有保存到數據庫,所以這行代碼並不需要。 Alexa會在整個用戶會話中保存變量,然後在技能超時或用戶結束技能時將其遺忘。

這可能是混淆試圖保存到一個不存在的數據庫,這是什麼導致你丟失的數據。

如果你有興趣保存到數據庫中按照Alexa的節點SDK https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs

如此簡單找到的指令如加1行代碼:

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"。我看到你「試圖保存數據」之間的切換意圖,但如上所述this.emit(':saveState', false);保存到本地數據庫。很有可能會在您發出時保存會話屬性,因此在您返回:ask:tell之前切換到新的意向處理程序Answer:ask:tell,因此很可能您正在寫入或丟棄以前的事件處理程序gender'行動。

建議:將代碼從Answer移動,並把它裏面gender。一旦獲得了所有你需要的信息,就不需要跳轉到不同的處理程序。

 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); 
    } 

PS你做DOB同樣的事情:

} else { 
      // gender not needed 
      // this.attributes['gender'] = "unisex"; 
      this.handler.state = states.ANSWER; 
      // this.emit(":saveState", false); 
      this.emit("Answer") 

    } 

刪除你的答案狀態和移動它的功能這兩種功能,它說this.emit("Answer")

0

只需添加:

'SessionEndedRequest': function() { 

    console.log('session ended!'); // Optional 
    this.emit(':saveState', true); 

} 

在你的處理函數旁邊的其他意圖和它將在用戶意外結束會話時保存數據。