Access to intent slots from lambda function - python

I created a simple bot with a simple lex intent. This lex intent has one single slot slotTwo.
Then I linked it with a python lambda function. I want to access to the value of that slot from inside the lambda function.
python lambda function receives the parameters context and event. The link below shows the structure for the parameter event.
https://docs.aws.amazon.com/lex/latest/dg/lambda-input-response-format.html
I copy it here too:
{
"currentIntent": {
"name": "intent-name",
"slots": {
"slot name": "value",
"slot name": "value"
},
"slotDetails": {
"slot name": {
"resolutions" : [
{ "value": "resolved value" },
{ "value": "resolved value" }
],
"originalValue": "original text"
},
"slot name": {
"resolutions" : [
{ "value": "resolved value" },
{ "value": "resolved value" }
],
"originalValue": "original text"
}
},
"confirmationStatus": "None, Confirmed, or Denied (intent confirmation, if configured)"
},
"bot": {
"name": "bot name",
"alias": "bot alias",
"version": "bot version"
},
"userId": "User ID specified in the POST request to Amazon Lex.",
"inputTranscript": "Text used to process the request",
"invocationSource": "FulfillmentCodeHook or DialogCodeHook",
"outputDialogMode": "Text or Voice, based on ContentType request header in runtime API request",
"messageVersion": "1.0",
"sessionAttributes": {
"key": "value",
"key": "value"
},
"requestAttributes": {
"key": "value",
"key": "value"
}
}
However, when I print out the content of this parameter I see only the slot and I can access to its value directly, first level.
START RequestId: 60a541d8-b3c8-48b0-a7a3-6b3f65d96482 Version: $LATEST
{
"slotTwo": "some text here"
}
The test from the lambda console works fine. It is able to retrieve the value and continue the logic.
However when I test the bot from Lex it does not work.
Any idea why?
Thanks so much
Ester

My bad.
I missed that the values indicated in the lamdda test must correspond to the whole JSON structure. So now I added this as event test:
{
"currentIntent": {
"name": "intent-name",
"slots": {
"slotTwo": "Hi from the sendmessagetest"
}
}
}
and I access the slot within the lambda this way:
messagetext = event['currentIntent'] ['slots'] ['slotTwo']
Feel free to delete my post if you find it confusing.
Thanks everyone

Related

AWS Lex: sending a response from lambda function with python

I am having trouble with sending a JSON response from my python3.8 lambda function (default lambda_handler function). I am pretty sure I understand what I am doing after reading most of the docs and the Lambda Function Input Event and Response Format. from that resource, it says the only required section is the 'dialogAction' section.
Right now, my lex-bot has 1 intent and one slot. I know that this works because when I add a logger to the code, I can see that my lambda function is recieving confirmed JSON format.
My code tries to send a final response from the lambda function, but when I run the lex-bot in the console I get the following error:
Invalid Lambda Response: Received invalid response from Lambda: Can not construct instance of IntentResponse, problem: The validated object is null at [Source: {"dialogAction": {"type": "Close", "fulfillmentState": "Fulfilled", "message": {"contentType": "PlainText", "content": "milk"}}}; line: 1, column: 128]
Here is my python code:
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
def lambda_handler(event, context):
# print
item = event["sessionState"]["intent"]["slots"]["MilkProduct"]["value"]["resolvedValues"][0]
logger.debug(item)
return{
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": item
}
}
}
I do not think this is necessary for you to see, but here is what the lex-bot is sending me after it confirms the slot for the intent has been confirmed:
{
"sessionId": "120304235774457",
"inputTranscript": "I want to buy milk",
"interpretations": [
{
"intent": {
"slots": {
"MilkProduct": {
"shape": "Scalar",
"value": {
"originalValue": "milk",
"resolvedValues": [
"milk"
],
"interpretedValue": "milk"
}
}
},
"confirmationState": "None",
"name": "BuyCream",
"state": "ReadyForFulfillment"
},
"nluConfidence": 1
},
{
"intent": {
"slots": {},
"confirmationState": "None",
"name": "FallbackIntent",
"state": "ReadyForFulfillment"
}
}
],
"responseContentType": "text/plain; charset=utf-8",
"invocationSource": "FulfillmentCodeHook",
"messageVersion": "1.0",
"sessionState": {
"intent": {
"slots": {
"MilkProduct": {
"shape": "Scalar",
"value": {
"originalValue": "milk",
"resolvedValues": [
"milk"
],
"interpretedValue": "milk"
}
}
},
"confirmationState": "None",
"name": "BuyCream",
"state": "ReadyForFulfillment"
},
"originatingRequestId": "417dff57-5260-45cc-81a7-06df13fbee9a"
},
"inputMode": "Text",
"bot": {
"aliasId": "TSTALIASID",
"aliasName": "TestBotAlias",
"name": "Shopping",
"version": "DRAFT",
"localeId": "en_US",
"id": "JTGNDOEVQG"
}
}
Can someone please tell me what I am doing wrong? I have been at this for hours and I seriously do not know what I am doing wrong.
Thanks

