First alexa skill - node.js

I am trying to develop my first Alexa skill using Node.js, and every time I try to test it I get "There was a problem with the requested skill's response".
I am trying create a random restaurant generator. Pretty simple its an array of restaurants, a random index is selected, and Alexa says the restaurant. I don't know where I went wrong I have uploaded my .json and .js files if anyone can help i'd really appreciate it.
index.js:
const Alexa = require('alexa-sdk');
const APP_ID = 'amzn1.ask.skill.9350e65b-fb41-48ce-9930-98b5156eb63c';
const handlers = {
'LaunchRequest': function () {
this.emit('randomRestaurantGeneratorIntent');
},
'randomRestaurantGeneratorIntent': function () {
var randomResturant;
var foodArray = ['IHOP', 'Dennys', 'burger king'];
randomResturant = foodArray[Math.floor(Math.random() * foodArray.length)];
this.response.speak(randomResturant);
this.emit(':responseReady');
},
'AMAZON.HelpIntent': function () {
const say = 'You can say what did I learn, or, you can say exit... How can I help you?';
this.response.speak(say).listen(say);
this.emit(':responseReady');
},
'AMAZON.CancelIntent': function () {
this.response.speak('Bye!');
this.emit(':responseReady');
},
'AMAZON.StopIntent': function () {
this.response.speak('Bye!');
this.emit(':responseReady');
}
};
exports.handler = function (event, context, callback) {
const alexa = Alexa.handler(event, context, callback);
alexa.APP_ID = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
};
randomResturantGeneratorIntent.JSON:
{
"interactionModel": {
"languageModel": {
"invocationName": "random restaurant generator",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "randomRestaurantGeneratorIntent",
"slots": [],
"samples": [
"Launch Random Restaurant Generator "
]
}
],
"types": []
}
}
}
Thank you

Try this way to render responses.
var speechOutput = 'Your response here';
var reprompt = "How can I help?";
this.response.speak(speechOutput);
this.response.listen(reprompt);
this.emit(":responseReady");

Try this function in inline editor for your first skill. and try to test with open random restaurant generator,
/**
* Called when the user launches the skill without specifying what they want.
*/
function onLaunch(launchRequest, session, callback) {
console.log(`onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}`);
// Dispatch to your skill's launch.
getWelcomeResponse(callback);
}
function buildResponse(sessionAttributes, speechletResponse) {
return {
version: '1.0',
sessionAttributes,
response: speechletResponse,
};
}
function getWelcomeResponse(callback) {
// If we wanted to initialize the session to have some attributes we could add those here.
const sessionAttributes = {};
const cardTitle = 'Welcome';
const speechOutput = 'Welcome to Your First Alexa Skill';
// If the user either does not reply to the welcome message or says something that is not
// understood, they will be prompted again with this text.
const repromptText = 'Please tell me What do you want to know?';
const shouldEndSession = false;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: 'PlainText',
text: output,
},
//For testing purpose only
// card: {
// type: 'Simple',
// title: `SessionSpeechlet - ${title}`,
// content: `SessionSpeechlet - ${output}`,
// },
reprompt: {
outputSpeech: {
type: 'PlainText',
text: repromptText,
},
},
shouldEndSession,
};
}
exports.handler = (event, context, callback) => {
try {
console.log(`event.session.application.applicationId=${event.session.application.applicationId}`);
if (event.request.type === 'LaunchRequest') {
onLaunch(event.request,
event.session,
(sessionAttributes, speechletResponse) => {
callback(null, buildResponse(sessionAttributes, speechletResponse));
});
}
}
catch (err) {
callback(err);
}
};

I’ve been using lambda for two years and it’s terrible to debug and deploy for me until I started to use aws cloud9.
I suggest that you use aws cloud9 which is a cloud IDE for writing, running and debugging code. You could run the lambda function as local environment.
Check their website to get more information. It’s time consuming, but totally worth it, especially if you want to develop an Alexa skill.

Most of the times you get that error for 2 things:
You don't have the trigger "Alexa Skill Kit" in your lambda function. If you don't have it, you can add one in the designer tab of the configuration of the lambda function.
You don't have the neccesary modules in your lambda function. You can add them locally with "npm install ask-sdk-core" and then upload the folder.

Use this way:
var speechOutput = 'Your response here';
var reprompt = "How can I help?";
this.response.speak(speechOutput);
this.response.listen(reprompt);
this.emit(":responseReady");

Related

AWS PUT request met with "Provided key element does not match schema."

