我正在阅读 Pragmatic Programmer 的《Build Talking Apps》一书,并构建我的第一个 Alexa 应用程序。
应用名称为“星港旅行”,但调用名称为“星港七十五”。这是在 en-US.json 文件中指定的,也是 Alexa 开发者控制台网站上的 invokingName。这是 Alexa 托管的技能。
问题是,在部署和构建代码后,当我尝试通过网站测试它时,通过输入“open star port 75”、“star port 75”或“star port 75”,Alexa 会做出响应:“所请求的技能响应存在问题。”
我仔细检查了区域设置为“en-US”并且应用程序使用英语。
我错过了什么?
这是我的 en-US.json 文件:
{
"interactionModel": {
"languageModel": {
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": [
"huh",
"what",
"confused"
]
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"slots": [],
"name": "HelloWorldIntent",
"samples": [
"hello",
"how are you",
"say hi world",
"say hi",
"hi",
"say hello world",
"say hello"
]
},
{
"name": "AMAZON.FallbackIntent",
"samples": []
}
],
"types": [],
"invocationName": "star port seventy five"
}
}
}
这是我的index.js:
/* *
* This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).
* Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
* session persistence, api calls, and more.
* */
const Alexa = require('ask-sdk-core');
const i18next = require('i18next');
const languageStrings = require('./languageStrings');
const LaunchRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
},
handle(handlerInput) {
const speakOutput = 'Welcome to Star Port Seventy Five.';
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
const HelloWorldIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
},
handle(handlerInput) {
const speakOutput = handlerInput.t('HELLO_MSG');
return handlerInput.responseBuilder
.speak(speakOutput)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
const HelpIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const speakOutput = handlerInput.t('HELP_MSG');
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
|| Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
},
handle(handlerInput) {
const speakOutput = handlerInput.t('GOODBYE_MSG');
return handlerInput.responseBuilder
.speak(speakOutput)
.getResponse();
}
};
/* *
* FallbackIntent triggers when a customer says something that doesn’t map to any intents in your skill
* It must also be defined in the language model (if the locale supports it)
* This handler can be safely added but will be ingnored in locales that do not support it yet
* */
const FallbackIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.FallbackIntent';
},
handle(handlerInput) {
const speakOutput = handlerInput.t('FALLBACK_MSG');
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
/* *
* SessionEndedRequest notifies that a session was ended. This handler will be triggered when a currently open
* session is closed for one of the following reasons: 1) The user says "exit" or "quit". 2) The user does not
* respond or says something that does not match an intent defined in your voice model. 3) An error occurs
* */
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
},
handle(handlerInput) {
console.log(`~~~~ Session ended: ${JSON.stringify(handlerInput.requestEnvelope)}`);
// Any cleanup logic goes here.
return handlerInput.responseBuilder.getResponse(); // notice we send an empty response
}
};
/* *
* The intent reflector is used for interaction model testing and debugging.
* It will simply repeat the intent the user said. You can create custom handlers for your intents
* by defining them above, then also adding them to the request handler chain below
* */
const IntentReflectorHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
},
handle(handlerInput) {
const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
const speakOutput = `You just triggered ${intentName}`;
return handlerInput.responseBuilder
.speak(speakOutput)
//.reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
/**
* Generic error handling to capture any syntax or routing errors. If you receive an error
* stating the request handler chain is not found, you have not implemented a handler for
* the intent being invoked or included it in the skill builder below
* */
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
const speakOutput = 'Sorry, I had trouble doing what you asked. Please try again.';
console.log(`~~~~ Error handled: ${JSON.stringify(error)}`);
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
}
};
/**
* This request interceptor will handle the externalized string values.
*/
const LocalisationRequestInterceptor = {
process(handlerInput) {
i18next.init({
lng: Alexa.getLocale(handlerInput.requestEnvelope),
resources: languageStrings
}).then((i18n) => {
handlerInput.t = (...args) => i18n(...args);
});
}
};
/**
* This handler acts as the entry point for your skill, routing all request and response
* payloads to the handlers above. Make sure any new handlers or interceptors you've
* defined are included below. The order matters - they're processed top to bottom
* */
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
HelloWorldIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
FallbackIntentHandler,
SessionEndedRequestHandler,
IntentReflectorHandler)
.addErrorHandlers(
ErrorHandler)
.withCustomUserAgent('sample/hello-world/v1.2')
.addRequestInterceptors(
LocalisationRequestInterceptor
)
.lambda();
这是 JSON 输入 1:
{
"version": "1.0",
"session": {
"new": true,
"sessionId": "amzn1.echo-api.session.826ac7d7-0ef0-4574-984c-d92f0f9cbc6f",
"application": {
"applicationId": "amzn1.ask.skill.20a7255b-e6b4-4328-aa03-2b0aa6e53b5b"
},
"attributes": {},
"user": {
"userId": "amzn1.ask.account.AMAR7ONMIGNT44YC3SLVLMAARW5MDRYY6LDIYEMZJVVYDYRFAPQKX3RZBLR736N2JF6HAZSJYLXXF4NZ65MQ3HLGMPJ76KAVIIUUKP6PGTF7SHVWZNHHEGZ7LB2QX3PC57IZ6VFFVWJC5LHGRYNKFGCEIMGK2KTZ7OMGE3ZPMANXSWUEBHAMVOCIG6DVGHMII3KVVHPH2ECL4G6Q5DSTCE3YR6TDLBT2UPHHYVSQCBOQ"
}
},
"context": {
"Viewports": [
{
"type": "APL",
"id": "main",
"shape": "RECTANGLE",
"dpi": 213,
"presentationType": "STANDARD",
"canRotate": false,
"configuration": {
"current": {
"mode": "HUB",
"video": {
"codecs": [
"H_264_42",
"H_264_41"
]
},
"size": {
"type": "DISCRETE",
"pixelWidth": 1280,
"pixelHeight": 800
}
}
}
}
],
"Viewport": {
"experiences": [
{
"arcMinuteWidth": 346,
"arcMinuteHeight": 216,
"canRotate": false,
"canResize": false
}
],
"mode": "HUB",
"shape": "RECTANGLE",
"pixelWidth": 1280,
"pixelHeight": 800,
"dpi": 213,
"currentPixelWidth": 1280,
"currentPixelHeight": 800,
"touch": [
"SINGLE"
],
"video": {
"codecs": [
"H_264_42",
"H_264_41"
]
}
},
"Extensions": {
"available": {
"aplext:backstack:10": {}
}
},
"Advertising": {
"advertisingId": "00000000-0000-0000-0000-000000000000",
"limitAdTracking": true
},
"System": {
"application": {
"applicationId": "amzn1.ask.skill.20a7255b-e6b4-4328-aa03-2b0aa6e53b5b"
},
"user": {
"userId": "amzn1.ask.account.AMAR7ONMIGNT44YC3SLVLMAARW5MDRYY6LDIYEMZJVVYDYRFAPQKX3RZBLR736N2JF6HAZSJYLXXF4NZ65MQ3HLGMPJ76KAVIIUUKP6PGTF7SHVWZNHHEGZ7LB2QX3PC57IZ6VFFVWJC5LHGRYNKFGCEIMGK2KTZ7OMGE3ZPMANXSWUEBHAMVOCIG6DVGHMII3KVVHPH2ECL4G6Q5DSTCE3YR6TDLBT2UPHHYVSQCBOQ"
},
"device": {
"deviceId": "amzn1.ask.device.AMAZFZBJRMVDO746EWSGOX3PRO5FSWRAD4W2J5ZOGPVZMBWL76NZK4A3Z6CDHWV7YMQSISWFZH2QGFASJ2PRXCQCCJKBIA2K6XW5NN46XC3O2XYJZ2ZYSPJNVLKQYOHVVPCRAEAY2Y4T7XCS45PJHSRMEW324FXD2SZ3K6GTPO7KFTFPROIEQRWZ3S4WCQ23VXTKOL3REPBRQSCO",
"supportedInterfaces": {}
},
"apiEndpoint": "https://api.amazonalexa.com",
"apiAccessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJhdWQiOiJodHRwczovL2FwaS5hbWF6b25hbGV4YS5jb20iLCJpc3MiOiJBbGV4YVNraWxsS2l0Iiwic3ViIjoiYW16bjEuYXNrLnNraWxsLjIwYTcyNTViLWU2YjQtNDMyOC1hYTAzLTJiMGFhNmU1M2I1YiIsImV4cCI6MTcwNDk1MzkzMCwiaWF0IjoxNzA0OTUzODcwLCJuYmYiOjE3MDQ5NTM4NzAsInByaXZhdGVDbGFpbXMiOnsiY29udGV4dCI6IkFBQUFBQUFBQUFEVmdycUNiVkpqdStVcXBFUllIZWNpVGdFQUFBQUFBQUJQeUhVRENuN1BpanpIUC8yWk1mY3NBVHR6eWhMUUZtYjlFaUVSVTRoSGgyQ1piRU1BWWpnQVNqOVlaS1BZUThwUzBtSmtaZ1htQTAwR3N1U1graE9EUmpEdTlocGhTeGxmTXNvc2p2RWRDcDdQcmxNRlhZOURoRWlFSWZTQkV0ay96MU1YTktoWTZVN2pua1dlMEhXMG9FaVF4NTZTN0NSRldZUW9WTmRyTHRLdjJzb05PdDk3Yk11QzVqamIwKy9RM1dWUHRBL3BRU0kzV015dXVNaGVqelVSbmc1aFB0SGt4N0ptRHc3bmEwajl1aERFOWhSZTNBSG5FSURka2JyTlRMcFQvMzR1d09TdlRuZGlyMnF1bytialJZTjgybjZBd1IrSE1VM1U5Nytpd3VzMXlzQUFaZy9ZZzk0N2VwSUlYMVA5cm1ML3BrdUdicUpnaG92UzRoR3ZXbjZMUG9PNFBSaTVHVlFEVTU0c2VwWTA0ajVUVkFISmZhTFJhdmg3UkpDUU00RmRIOVYyOHgzZUk0TG9oWWR2aVM1S0JqSmJ3aHdlUXJQc0duUTMzUDVBZHkxWjAvK3hJQnd5IiwiZGV2aWNlSWQiOiJhbXpuMS5hc2suZGV2aWNlLkFNQVpGWkJKUk1WRE83NDZFV1NHT1gzUFJPNUZTV1JBRDRXMko1Wk9HUFZaTUJXTDc2TlpLNEEzWjZDREhXVjdZTVFTSVNXRlpIMlFHRkFTSjJQUlhDUUNDSktCSUEySzZYVzVOTjQ2WEMzTzJYWUpaMlpZU1BKTlZMS1FZT0hWVlBDUkFFQVkyWTRUN1hDUzQ1UEpIU1JNRVczMjRGWEQyU1ozSzZHVFBPN0tGVEZQUk9JRVFSV1ozUzRXQ1EyM1ZYVEtPTDNSRVBCUlFTQ08iLCJ1c2VySWQiOiJhbXpuMS5hc2suYWNjb3VudC5BTUFSN09OTUlHTlQ0NFlDM1NMVkxNQUFSVzVNRFJZWTZMRElZRU1aSlZWWURZUkZBUFFLWDNSWkJMUjczNk4ySkY2SEFaU0pZTFhYRjROWjY1TVEzSExHTVBKNzZLQVZJSVVVS1A2UEdURjdTSFZXWk5ISEVHWjdMQjJRWDNQQzU3SVo2VkZGVldKQzVMSEdSWU5LRkdDRUlNR0syS1RaN09NR0UzWlBNQU5YU1dVRUJIQU1WT0NJRzZEVkdITUlJM0tWVkhQSDJFQ0w0RzZRNURTVENFM1lSNlRETEJUMlVQSEhZVlNRQ0JPUSJ9fQ.ExffPoK4dQwcX-LZMfld9tYstnwqrmrNjW_ERpWvm1ZC3PXrEaOBCK65eGLs4PW4CNtmAJMKqFPvUhbWH-ma2bm5U3aA0IZEybmBSj7-CsOvMz61FzWGBNTvueubSjVOabjB0gOGG88ly__ddT-oegi43GUT9ReAGI0A2sMWOQM6UMRQvZFSuPy9v8aRB2Rbxb5mfrNBMtkK2Y7CIo-Vk-YOS0hUy0olvYVDZCqqswP8QnoRGUjVwg31pUDw08OxdJtiwdafn8Ckdpn1RnBEb3BVs0Sf9aLBEQK9AxX7r_yVqcxWw89qgE4RBhZxs7I-bPm6obAfvCSs2fPkcxebRg"
}
},
"request": {
"type": "LaunchRequest",
"requestId": "amzn1.echo-api.request.43e7975f-518d-4c5f-8631-b96a810b4ba6",
"locale": "en-US",
"timestamp": "2024-01-11T06:17:50Z",
"shouldLinkResultBeReturned": false
}
}
JSON 输出 1 为空。
该响应意味着您的后端技能处理程序代码返回错误。您需要查看与该代码关联的日志以了解出了什么问题。如果该技能是 Alexa 托管的,那么您可以通过 Alexa 开发控制台中的“代码”页面访问日志,然后您将在菜单栏中看到 Cloudwatch Logs 下拉列表。可能会显示多个区域,这是选择每个区域并查看日志创建位置的情况(可能不是“默认”区域)。 如果不是 Alexa 托管,那么如果使用 AWS Lambda,请转到 Cloudwatch AWS 页面查找您的日志。