Elasticsearch not returning result for single word query

I have a basic Elasticsearch index that consists of a variety of help articles. Users can search for them in my Python/Django app.
The index has the following mappings:
{
"mappings": {
"properties": {
"body": {
"type": "text"
},
"category": {
"type": "nested",
"properties": {
"category_id": {
"type": "long"
},
"category_title": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
}
}
},
"title": {
"type": "keyword"
},
"date_updated": {
"type": "date"
},
"position": {
"type": "integer"
}
}
}
}
I basically want the user to be able to search for a query and get any results that match the article title or category.
Say I have an article called "I Can't Remember My Password" in the "Your Account" category.
If I search for the article title exactly, I see the result. If I search for the category title exactly, I also see the result.
But if I search for just "password", I get nothing. What do I need to change in my setup/query to make it so that this query (or similarly non-exact queries) also returns the result?
My query looks like:
{
"query": {
"bool": {
"should": [{
"multi_match": {
"fields": ["title"],
"query": "password"
}
},
{
"nested": {
"path": "category",
"query": {
"multi_match": {
"fields": ["category.category_title"],
"query": "password"
}
}
}
}
]
}
}
}
I have read other questions and experimented with various settings but no luck so far. I am not doing anything particularly special at index time in terms of preparing the fields so I don't know if that's something to look at. I'm just using the elasticsearch-dsl defaults.
The solution was to reindex the title field as text rather than keyword. The latter only allows exact matching.
Credit to LeBigCat for pointing that out in the comments. They haven't posted it as an answer so I'm doing it on their behalf to improve visibility.

how to access error information in Step Function from previous Function in Catch state