(Edited to incorporate comments)
So I apologize in advance for the long question. I don't know how else to ask it.
I'm trying to finish up a full-stack web app using React, Node, and DynamoDB. POST and GET requests are working fine, but I'm stuck on PUT. My mock PUT request works fine, but once I try it from the front end in React, I get the error mentioned in the title. I'll show the back end code first, then the mock update, and then the front end.
import handler from "./libs/handler-lib";
import dynamoDb from "./libs/dynamodb-lib";
export const main = handler(async (event, context) => {
const data = JSON.parse(event.body);
const params = {
TableName: process.env.tableName,
Key: {
userId: event.requestContext.identity.cognitoIdentityId,
activityId: event.pathParameters.activityId
},
UpdateExpression: "SET title = :title, activityType = :activityType, activityRoutine = :activityRoutine, activityComment = :activityComment",
ExpressionAttributeValues: {
":title": data.title || null,
":activityType": data.activityType || null,
// ":activityRoutine": data.activityRoutine == '' ? "None" : data.activityRoutine,
// ":activityComment": data.activityComment == '' ? "None" : data.activityComment
":activityRoutine": data.activityRoutine || null,
":activityComment": data.activityComment || null
},
ReturnValues: "ALL_NEW"
};
await dynamoDb.update(params);
return { status: true };
This mock update event works without issue:
{
"body": "{\"title\":\"test\",\"activityType\":\"testing\",\"activityRoutine\":\"\",\"activityComment\":\"\"}",
"pathParameters": {
"activityId": "long-alphanumeric-id"
},
"requestContext": {
"identity": {
"cognitoIdentityId": "us-east-and-so-on"
}
}
}
But this code, which produces the exact same Javascript object as the mock, is not okay with AWS:
function saveActivity(activity) {
try {
return API.put("activities", `/activities/${id}`, {
body: activity
});
} catch(e) {
console.log("saveActivity error:", e);
}
}
async function handleSubmit(event) {
event.preventDefault();
setIsLoading(true)
try {
await saveActivity({
title: title, activityType: activityType, activityRoutine: activityRoutine, activityComment: activityComment
// "key": {userId: userId, activityId: activityId}
// "pathParameters": {"id": activityId},
// "requestContext": {"identity": {"cognitoIdentityId": userId}}
});
} catch(e) {
console.log(e)
setIsLoading(false)
}
}
If anyone needs to see more of the code, I'm happy to share, but I figured this question is already getting very long. Any code you see commented out has been tried before without success.
I'd also be happy if someone could point me in the right direction as far as the AWS documentation is concerned. I've been going off of a tutorial and modifying it where need be.
Any help is appreciated!

Unable to write item(s) to DynamoDB table utilizing DocumentClient - Nodejs

I'm absolutely brand new to DynamoDb and I'm trying to simply write an object from a NodeJS Lambda. Based on what I've read and researched I should probably be using DocumentClient from the aws-sdk. I also found the following question here regarding issues with DocumentClient, but it doesn't seem to address my specific issue....which I can't really find/pinpoint unfortunately. I've set up a debugger to help with SAM local development, but it appears to be only providing some of the errors.
The code's implementation is shown here.
var params = {
TableName: "March-Madness-Teams",
Item: {
"Id": {"S": randstring.generate(9)},
"School":{"S": team_name},
"Seed": {"S": seed},
"ESPN_Id": {"S": espn_id}
}
}
console.log(JSON.stringify(params))
dynamodb.put(params, (error,data) => {
if (error) {
console.log("Error ", error)
} else {
console.log("Success! ", data)
}
})
Basically I'm scrubbing a website utilizing cheerio library and cherry picking values from the DOM and saving them into the json object shown below.
{
"TableName": "March-Madness-Teams",
"Item": {
"Id": {
"S": "ED311Oi3N"
},
"School": {
"S": "BAYLOR"
},
"Seed": {
"S": "1"
},
"ESPN_Id": {
"S": "239"
}
}
}
When I attempt to push this json object to Dynamo, I get errors says
Error MultipleValidationErrors: There were 2 validation errors:
* MissingRequiredParameter: Missing required key 'TableName' in params
* MissingRequiredParameter: Missing required key 'Item' in params
The above error is all good in well....I assume it didn't like the fact that I had wrapped those to keys in strings, so I removed the quotes and sent the following
{
TableName: "March-Madness-Teams",
Item: {
"Id": {
"S": "ED311Oi3N"
},
"School": {
"S": "BAYLOR"
},
"Seed": {
"S": "1"
},
"ESPN_Id": {
"S": "239"
}
}
}
However, when I do that...I kind of get nothing.
Here is a larger code snippet.
return new Promise((resolve,reject) => {
axios.get('http://www.espn.com/mens-college-basketball/bracketology')
.then(html => {
const dynamodb = new aws.DynamoDB.DocumentClient()
let $ = cheerio.load(html.data)
$('.region').each(async function(index, element){
var preregion = $(element).children('h3,b').text()
var region = preregion.substr(0, preregion.indexOf('(') - 1)
$(element).find('a').each(async function(index2, element2){
var seed = $(element2).siblings('span.rank').text()
if (seed.length > 2){
seed = $(element2).siblings('span.rank').text().substring(0, 2)
}
var espn_id = $(element2).attr('href').split('/').slice(-2)[0]
var team_name = $(element2).text()
var params = {
TableName: "March-Madness-Teams",
Item: {
"Id": randstring.generate(9),
"School":team_name,
"Seed": seed,
"ESPN_Id": espn_id
}
}
console.log(JSON.stringify(params))
// dynamodb.put(params)
// .then(function(data) {
// console.log(`Success`, data)
// })
})
})
})
})
Can you try without the type?
Instead of
"School":{"S": team_name},
for example, use
"School": team_name,
From your code, I can see the mis promise on the dynamodb request. Try to change your lines :
dynamodb.put(params).then(function(data) {
console.log(`Success`, data)
})
to be :
dynamodb.put(params).promise().then(function(data) {
console.log(`Success`, data)
})
you can combine with await too :
await dynamodb.put(params).promise().then(function(data) {
console.log(`Success`, data)
})
exports.lambdaHandler = async (event, context) => {
const html = await axios.get('http://www.espn.com/mens-college-basketball/bracketology')
let $ = cheerio.load(html.data)
const schools = buildCompleteSchoolObject(html, $)
try {
await writeSchoolsToDynamo(schools)
return { statusCode: 200 }
} catch (error) {
return { statusCode: 400, message: error.message }
}
}
const writeSchoolsToDynamo = async (schools) => {
const promises = schools.map(async school => {
await dynamodb.put(school).promise()
})
await Promise.all(promises)
}
const buildCompleteSchoolObject = (html, $) => {
const schools = []
$('.region').each(loopThroughSubRegions(schools, $))
return schools
}
const loopThroughSubRegions = (schools, $) => {
return (index, element) => {
var preregion = $(element).children('h3,b').text()
var region = preregion.substr(0, preregion.indexOf('(') - 1)
$(element).find('a').each(populateSchoolObjects(schools, $))
}
}
const populateSchoolObjects = (schools, $) => {
return (index, element) => {
var seed = $(element).siblings('span.rank').text()
if (seed.length > 2) {
seed = $(element).siblings('span.rank').text().substring(0, 2)
}
var espn_id = $(element).attr('href').split('/').slice(-2)[0]
var team_name = $(element).text()
schools.push({
TableName: "March-Madness-Teams",
Item: {
"Id": randstring.generate(9),
"School": team_name,
"Seed": seed,
"ESPN_Id": espn_id
}
})
}
}
I know this is drastically different from what I started with but I did some more digging and kind of kind of worked to this...I'm not sure if this is the best way, but I seemed to get it to work...Let me know if something should change!
Oh I understand what you want.
Maybe you can see the code above works, but there is one concept you have to improve here about async - await and promise especially on lambda function.
I have some notes here from your code above, maybe can be your consideration to improve your lambda :
Using await for every promise in lambda is not the best approach because we know the lambda time limitation. But sometimes we can do that for other case.
Maybe you can change the dynamodb.put method to be dynamodb.batchWriteItem :
The BatchWriteItem operation puts or deletes multiple items in one or more tables.
Or If you have to use dynamodb.put instead, try to get improve the code to be like so :
const writeSchoolsToDynamo = async (schools) => {
const promises = schools.map(school => {
dynamodb.put(school).promise()
})
return Promise.all(promises)
}

How to intergrate lex with lambda in amazon aws?

I have a simple bot with the following logic.
Bot: select one of the following item your interested
(response card)
-ecommerce
-travel etc
Human : clicks eg travel
Bot: response card
-marketing
-digital
Here is what I have in my lambda function
'use strict';
exports.handler = (event, context, callback) => {
const sessionAttributes = event.sessionAttributes;
const slots = event.currentIntent.slots;
const videommerceType = slots.videommerceType;
// predefined list of available pizza
const validData = ['ecommerce', 'startup', 'lead generation', 'crm', 'travel'];
// negative check: if valid slot value is not obtained, inform lex that user is expected
// respond with a slot value
if (videommerceType && !(videommerceType === "") && validData.indexOf(videommerceType.toLowerCase()) === -1) {
let response = {
sessionAttributes: event.sessionAttributes,
dialogAction: {
type : "'ElicitSlot",
message: {
contentType: "PlainText or SSML",
content: "Message to convey to the user. For example, Thanks, your pizza has been ordered."
},
responseCard: {
version: "1",
contentType: "application/vnd.amazonaws.card.generic",
genericAttachments: [{
title: "card-title",
subTitle: "card-sub-title",
imageUrl: "URL of the image to be shown",
attachmentLinkUrl: "URL of the attachment to be associated with the card",
buttons: [{
text: "button-text",
value: "Value sent to server on button click"
}]
}]
}
}
}
callback(null, response);
}
let response = {
sessionAttributes: sessionAttributes,
dialogAction: {
type: "Delegate",
slots: event.currentIntent.slots
}
}
callback(null, response);
};
Unfortunately this not working, on lex bot I get the following error
Intent videommerceIntent is ReadyForFulfillment: name:jim videommerceType:ecommerce
What is wrong with my code? any help or similar working demo would be appreciated , thanks

Alexa Two Intents; Second Intent not triggering

I am having trouble making two intents to work in hello world demo in alexa. I added in AboutSarawakIntent to trigger another lambda function.
{
"interactionModel": {
"languageModel": {
"invocationName": "greet chief minister",
"intents": [
...,
{
"name": "HelloWorldIntent",
"slots": [],
"samples": [
"Ok",
"Awesome",
"Good",
"Great",
"Okay",
"Yes",
"Good Afternoon",
"Good Morning",
"Hello",
"Say Hello",
"Say hi",
"Tell Me More"
]
},
{
"name": "AboutSarawakIntent",
"slots": [],
"samples": [
"how do you do",
"I am fine",
"how are you"
]
},
...
],
"types": []
}
}
}
so the following codes are from lambda functions where i add AboutSarawakIntentHandler to listen to AboutSarawakIntent.
....
const HelloWorldIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'HelloWorldIntent';
},
handle(handlerInput) {
const randomNumber = Math.floor(Math.random() * speeches.length);
const speechText = speeches[randomNumber];
const continueSpeech = continues[randomNumber];
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(continueSpeech)
.getResponse();
}
};
const AboutSarawakIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'AboutSarawakIntent';
},
handle(handlerInput) {
const speechText = 'Welcome to Sarawak'
const continueText = 'I am honored to be here.';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(continueText)
.WithStandardCard('Greeting from Sarawak', 'Welcome Everybody', 'https://s1.bukalapak.com/img/6425275433/w-1000/banner_selamat_datang_di_pernikahan.jpg', 'https://s1.bukalapak.com/img/6425275433/w-1000/banner_selamat_datang_di_pernikahan.jpg')
.getResponse();
}
};
....
// 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,
AboutSarawakIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler,
IntentReflectorHandler) // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
.addErrorHandlers(
ErrorHandler)
.lambda();
I can trigger HelloWorldIntent successfully everytime when I use the utterances for it but the other one always give me back 'Sorry, I couldn't understand what you said. Please try again.' Can someone advise me where could go wrong?
I can finally trigger because my alexa device can't support Picture Output. so WithStandardCard always give me errors. so i change it to WithSimpleCard and it worked.
"https://ask-sdk-for-nodejs.readthedocs.io/en/latest/Building-Response.html"

