I have been trying to make a lambda function for a Lex chatbot I'm making, but whenever my intent calls upon the function, it keeps giving me the same error and I am tired of it. I am using node.js. The error message it gives me is:
An error has occurred: Invalid Lambda Response:
Received invalid response from Lambda: Can not construct instance of
IntentResponse: no String-argument constructor/factory method to
deserialize from String value ('this works') at
[Source: "this works"; line: 1, column: 1
This happens no matter what kind of lambda function I input. Any answers?
This is happening because all you're sending back is a String, whereas Lex expects replies in specific formats e.g.
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled or Failed",
"message": {
"contentType": "PlainText or SSML",
"content": "Message to convey to the user. For example, Thanks, your pizza has been ordered."
},
"responseCard": {
"version": integer-value,
"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"
}
]
}
]
}
}
This code will work:
function close(sessionAttributes, fulfillmentState, message, responseCard) {
return {
sessionAttributes,
dialogAction: {
type: 'Close',
fulfillmentState,
message,
responseCard,
},
};
}
function dispatch(intentRequest, callback) {
const outputSessionAttributes = intentRequest.sessionAttributes || {};
callback(close(outputSessionAttributes, 'Fulfilled', { contentType: 'PlainText',
content: 'Thank you and goodbye' }));
}
function loggingCallback(response, originalCallback) {
originalCallback(null, response);
}
exports.handler = (event, context, callback) => {
try {
console.log("event: " + JSON.stringify(event));
dispatch(event, (response) => loggingCallback(response, callback));
} catch (err) {
callback(err);
}
};
It simply sends back "Thank you and goodbye" in the required format, in this case with a "dialogAction" type of "Close" - which informs Lex not to expect a response from the user.
There are other types - this and more are all explained in the Lex documentation.
Related
I use a Dialogflow API as NLP and the interface that we use is Whatsapp API.
my problem is, when I want to bypass Text and Whatsapp client number to Dialogflow (my reference), I didn't found document to explain that. for comparison, the Telegram official integration dialogflow, from the body request we can extract that data like name and Telegram user ID.
const sessionId = phone_number_id; //session ID get from phone number
const sessionPath = sessionClient.projectAgentSessionPath(projectId, sessionId);
const request = {
session: sessionPath,
queryInput: {
text: {
text: msg_body,
languageCode: "id-ID"
},
},
payload: {
data: "testing",
phoneNumber : phone_number_id
}
};
console.log("request", request);
await sessionClient.detectIntent(request).then(responses => {
console.log("DetectIntent", JSON.stringify(responses));
}).catch(err => {
console.error("ERROR:", err);
})
I tried it with request variable like that but in request body in dialogflow fulfillment, it never showed up
{
"responseId": "censored",
"queryResult": {
"queryText": "halo",
"action": "input.welcome",
"parameters": {},
"allRequiredParamsPresent": true,
"fulfillmentText": "error",
"fulfillmentMessages": [
{
"text": {
"text": [
"error"
]
}
}
],
"outputContexts": [
{
"name": "censored",
"parameters": {
"no-input": 0,
"no-match": 0
}
}
],
"intent": {
"name": "censored",
"displayName": "Default Welcome Intent"
},
"intentDetectionConfidence": 1,
"languageCode": "id"
},
"originalDetectIntentRequest": {
"payload": {}
},
"session": "censored"
}
#Maulana ahmad, As you have mentioned in the comment below example code can be referred to extract data from the body request.
const dialogflow = require('dialogflow');
// Import the JSON to gRPC struct converter
const structjson = require('./structjson.js');
// Instantiates a sessison client
const sessionClient = new dialogflow.SessionsClient();
// The path to identify the agent that owns the created intent.
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
event: {
name: eventName,
parameters: structjson.jsonToStructProto({foo: 'bar'}),
languageCode: languageCode,
},
},
};
sessionClient
.detectIntent(request)
.then(responses => {
console.log('Detected intent');
logQueryResult(sessionClient, responses[0].queryResult);
})
.catch(err => {
console.error('ERROR:', err);
});
This Stack Overflow link can be referred for more information.
Posting the answer as community wiki for the benefit of the community that might encounter this use case in the future.
Feel free to edit this answer for additional information.
I am new to AWS and got the following error when I tried to input data to the dynamodb invoking the lambda function between the 'API gateway' and the 'DynamoDB'.
Error:
Expected params.Item['Age'].S to be a string........
Screenshot of the Error:
Code:
I tried in the browser (CodePen) (I used the correct Invoke URL from the API gateway):
var xhr = new XMLHttpRequest();
xhr.open('POST', 'The API Invoke URL');
xhr.onreadystatechange = function(event){
console.log(event.target.response);
}
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({age: 26, height: 71, income: 2400}));
The following lambda function is invoked when running the above code from CodePen:
There I have imported the aws-sdk and dynamodb correctly.
exports.fn = (event, context, callback) => {
const params = {
Item: {
"UserId": {
S: "user_" + Math.random()
},
"Age": {
N: event.age
},
"Height": {
N: event.height
},
"Income": {
N: event.income
}
},
TableName: "compare-yourself"
};
dynamodb.putItem(params, function(err, data) {
if (err) {
console.log(err);
callback(err);
} else {
console.log(data);
callback(null, data);
}
});
};
In the above lambda function you can observe that I have formatted the inputs as numbers but in the API gateway, in the POST integration request I have converted the inputs to strings. so the data that is passed via the lambda function is already a string. No need to format by the Lambda function, again.
Body mapper in 'POST Integration-Request':
#set($inputRoot = $input.path('$'))
{
"age" : "$inputRoot.age",
"height": "$inputRoot.height",
"income": "$inputRoot.income"
}
I need to know the reason for the above error and am happy to provide any additional information required.
Thank you in advance.
Change the params to indicate that the value of the age field is "String" and not "Numeric":
const params = {
Item: {
"UserId": {
S: "user_" + Math.random()
},
"Age": {
"S": event.age # This was previously set to "N" which causes the issue
},
"Height": {
N: event.height
},
"Income": {
N: event.income
}
},
TableName: "compare-yourself"
};
This is my complete error on the test event details execution, whe i try to test my function this error its appears
{
"errorType": "TypeError",
"errorMessage": "Cannot read property 'NaN' of undefined",
"trace": [
"TypeError: Cannot read property 'NaN' of undefined",
" at findCar (/var/task/index.js:87:22)",
" at processTicksAndRejections (internal/process/task_queues.js:95:5)",
" at async Runtime.exports.handler (/var/task/index.js:46:17)"
]
}
This is the code for the lambda function for execute the requesting a car on my website...
const randomBytes = require('crypto').randomBytes;
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient();
const params = {
TableName : 'Cars'
}
async function listCars(){
console.log("Starting query to fetch cars data.")
try {
const data = await ddb.scan(params).promise()
console.log("Data retrieved, Printing Data from Cars table......")
console.log(data)
return data
} catch (err) {
console.log("Error occurred while retrieving cars......" + err)
return err
}
}
exports.handler = async (event, context, callback) => {
if (!event.requestContext.authorizer) {
errorResponse('Authorization not configured', context.awsRequestId, callback);
return;
}
const rideId = toUrlString(randomBytes(16));
console.log('Received event (', rideId, '): ', event);
// Because we're using a Cognito User Pools authorizer, all of the claims
// included in the authentication token are provided in the request context.
// This includes the username as well as other attributes.
const username = event.requestContext.authorizer.claims['cognito:username'];
// The body field of the event in a proxy integration is a raw string.
// In order to extract meaningful values, we need to first parse this string
// into an object. A more robust implementation might inspect the Content-Type
// header first and use a different parsing strategy based on that value.
const requestBody = JSON.parse(event.body);
const pickupLocation = requestBody.PickupLocation;
const car = await findCar(pickupLocation);
return recordRide(rideId, username, car).then(() => {
// You can use the callback function to provide a return value from your Node.js
// Lambda functions. The first parameter is used for failed invocations. The
// second parameter specifies the result data of the invocation.
// Because this Lambda function is called by an API Gateway proxy integration
// the result object must use the following structure.
callback(null, {
statusCode: 201,
body: JSON.stringify({
RideId: rideId,
Car: car,
CarName: car.carName,
Eta: '30 seconds',
Rider: username,
}),
headers: {
'Access-Control-Allow-Origin': '*',
},
});
}).catch((err) => {
console.error("printing error on record ride: "+ err);
// If there is an error during processing, catch it and return
// from the Lambda function successfully. Specify a 500 HTTP status
// code and provide an error message in the body. This will provide a
// more meaningful error response to the end client.
errorResponse(err.message, context.awsRequestId, callback)
});
};
// This is where you would implement logic to find the optimal car for
// this ride (possibly invoking another Lambda function as a microservice.)
// For simplicity, we'll just pick a car at random.
async function findCar(pickupLocation) {
console.log('Finding car for ', pickupLocation.Latitude, ', ', pickupLocation.Longitude);
const cars = await listCars();
return cars.Items[Math.floor(Math.random() * cars.Count)];
}
function recordRide(rideId, username, car) {
console.log("Car inside recordRide function" + car.carName)
return ddb.put({
TableName: 'Rides',
Item: {
RideId: rideId,
User: username,
Car: car,
CarName: car.carName,
RequestTime: new Date().toISOString(),
},
}).promise();
}
function toUrlString(buffer) {
return buffer.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
function errorResponse(errorMessage, awsRequestId, callback) {
callback(null, {
statusCode: 500,
body: JSON.stringify({
Error: errorMessage,
Reference: awsRequestId,
}),
headers: {
'Access-Control-Allow-Origin': '*',
},
});
}
And finally my testEvent code that I establish for testing my function...
{
"path": "/ride",
"httpMethod": "POST",
"headers": {
"Accept": "*/*",
"Authorization": "fDjWQiOiJLTzRVMWZs",
"content-type": "application/json; charset=UTF-8"
},
"queryStringParameters": null,
"pathParameters": null,
"requestContext": {
"authorizer": {
"claims": {
"cognito:username": "the_username"
}
}
},
"body": "{\"PickupLocation\":{\"Latitude\":47.6174755835663,\"Longitude\":-122.28837066650185}}"
}
I hope that you can help me with this...
You aren't checking that anything is returned from the database after querying for cars. If the returned data has neither Items nor Count properties, then you get the error that you see:
> cars = {}
{}
> cars.Items[Math.floor(Math.random() * cars.Count)]
Uncaught TypeError: Cannot read property 'NaN' of undefined
I setup PushNotification.onNotification from #aws-amplify/pushnotification like so:
if (notification.foreground) {
console.log('notification received in foreground ', notification);
} else {
console.log('notification received in background ', notification);
}
if (PushNotificationIOS !== undefined) {
notification.finish(PushNotificationIOS.FetchResult.NoData);
}
});
On Android it's working fine. On iOS I can't trigger the onNotification/onNotificationOpened events, although I receive the notifications. There is a special case when I send the "Standard Message" via Pinpoint -> Test Messaging, which actually triggers the function, and when I log the notification it looks like this
'notification received in background ',
{ _data:
{ remote: true,
notificationId: '45BAA2A6-8676-402E-8E6B-03A69173AC8C' },
_remoteNotificationCompleteCallbackCalled: false,
_isRemote: true,
_notificationId: '45BAA2A6-8676-402E-8E6B-03A69173AC8C',
_alert: { title: ' test', body: 'qwerty' },
_sound: undefined,
_badgeCount: undefined,
_category: undefined,
_contentAvailable: 1,
_threadID: undefined }
(the notification was received in the foreground, but the message doesn't contain that info, so it prints as background. Both foreground/background will trigger the event when using Pintpoint -> Test messaging -> Standard message). Also Pinpoint Raw message does not work. SNS Identical / custom payload does not work.
I have augmented AppDelegate as according to
https://github.com/react-native-push-notification-ios/push-notification-ios#augment-appdelegate
Anybody knows if amplify expects a certain payload in order to trigger the event? Or if something else could be causing this..?
Structures tested so far:
The SNS custom structure
"APNS_SANDBOX": "{\"aps\":{\"alert\":\"Sample message for iOS development endpoints\"}}"
}
Pinpoint raw message
{
"APNSMessage": {
"aps": {
"alert": ""
}
},
"GCMMessage": {
"data": {
"message": ""
}
},
"ADMMessage": {
"data" : {
"message": ""
}
},
"BaiduMessage": {
"title":"",
"description": ""
}
}
The solution was to add content-available and additional data to access the title, body and data in iOS of amplify PushNotification.
{
aps: {
alert: {
title: "title",
body: "message"
},
"content-available": "1",
key1: "value",
key2: "value",
},
}
Stringified
"{\"aps\":{\"alert\":{\"title\":\"title\",\"body\":\"message\"},\"content-available\":\"1\",\"key1\":\"value\",\"key2\":\"value\"}}"
I'm trying to use the before Remote hook in loopback to check an user token before the call of my remote method, but the thing is that I can only return a javascript Error, like these.
"error": {
"statusCode": 401,
"name": "Error",
"message": ""
}
This is the code that i'm using in the before remote.
Model.beforeRemote('method', function (context, unused, next) {
let token = Model.app.models.Token;
let id = context.args.Id;
let date = moment();
Rx.Observable.fromPromise(token.find({
where: {
and: [
{ id: id, },
{ expiration: { gt: date } }
]
}
})).subscribe((token => {
if (token.length > 0) {
next();
} else {
let err = new Error();
err.status = 401;
delete err.stack;
return next()
}
}))
});
And I need a "custom" response that isn't an error, something like these.
{
"success": false,
"data": {
"service": "self",
"operation": "rest",
"code": "unauthorized"
},
"message": "Invalid token"
}
I tried with the after Remote hook and I can change the response to get something like that, but I want to get a quicker response in the case that the token is invalid.
Is there any way to achieve this with the before hook? or I had to use after hook?
Thanks
You may want to pass the error to the next function:
return next(err)