I am wondering how to access the cause of my custom exception raised inside my lambda function. I need to access it at the end of my Step Functions workflow, as shown below.
The diagram below is an example of a failed execution. The error (error-info object, with its' own Error and Cause sections) is found in the output of ParseTextractOutput, but I am wondering how to access it in OutputNotFound as shown below.
Step Functions Diagram
Output
The output of ParseTextractOutput is
{
"event"...
"error-info": {
"Error": "OutputNotFoundException",
"Cause": "{\"errorMessage\": \"Contents of Textracted file: {...}}"
}
}
}
I'd like to access this data somehow in these fields (of the Step Functions definition):
...
"States": {
"OutputNotFound": {
"Type": "Fail",
"Error": "<useful stuff here, like $.error-info.Error or something>",
"Cause": "<useful stuff here, like $.error-info.Cause or something>"
},
...
"ParseTextractOutput": {
"Type": "Task",
"Resource": "functionARN",
"Catch": [
{
"ErrorEquals": ["OutputNotFoundException"],
"ResultPath": "$.error-info",
"Next": "OutputNotFound"
}
],
"End": true
}
Python Code
Here's the relevant code for the Function ParseTextractOutput.
class OutputNotFoundException(Exception):
pass
...
try:
blocks = data['Blocks']
except KeyError as e:
raise OutputNotFoundException('Contents of Textracted file: {}'.format(data))
At the moment (with the current version of https://states-language.net/spec.html) Fail.Error and Fail.Cause cannot be dynamic. The input that is passed to Fail state is ignored and fix strings are used for error and cause.
We can view Fail as a point in the execution to announce a fix message, indicate end of execution with an error and exit.
This means any processing has to be done before these announcement points. As #frosty mentioned in the comments a Choice state can be useful.
Alternative 1: using Choice
Here is an example:
Let's say I have this Python code in my Lambda function:
class OutputNotFoundException(Exception):
pass
def lambda_handler(event, context):
raise OutputNotFoundException('Error message A')
When function returns, the output will be a JSON like this:
{
"Error": "OutputNotFoundException",
"Cause": "{\"errorMessage\":\"Error message A\",\"errorType\":\"OutputNotFoundException\",\"stackTrace\":[\"...\\n\"]}"
}
Notice how "Cause" is another JSON which is string encoded. We can convert OutputNotFound to Pass and use intrinsic function StringToJson() to convert the encoded string to normal JSON for easier processing later:
"OutputNotFound": {
"Type": "Pass",
"Parameters": {
"details.$": "States.StringToJson($.Cause)"
},
"Next": "Error message?"
},
Now we have an output like this:
{
"details": {
"errorMessage": "Error message A",
"errorType": "OutputNotFoundException",
"stackTrace": ["...\n"]
}
}
Next state will be a Choice which looks into $.details.errorMessage to decide a proper Fail state:
"Error message?": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.details.errorMessage",
"StringEquals": "Error message A",
"Next": "Error A"
},
{
"Variable": "$.details.errorMessage",
"StringEquals": "Error message B",
"Next": "Error B"
}
],
"Default": "Unknown Error"
},
Each choice is now pointing to a normal Fail state to announce a fix string:
"Error A": {
"Type": "Fail",
"Error": "OutputNotFoundException",
"Cause": "OutputNotFoundException of type A happened"
},
Alternative 2: End with Pass
If your intention is to have the exact error message as output of your execution for later logging/processing one way can be leaving at the Pass state:
"OutputNotFound": {
"Type": "Pass",
"Parameters": {
"details.$": "States.StringToJson($.Cause)",
"isError": true
},
"End": true
}
The downside of this solution is, of course, the execution ends with a successful status and we need to process output to discover there was an error (hence the extra isError field above)
Including a section of the workflow as one single branch of a Parallel state definition might be useful to define one exception scope, and take a new path with the exception details, like explained in this post:
{
"Comment": "Better error handling",
"StartAt": "ErrorHandler",
"States": {
"ErrorHandler": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "Hello",
"States": {
"Hello": {
"Type": "Pass",
"Result": "Hello",
"Next": "World"
},
"World": {
"Type": "Pass",
"Result": "World",
"Next": "Foo"
},
"Foo": {
"Type": "Pass",
"Result": "World",
"Next": "Bar"
},
"Bar": {
"Type": "Pass",
"Result": "World",
"End": true
}
}
}
],
"Catch": [
{
"ErrorEquals": [
"States.ALL"
],
"ResultPath": "$.error",
"Next": "Send Failure Message"
}
],
"Next": "Job Succeeded"
},
"Job Succeeded": {
"Type": "Succeed"
},
"Send Failure Message": {
"Type": "Pass",
"Next": "Fail Workflow"
},
"Fail Workflow": {
"Type": "Fail"
}
}
}

What is in the JSON payload Cloudwatch sends to SNS? How can I read that data?