How to return node js output to test-bot (aws-lex) frpm aws lambda

I am new to AWS lambda, lex and node-js, so this is very basic question:
I am trying to write a node-js lambda function which will return simple command output to test-bot (lex), I am able to log the correct output to cloud-watch but same is not getting returned to the test-bot - I believe that I have to use something like callback (I am able to return hardcoded string to auto-bot but not able to return my command's output) but not sure how to use it, below is code snip which I am trying to run, can you pl. help
var SSH = require('simple-ssh')
var ssh_test = new SSH({
host: 'xx.xx.xx.xx',
user: 'xyz',
pass: 'xyz'
});
exports.handler = (event, context, callback) => {
var test = event.currentIntent.slots.purchase,
ssh_test.exec('ls /tmp/', {
out: console.log.bind(console)
})
.exec('exit', {
out: console.log.bind(console)
}).start();
callback(null, {
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": "I AM ABLE TO RETURN THIS HARDCODED STRING TO BOT" //ALONG WITH THIS I WANT TO APPEND COMMAND OUTPUT ('ls /tmp/')
}
}
});
}
Try this,
var SSH = require('simple-ssh')
var ssh_test = new SSH({
host: 'xx.xx.xx.xx',
user: 'xyz',
pass: 'xyz'
});
exports.handler = (event, context, callback) => {
var test = event.currentIntent.slots.purchase,
ssh_test.exec('ls /tmp/', {
out: console.log.bind(console)
callback(null, {
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": "I AM ABLE TO RETURN THIS HARDCODED STRING TO BOT" //ALONG WITH THIS I WANT TO APPEND COMMAND OUTPUT ('ls /tmp/')
}
}
});
})
.exec('exit', {
out: console.log.bind(console)
}).start();
}

Resources