I am trying to make a template to write new functions for AWS Lambda to validate input from Lex. I am not having any trouble with fulfilling correct inputs, only in getting Lex to relay the prompt messages when the slots are incorrectly filled.
def hello_world(intent_request):
object_of_greeting = get_slots(intent_request)["HelloObject"]
type_of_greeting = get_slots(intent_request)["GreetingType"]
# add more of these calls if more slots are involved
source = intent_request['invocationSource']
if source == 'DialogCodeHook':
# Perform basic validation on the supplied input slot(s).
# Use the elicitSlot dialog action to re-prompt for the first violation detected.
slots = get_slots(intent_request)
validation_result = validate_greeting(object_of_greeting, type_of_greeting)
if not validation_result['isValid']:
slots[validation_result['violatedSlot']] = None
return elicit_slot(intent_request['sessionAttributes'],
intent_request['currentIntent']['name'],
slots,
validation_result['violatedSlot'],
validation_result['message'])
Etc... Then, the validation function:
def validate_greeting(object_of_greeting, type_of_greeting):
objects = ['world', 'kitty', 'my dear']
# these should match the listed examples of their corresponding slot, which is 'HelloObject' here
if object_of_greeting is not None and object_of_greeting.lower() not in objects:
return build_validation_result(False,
'HelloObject',
f'You cannot greet {object_of_greeting},
would you like to greet someone else?')
greetings = ['hello', "gday", "whassup"]
if type_of_greeting is not None and type_of_greeting.lower() not in greetings:
return build_validation_result(False,
'GreetingType',
f'You cannot say {type_of_greeting} to anyone.
How about gday or whassup?')
return build_validation_result(True, None, None)
I am able to return a correct JSON with a test script for the build_validation_result function, like the following:
{
'isValid': False,
'violatedSlot': 'HelloObject',
'message': {
'contentType': 'PlainText',
'content': 'You cannot greet foobar, would you like to greet someone else?
}
}
I have also written a Lambda test for false inputs, which gives the following response:
{
"sessionAttributes": {},
"dialogAction": {
"type": "ElicitSlot",
"intentName": "HelloWorld",
"slots": {
"HelloObject": null,
"GreetingType": "howdy"
},
"slotToElicit": "HelloObject",
"message": {
"contentType": "PlainText",
"content": "You cannot greet foobar, would you like to greet someone else?"
}
}
}
Somehow, the validation result isn't making its way back to Lex, and so it's only returning the console prompt, 'Who would you like to greet?' over and over before giving up after n-attempts.
My testing suggests that the correct JSONs are being output from the Lambda function, so I don't know why I can never get their prompt messages to appear in the chat bot. Any help much appreciated. Will post more details if required.
Related
I have researched this error for hours and have also tried many different edits and nothing seems to give me a different response in the chatbot. I keep getting "Intent jokeintent is fulfilled" in the chatbot instead of the joke I am wanting. Seems like this has to do with the response the chabot is getting from the lambda function. I can't seem to get it right. Can anyone help?
-testing the lambda function gives me the correct joke
-Lex 2 is being used
-I do have the fulfillment lambdad ode hook selected
-I do have the settings on the chatbot selecting the lambda function
Lambda Function:
import json
import boto3
import os
def dad_joke():
return "How do you find Will Smith in a snow storm? Find the fresh prints"
def random_joke():
return "Why was Cinderella so bad at soccer? she kept rnning away from the ball"
def animal_joke():
return "What do rabbits eat for breakfast? IHOP"
def poop_joke():
return "What did one piece of toilet paper say to the other? I'm feeling really wiped"
def lambda_handler(event, context):
# telling Lex what to do with response
response = {
"sessionState": {
"dialogAction": {
"type": "Close"
},
"intent": {
"name" : "jokesIntent",
"state": "Fulfilled"
}
},
"sessionId": "test_session",
"messages": {
"contentType": "PlainText"
}
}
print(event)
if event["sessionState"]["intent"]["slots"]["joketype"] == "random joke":
response["messages"]["content"] = random_joke()
else:
if event["sessionState"]["intent"]["slots"]["joketype"] == "animal joke":
response["messages"]["content"] = animal_joke()
else:
if event["sessionState"]["intent"]["slots"]["joketype"] == "dad joke":
response["messages"]["content"] = dad_joke()
else:
if event["sessionState"]["intent"]["slots"]["joketype"] == "poop joke":
response["messages"]["content"] = poop_joke()
return response
{
action: "lambda:InvokeFunction",
principal: new iam.AnyPrincipal(),
}
Add this permission to your lambda
If it works, you can limit your principal later on
So basically, I'm trying to make this command where the user enters in an argument in this case the name of a state. Once the user has entered an argument, the client looks inside the voter.json file and finds each dict with the state that is the same as the argument. Once finished, it put the results on a discord embed then sends it to the user. Problem is that, the code works but, it will send a separate embed with each dict that the client found. I want it to send the dicts all in the same embed.
main.py
#client.command(name='search')
async def search(context, *, user):
with open('votes.json','r') as f:
data = json.load(f)
for officials in data['officials']:
o_name = officials["name"]
o_state = officials["state"]
if o_state == user:
myEmbed = discord.Embed(color=0xd4af37)
myEmbed.add_field(name=o_state, value="".join(o_name))
await context.send(embed=myEmbed)
votes.json
{
"officials" :[
{
"name": "RestiveSole267",
"state": "Anchorage"
},
{
"name":"Avia_JP",
"state": "Anchorage"
},
{
"name":"BillBobj",
"state":"Anchorage"
}
]
}
output:
1st Embed:
RestiveSole267
2nd Embed:
Avia_JP
3rd Embed:
BillBobj
This is an example of how to accomplish your goal.
x = {
"officials" :[
{
'BuddyBob':10,
'Guy':True
},
{
'BuddyGirl':13,
'Guy':False
},
{
'BuddyBobo':15,
'Guy':True
}
]
}
for official in x['officials']:
if official["Guy"]==True:
print(official)
output:
{'BuddyBob': 10, 'Guy': True}
{'BuddyBobo': 15, 'Guy': True}
I use AWS Step Functions and have the following workflow
initStep - It's a lambda function handler, that gets some data and sends it to SQS for external service.
activity = os.getenv('ACTIVITY')
queue_name = os.getenv('QUEUE_NAME')
def lambda_handler(event, context):
event['my_activity'] = activity
data = json.dumps(event)
# Retrieving a queue by its name
sqs = boto3.resource('sqs')
queue = sqs.get_queue_by_name(QueueName=queue_name)
queue.send_message(MessageBody=data, MessageGroupId='messageGroup1' + str(datetime.time(datetime.now())))
return event
validationWaiting - It's an activity that waits for an answer from the external service that include the data.
complete - It's a lambda function handler, that uses the data from the initStep.
def lambda_handler(event, context):
email = event['email'] if 'email' in event else None
data = event['data'] if 'data' in event else None
client = boto3.client(service_name='ses')
to = email.split(', ')
message_conrainer = {'Subject': {'Data': 'Email from step functions'},
'Body': {'Html': {
'Charset': "UTF-8",
'Data': """<html><body>
<p>""" + data """</p>
</body> </html> """
}}}
destination = {'ToAddresses': to,
'CcAddresses': [],
'BccAddresses': []}
return client.send_email(Source=from_addresses,
Destination=destination,
Message=message_container)
It does work, but the problem is that I'm sending full data from the initStep to external service, just to pass it later to complete. Potentially more steps can be added.
I believe it would be better to share it as some sort of global data (of current step function), that way I could add or remove steps and data would still be available for all.
You can make use of InputPath and ResultPath. In initStep you would only send necessary data to external service (probably along with some unique identifier of Execution). In the ValidaitonWaiting step you can set following properties (in State Machine definition):
InputPath: What data will be provided to GetActivityTask. Probably you want to set it to something like $.execution_unique_id where execution_unique_id is field in your data that external service uses to identify Execution (to match it with specific request during initStep).
ResultPath: Where output of ValidationWaiting Activity will be saved in data. You can set it to $.validation_output and json result from external service will be present there.
This way you can send to external service only data that is actually needed by it and you won't lose access to any data that was previously (before ValidationWaiting step) in the input.
For example, you could have following definition of the State Machine:
{
"StartAt": "initStep",
"States": {
"initStep": {
"Type": "Pass",
"Result": {
"executionId": "some:special:id",
"data": {},
"someOtherData": {"value": "key"}
},
"Next": "ValidationWaiting"
},
"ValidationWaiting": {
"Type": "Pass",
"InputPath": "$.executionId",
"ResultPath": "$.validationOutput",
"Result": {
"validationMessages": ["a", "b"]
},
"Next": "Complete"
},
"Complete": {
"Type": "Pass",
"End": true
}
}
}
I've used Pass states for initStep and ValidationWaiting to simplify the example (I haven't run it, but it should work). Result field is specific to Pass task and it is equivalent to the result of your Lambda functions or Activity.
In this scenario Complete step would get following input:
{
"executionId": "some:special:id",
"data": {},
"someOtherData": {"value": key"},
"validationOutput": {
"validationMessages": ["a", "b"]
}
}
So the result of ValidationWaiting step has been saved into validationOutput field.
Based on the answer of Marcin Sucharski I've came up with my own solution.
I needed to use Type: Task since initStep is a lambda, which sends SQS.
I didn't needed InputPath in ValidationWaiting, but only ResultPath, which store the data received in activity.
I work with Serverless framework, here is my final solution:
StartAt: initStep
States:
initStep:
Type: Task
Resource: arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:init-step
Next: ValidationWaiting
ValidationWaiting:
Type: Task
ResultPath: $.validationOutput
Resource: arn:aws:states:#{AWS::Region}:#{AWS::AccountId}:activity:validationActivity
Next: Complete
Catch:
- ErrorEquals:
- States.ALL
ResultPath: $.validationOutput
Next: Complete
Complete:
Type: Task
Resource: arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:complete-step
End: true
Here a short and simple solution with InputPath and ResultPath. My Lambda Check_Ubuntu_Updates return a list of instance ready to be updated. This list of instances is received by the step Notify_Results, then it use this data. Remember that if you have several ResultPath in your Step Function and you need more than 1 input in a step you can use InputPath only with $.
{
"Comment": "A state machine that check some updates systems available.",
"StartAt": "Check_Ubuntu_Updates",
"States": {
"Check_Ubuntu_Updates": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:#############:function:Check_Ubuntu_Updates",
"ResultPath": "$.instances",
"Next": "Notify_Results"
},
"Notify_Results": {
"Type": "Task",
"InputPath": "$.instances",
"Resource": "arn:aws:lambda:us-east-1:#############:function:Notify_Results",
"End": true
}
}
}
I try to do a payment with api yandex money.
I use
instance_id = ExternalPayment.get_instance_id(client_id)['instance_id']
api = ExternalPayment(instance_id)
def wallet_payments(access_token, ym_account, total, api):
wallet = Wallet(access_token)
request_options = {
"pattern_id": "p2p",
"to": ym_account,
"amount_due": total,
"comment": "test payment comment from yandex-money-python",
"message": "test payment message from yandex-money-python",
"label": "testPayment",
"test_payment": True,
"test_result": "success"
}
request_result = api.request(request_options)
process_payment = api.process({
"request_id": request_result['request_id'],
})
return process_payment['status']
request_result['status'] returns success, but after
`process_payment = api.process({
"request_id": request_result['request_id'],
})`
I get {'status': 'refused', 'error': 'illegal_param_ext_auth_success_uri'}.
How can I solve that?
From the yandex documentation:
illegal_param_ext_auth_success_uri:
The ext_auth_success_uri parameter has a missing or invalid value.
So you probably need to define a ext_auth_success_uri parameter which will be a listener url that receive yandex api response in case of success.
And you probably will need this one too which is the same but in case of error:
illegal_param_ext_auth_fail_uri:
The ext_auth_fail_uri parameter has a missing or invalid value.
source: https://tech.yandex.com/money/doc/dg/reference/process-payment-docpage/
I have 3 slots (account, dollar_value, recipient_first) within my intent schema for an Alexa skill and I want to save whatever slots are provided by the speaker in the session Attributes.
I am using the following methods to set session attributes:
def create_dollar_value_attribute(dollar_value):
return {"dollar_value": dollar_value}
def create_account_attribute(account):
return {"account": account}
def create_recipient_first_attribute(recipient_first):
return {"recipient_first": recipient_first}
However, as you may guess, if I want to save more than one slot as data in sessionAttributes, the sessionAttributes is overwritten as in the following case:
session_attributes = {}
if session.get('attributes', {}) and "recipient_first" not in session.get('attributes', {}):
recipient_first = intent['slots']['recipient_first']['value']
session_attributes = create_recipient_first_attribute(recipient_first)
if session.get('attributes', {}) and "dollar_value" not in session.get('attributes', {}):
dollar_value = intent['slots']['dollar_value']['value']
session_attributes = create_dollar_value_attribute(dollar_value)
The JSON response from my lambda function for a speech input in which two slots (dollar_value and recipient_first) were provided is as follows (my guess is that the create_dollar_value_attribute method in the second if statement is overwriting the first):
{
"version": "1.0",
"response": {
"outputSpeech": {
"type": "PlainText",
"text": "Some text output"
},
"card": {
"content": "SessionSpeechlet - Some text output",
"title": "SessionSpeechlet - Send Money",
"type": "Simple"
},
"reprompt": {
"outputSpeech": {
"type": "PlainText"
}
},
"shouldEndSession": false
},
"sessionAttributes": {
"dollar_value": "30"
}
}
The correct response for sessionAttributes should be:
"sessionAttributes": {
"dollar_value": "30",
"recipient_first": "Some Name"
},
How do I create this response? Is there a better way to add values to sessionAttributes in the JSON response?
The easiest way to add sessionAttributes with Python in my opinion seems to be by using a dictionary. For example, if you want to store some of the slots for future in the session attributes:
session['attributes']['slotKey'] = intent['slots']['slotKey']['value']
Next, you can just pass it on to the build response method:
buildResponse(session['attributes'], buildSpeechletResponse(title, output, reprompt, should_end_session))
The implementation in this case:
def buildSpeechletResponse(title, output, reprompt_text, should_end_session):
return {
'outputSpeech': {
'type': 'PlainText',
'text': output
},
'card': {
'type': 'Simple',
'title': "SessionSpeechlet - " + title,
'content': "SessionSpeechlet - " + output
},
'reprompt': {
'outputSpeech': {
'type': 'PlainText',
'text': reprompt_text
}
},
'shouldEndSession': should_end_session
}
def buildResponse(session_attributes, speechlet_response):
return {
'version': '1.0',
'sessionAttributes': session_attributes,
'response': speechlet_response
}
This creates the sessionAttributes in the recommended way in the Lambda response JSON.
Also just adding a new sessionAttribute doesn't overwrite the last one if it doesn't exist. It will just create a new key-value pair.
Do note, that this may work well in the service simulator but may return a key attribute error when testing on an actual Amazon Echo. According to this post,
On Service Simulator, sessions starts with Session:{ ... Attributes:{}, ... }
When sessions start on the Echo, Session does not have an Attributes key at all.
The way I worked around this was to just manually create it in the lambda handler whenever a new session is created:
if event['session']['new']:
event['session']['attributes'] = {}
onSessionStarted( {'requestId': event['request']['requestId'] }, event['session'])
if event['request']['type'] == 'IntentRequest':
return onIntent(event['request'], event['session'])
First, you have to define the session_attributes.
session_attributes = {}
Then instead of using
session_attributes = create_recipient_first_attribute(recipient_first)
You should use
session_attributes.update(create_recipient_first_attribute(recipient_first)).
The problem you are facing is because you are reassigning the session_attributes. Instead of this, you should just update the session_attributes.
So your final code will become:
session_attributes = {}
if session.get('attributes', {}) and "recipient_first" not in session.get('attributes', {}):
recipient_first = intent['slots']['recipient_first']['value']
session_attributes.update(create_recipient_first_attribute(recipient_first))
if session.get('attributes', {}) and "dollar_value" not in session.get('attributes', {}):
dollar_value = intent['slots']['dollar_value']['value']
session_attributes.update(create_dollar_value_attribute(dollar_value))
The ASK SDK for Python provides an attribute manager, to manage request/session/persistence level attributes in the skill. You can look at the color picker sample, to see how to use these attributes in skill development.
Take a look at the below:
account = intent['slots']['account']['value']
dollar_value = intent['slots']['dollar_value']['value']
recipient_first = intent['slots']['recipient_first']['value']
# put your data in a dictionary
attributes = {
'account':account,
'dollar_value':dollar_value,
'recipient_first':recipient_first
}
Put the attributes dictionary in 'sessionAttributes' in your response. You should get it back in 'sessionAttributes' once Alexa replies to you.
Hope this helps.
The following code snippet will also prevent overwriting the session attributes:
session_attributes = session.get('attributes', {})
if "recipient_first" not in session_attributes:
session_attributes['recipient_first'] = intent['slots']['recipient_first']['value']
if "dollar_value" not in session_attributes:
session_attributes['dollar_value'] = = intent['slots']['dollar_value']['value']