I'm trying to parse data with Lambda when an alarm is triggered in Cloudwatch. I am currently using SNS to trigger the lambda; however, I want to know what data is being sent to that Lambda so that I can parse it correctly.
How can I read the JSON alarm data that's passed into Lambda by SNS?
The simplest and the most accurate way is to print(event) and actually see what's inside the payload, the official AWS doco provides the following structure for SNS event Amazon SNS Sample Event, reference
{
"Records": [
{
"EventVersion": "1.0",
"EventSubscriptionArn": eventsubscriptionarn,
"EventSource": "aws:sns",
"Sns": {
"SignatureVersion": "1",
"Timestamp": "1970-01-01T00:00:00.000Z",
"Signature": "EXAMPLE",
"SigningCertUrl": "EXAMPLE",
"MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
"Message": "Hello from SNS!",
"MessageAttributes": {
"Test": {
"Type": "String",
"Value": "TestString"
},
"TestBinary": {
"Type": "Binary",
"Value": "TestBinary"
}
},
"Type": "Notification",
"UnsubscribeUrl": "EXAMPLE",
"TopicArn": topicarn,
"Subject": "TestInvoke"
}
}
]
}
also, this CloudWatch specific example could be useful
{
"Records": [
{
"EventSource": "aws:sns",
"EventVersion": "1.0",
"EventSubscriptionArn": "arn:aws:sns:eu-west-1:000000000000:cloudwatch-alarms:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"Sns": {
"Type": "Notification",
"MessageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"TopicArn": "arn:aws:sns:eu-west-1:000000000000:cloudwatch-alarms",
"Subject": "ALARM: \"Example alarm name\" in EU - Ireland",
"Message": "{\"AlarmName\":\"Example alarm name\",\"AlarmDescription\":\"Example alarm description.\",\"AWSAccountId\":\"000000000000\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 datapoint (10.0) was greater than or equal to the threshold (1.0).\",\"StateChangeTime\":\"2017-01-12T16:30:42.236+0000\",\"Region\":\"EU - Ireland\",\"OldStateValue\":\"OK\",\"Trigger\":{\"MetricName\":\"DeliveryErrors\",\"Namespace\":\"ExampleNamespace\",\"Statistic\":\"SUM\",\"Unit\":null,\"Dimensions\":[],\"Period\":300,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanOrEqualToThreshold\",\"Threshold\":1.0}}",
"Timestamp": "2017-01-12T16:30:42.318Z",
"SignatureVersion": "1",
"Signature": "Cg==",
"SigningCertUrl": "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.pem",
"UnsubscribeUrl": "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:000000000000:cloudwatch-alarms:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"MessageAttributes": {}
}
}
]
}
The actual payload sent by Cloudwatch to SNS,
{
"AlarmName": "Example alarm name",
"AlarmDescription": "Example alarm description.",
"AWSAccountId": "000000000000",
"NewStateValue": "ALARM",
"NewStateReason": "Threshold Crossed: 1 datapoint (10.0) was greater than or equal to the threshold (1.0).",
"StateChangeTime": "2017-01-12T16:30:42.236+0000",
"Region": "EU - Ireland",
"OldStateValue": "OK",
"Trigger": {
"MetricName": "DeliveryErrors",
"Namespace": "ExampleNamespace",
"Statistic": "SUM",
"Unit": null,
"Dimensions": [],
"Period": 300,
"EvaluationPeriods": 1,
"ComparisonOperator": "GreaterThanOrEqualToThreshold",
"Threshold": 1
}
}
If you want to read this event in Lambda,
import json
def lambda_handler(event, context):
alert_message = json.loads(json.dumps(event))["Records"][0]["Sns"]["Message"]
return {
'statusCode': 200,
'body': json.dumps(alert_message)
}
The easiest and simple way is to create a new subscription for the SNS Topic to which the cloudwatch sends alarm data, with Email-JSON protocol and enter your email and create subscription.
Confirm the subscription by clicking on the verification link in email. When CloudWatch sends an alarm next time, you will get the JSON data in email and then you can figure out how to parse it correctly.

Slack chat.postMessage attachment gives no_text

When I try to add an attachment to my message I either only get the text or if I leave out the text I get "error": "no_text", is there any way to send an attachmet with chat.postMessage?
This is the python code I use for sending the message:
r = requests.post('https://slack.com/api/chat.postMessage', params=json.loads("""
{
"token": "xoxp-mytokenhere",
"channel": "C4mychannelhere",
"attachments": [
{
"text": "Question?",
"fallback": "Question?",
"callback_id": "callback_id",
"color": "#3AA3E3",
"attachment_type": "default",
"actions": [
{
"name": "question",
"text": "Yes",
"style": "good",
"type": "button",
"value": "yes"
},
{
"name": "question",
"text": "Nope",
"style": "good",
"type": "button",
"value": "no"
}
]
}
]
}
"""))
Based on the comment I went with the following solution:
r = requests.post('https://slack.com/api/chat.postMessage', params=json.loads({
"token": "xoxp-mytokenhere",
"channel": "C4mychannelhere",
"attachments": json.dumps([
{
"text": "Question?",
"fallback": "Question?",
"callback_id": "callback_id",
"color": "#3AA3E3",
"attachment_type": "default",
"actions": [
{
"name": "question",
"text": "Yes",
"style": "good",
"type": "button",
"value": "yes"
},
{
"name": "question",
"text": "Nope",
"style": "good",
"type": "button",
"value": "no"
}
]
}
])
}))
It looks like you're trying to send a JSON string as your entire set of parameters to chat.postMessage.
chat.postMessage and other web API methods only support URL-encoded query or POST body parameters, so your fields like token and channel and attachments are sent as application/x-www-form-urlencoded key/value pairs instead.
To complicate things a little further, the attachments parameter actually does take a string of URL-encoded JSON data. Your JSON array needs to be URL-encoded and stuffed into that parameter.
Depending on your goals, you could either skip using json.loads and just pass that JSON string as your attachments parameter and requests will take care of URL-encoding it for you -- or you can use something like json.dump on a native Python array you build with the same attributes.

Categories

Resources