Enforcing custom enumeration in AWS LEX for slot values - amazon-web-services

I want to be able to specify a custom list of valid options for a slot that LEX will either attempt to approximate towards or, in the event that no valid option can be approximated, reject the invalid response.
At first I attempted to do this through custom slot types. And though their examples may lead you to believe these are enumerations, they are not. A user still has the capacity to input whatever value they like.
Their documentation has this to say: https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/migrating-to-the-improved-built-in-and-custom-slot-types#literal
A custom slot type is not the equivalent of an enumeration. Values outside the list may still be returned if recognized by the spoken language understanding system. Although input to a custom slot type is weighted towards the values in the list, it is not constrained to just the items on the list. Your code still needs to include validation and error checking when using slot values.
I am aware that I can validate their submission through a lambda after they have completed their full submission, but by then it's too late. A user has submitted their full intent message. I'm unable to capture it midway and correct them.
Am I missing some way to input slot options or a configuration option for custom slot types? Is there any way to enforce a custom list of options for a slot? (Similar to utterances for intents, or the built in slot types, which will ask the same question again if there is no match.)
Thanks!

I'm unable to capture it midway and correct them.
You can capture the error in lambda without fulfilling the intent and starting over. Here's how I validate input with Python.
If you detect a validation error in lambda, you can elicit the same slot and pass your error message. This allows you to set complex validation rules and have your bot return specific responses to the user.
def validate(input):
if input not in ['foo', 'bar']:
return elicit_slot("Your response must be foo or bar")
def elicit_slot(error_message):
return {
'dialogAction': {
'type': 'ElicitSlot',
'intentName': current_intent,
'slots': current_slots,
'slotToElicit': slot_with_validation_error,
'message': {'contentType': 'PlainText', 'content': error_message }
}
}

Related

Dialogflow webhook set parameter value

My intent sends a webhook as part of slot filling if a parameter is missing from the user query. My webhook then uses logic to estimate the value of the parameter. How can I return this parameter as part of the WebhookResponse object? I am using the C# client library in an ASP.NET Core app.
My code is:
string fulfillmentText;
WebhookRequest request = null;
using (var reader = new StreamReader(Request.Body))
{
request = jsonParser.Parse<WebhookRequest>(reader);
}
//If Parameter-1 has no value
if (request.QueryResult.Fields["Parameter-1].StringValue.Length == 0)
{
fulfillmentText = "I have guessed the value of Parameter-1";
//I apply some logic that is unimportant to this question
//For the sake of simplicity, say I estimate the value of Parameter-1 to be "foobar"
//I want to be able to give the parameter this value like this:
parameter["Parameter-1"] = "foobar"
}
UPDATE
So, I have pretty much got it all working using Prisoner's method. I will retry sid8491's at some point too. My intent is trying to obtain a user's address. I have required custom entities to retrieve the street number, street name, suburb and state.
Without creating any contexts myself, the following context is automatically generated by Dialogflow: projects/telebot-pianomoves-v1/agent/sessions/2b42cbc8-2418-4231-e4c0-bd3a175f2ea8/contexts/1320fe35-4329-4176-b136-9221dfaddd4e_id_dialog_context. I receive this context in my webhook, and can then CHANGE the value of a parameter. Let's assume $Suburb_Entity had no value in the webhook request and my code then returned the above context with the a new value for Suburb_Entity. My code successfully changes the Suburb_Entity from "" to aspendale as can be seen by the webhook response json:
Now the odd thing is, although I changed the Suburb_Entity to an actual value in the outputContext of my webhook response, the actual parameter $Suburb_Entity only changes to the new value of Suburb_Entity from the outputContext on the NEXT detect intent request. So, keeping in mind the fact that I returned the new Suburb_Entity in the outputContext of the webhook response, this is the detect intent response I get - noting that $Suburb_Entity is yet to be changed:
On the next detect intent request, the webhook request parameter Suburb_Entity is set to aspendale and $Suburb_Entity also equals aspendale. The important thing about this, is $Suburb_Entity only changed to the outputContext parameter value on the NEXT detect intent request, of which would have triggered another webhook. $Suburb_Entity did not change during the same detect intent request as when I modified the outputContext parameter Suburb_Entity, but in the next. This leads me to believe that somehow, $Suburb_Entity inherits parameter values from this automatically generated context. The issue here, is that when my webhook responds with the outputContext paramter Suburb_Entity equalling aspendale, $Suburb_Entity does not change to this value until the next request. This means that if all the other parameters have values set, but $Suburb_Entity is yet to have changed value, then allRequiredParamsSet == false. If I return the Suburb_Entity in the outputContext, I want it to immediately change the value of $Suburb_Entity without requiring another detect intent request so that allRequiredParamsSet == true in such a circumstance. I tried setting the default value by doing this (it didn't work):
An alternative of course would be a way for me to force allRequiredParamsSet = true. I save the parameter values from this the context parameter, not the actual response. So I don't need $Suburb_Entity, I just want the intent to think that it has a value.
Cheers
When you use a webhook for "slot filling", the intention is that you return the prompts you want to ask the user for and continue to use the same Intent to handle the responses. You're not expected to create values yourself.
If you want to "fill in" some answers that are used in the static "response" section of the Dialogflow Intent, or if you just want to record the answers so you can use them later, you can set the parameters of a Context. In the response string, you can refer to this value as #context-name.parameter-name.
Update
I don't know the internal mechanics of slot filling, but it doesn't surprise me that setting a value in the internal context for the input parameters doesn't "register" until the next round of handling the Intent.
The webhook for slot filling isn't intended to create values - it is intended to handle values and create prompts for the user to respond to. Intents are generally about processing user inputs and webhooks about handling them.
My workaround suggested that if you want this for output, you use the context for output.
There are multiple steps for get it done:
First give an event in intent
Check your condition in webhook
If your condition is satisfied, invoke the intent by calling the event from webhook which you have defined in step 1
Pass the paylaod (in json format) along with event calling, give parameters in the payload
In the intent, give default value of parameter as #eventName.parameterName
Hope it helps.

How to give response based on user response in Amazon Lex?

How do I give response to a user in Amazon Lex based on the choice that the user have made?
For example: If user is married then system should ask how many children do you have, if user is not married the system should ask when are you getting married.
Is there any way to do this?
If it's purpose is simply responding to the user without any elicit slot or chaining intents then it can be done simply in Lambda function with an if condition.
def close(message):
return {
"dialogAction":{
"type":"Close",
"fulfillmentState":"Fulfilled",
"message":{
"contentType":"PlainText",
"content":message
}
}
}
if user['married'] == True:
return close('how many children do you have')
else:
return close('when are you getting married')
If you are planning to trigger some different intent then inside if-else write code for intent switching. You will need to pass the confirmIntent dialog action from the lambda with the intent you want to switch to.
Check this link for details about intent switching.

Can't access SQS message attributes using boto3

I'm trying to pass and then retrieve a message with attributes into AWS SQS.
Even though I can see attributes of a message through Management Console, I can't get them using boto3, always get None. Changing "AttributeNames" doesn't make a difference. Message body can be retrieved OK.
import boto3
sqs = boto3.resource('sqs', region_name = "us-west-2")
queue = sqs.get_queue_by_name(QueueName='test')
queue.send_message(MessageBody = "LastEvaluatedKey",
MessageAttributes ={
'class_number':{
"StringValue":"Value value ",
"DataType":"String"
}
}
)
messages = queue.receive_messages(
MaxNumberOfMessages=1,
AttributeNames=['All']
)
for msg in messages:
print(msg.message_attributes) # returns None
print(msg.body) # returns correct value
Attributes (system generated) and Message Attributes (user defined) are two different kinds of things provided by the back-end API.
You're looking for message attributes, but you're asking the code to fetch attributes, instead.
AttributeNames=['All']`
It seems like you need to be fetching message attributes...
MessageAttributeNames=['All']
...when calling queue.receive_messages(). Or, the specific message attribute names that you want, if known, could be used instead of 'All' (which -- had I designed this API -- would have been called '*', but I digress).
Admittedly, this is only an intuitive guess on my part based on familiarity with the underlying API, but it seems to be consistent with http://boto3.readthedocs.io/en/latest/guide/sqs.html.

Handle Invalid Data Type in CF Remote Function

So I have a remote ColdFusion Function like:
remote string function name (required numeric varname){
This is accessed via AJAX call. Google has taken it upon itself to pass in junk/blank values to the URL to this remote function. How can I gracefully handle those for Bots/Users to manage to get in a junk value. I've tried putting try/catch around/inside the function and doesn't work. I've also tried setting a default value but I still get an error. I'd like to be able to return an error message.
Thoughts?
Right now:
domain.com/path/to/page.cfc?method=function&varname=
Is throwing an error
domain.com/path/to/page.cfc?method=function&varname=5
Is working as expected.
Update:
I am leaving this here for posterity, as it explains the cause of the error and chain of events with validation. However, Adam's response is the correct solution IMO.
remote string function name (required numeric varname){
I've tried putting try/catch around/inside the function and doesn't work.
Because the argument value is validated before CF executes anything inside the function. So it never even gets to the try/catch.
If you want to allow non-numeric values, you must set the argument type to string and perform validation inside the function. ie
// use whatever check is appropriate here
if ( IsNumeric(arguments.varname) ) {
// good value. do something
}
else {
// bad value. do something else
}
I've also tried setting a default value but I still get an error
domain.com/path/to/page.cfc?method=function&varname=
Update
The reason it does not work is because the varname parameter does exists. Its value is an empty string. As long as some value is passed (even an empty string) the default is ignored.
I disagree that the accepted solution is the best approach here.
Firstly, if your method is expecting a numeric and it's being passed a string, then an error is precisely the correct reaction here. You shouldn't feel the need to mitigate for requests that pass invalid values. Consider it like someone making a request to http://some.domain/path/to/file/wrongOne.html (they should have requested http://some.domain/path/to/file/rightOne.html)... it's completely OK for things to return a 404 "error" there, isn't it? An error response is exactly right in that situation.
Similarly, you have dictated that for your remote call URL, that argument is supposed to be numeric. So if it's not numeric... that is an error condition. So your server returning a 500-type error is actually the correct thing to do.
This is an example of the "garbage in, garbage out" rule.
If you are looking for an elegant solution, I'd say you already have the most elegant solution. Don't mess around writing special code to deal with incorrectly made requests. That is not an elegant approach.
You are better off letting the thing error, because then the mechanism requesting the URL will stop doing it. Messing around so that you are returning a 200 OK for a request that wasn't "OK" is the wrong thing to do.
Errors - when they are the correct result - are fine. There's nothing wrong with them.

How does Sentry aggregate errors?

I am using Sentry (in a django project), and I'd like to know how I can get the errors to aggregate properly. I am logging certain user actions as errors, so there is no underlying system exception, and am using the culprit attribute to set a friendly error name. The message is templated, and contains a common message ("User 'x' was unable to perform action because 'y'"), but is never exactly the same (different users, different conditions).
Sentry clearly uses some set of attributes under the hood to determine whether to aggregate errors as the same exception, but despite having looked through the code, I can't work out how.
Can anyone short-cut my having to dig further into the code and tell me what properties I need to set in order to manage aggregation as I would like?
[UPDATE 1: event grouping]
This line appears in sentry.models.Group:
class Group(MessageBase):
"""
Aggregated message which summarizes a set of Events.
"""
...
class Meta:
unique_together = (('project', 'logger', 'culprit', 'checksum'),)
...
Which makes sense - project, logger and culprit I am setting at the moment - the problem is checksum. I will investigate further, however 'checksum' suggests that binary equivalence, which is never going to work - it must be possible to group instances of the same exception, with differenct attributes?
[UPDATE 2: event checksums]
The event checksum comes from the sentry.manager.get_checksum_from_event method:
def get_checksum_from_event(event):
for interface in event.interfaces.itervalues():
result = interface.get_hash()
if result:
hash = hashlib.md5()
for r in result:
hash.update(to_string(r))
return hash.hexdigest()
return hashlib.md5(to_string(event.message)).hexdigest()
Next stop - where do the event interfaces come from?
[UPDATE 3: event interfaces]
I have worked out that interfaces refer to the standard mechanism for describing data passed into sentry events, and that I am using the standard sentry.interfaces.Message and sentry.interfaces.User interfaces.
Both of these will contain different data depending on the exception instance - and so a checksum will never match. Is there any way that I can exclude these from the checksum calculation? (Or at least the User interface value, as that has to be different - the Message interface value I could standardise.)
[UPDATE 4: solution]
Here are the two get_hash functions for the Message and User interfaces respectively:
# sentry.interfaces.Message
def get_hash(self):
return [self.message]
# sentry.interfaces.User
def get_hash(self):
return []
Looking at these two, only the Message.get_hash interface will return a value that is picked up by the get_checksum_for_event method, and so this is the one that will be returned (hashed etc.) The net effect of this is that the the checksum is evaluated on the message alone - which in theory means that I can standardise the message and keep the user definition unique.
I've answered my own question here, but hopefully my investigation is of use to others having the same problem. (As an aside, I've also submitted a pull request against the Sentry documentation as part of this ;-))
(Note to anyone using / extending Sentry with custom interfaces - if you want to avoid your interface being use to group exceptions, return an empty list.)
See my final update in the question itself. Events are aggregated on a combination of 'project', 'logger', 'culprit' and 'checksum' properties. The first three of these are relatively easy to control - the fourth, 'checksum' is a function of the type of data sent as part of the event.
Sentry uses the concept of 'interfaces' to control the structure of data passed in, and each interface comes with an implementation of get_hash, which is used to return a hash value for the data passed in. Sentry comes with a number of standard interfaces ('Message', 'User', 'HTTP', 'Stacktrace', 'Query', 'Exception'), and these each have their own implemenation of get_hash. The default (inherited from the Interface base class) is a empty list, which would not affect the checksum.
In the absence of any valid interfaces, the event message itself is hashed and returned as the checksum, meaning that the message would need to be unique for the event to be grouped.
I've had a common problem with Exceptions. Currently our system is capturing only exceptions and I was confused why some of these where merged into a single error, others are not.
With your information above I extraced the "get_hash" methods and tried to find the differences "raising" my errors. What I found out is that the grouped errors all came from a self written Exception type that has an empty Exception.message value.
get_hash output:
[<class 'StorageException'>, StorageException()]
and the multiple errors came from an exception class that has a filled message value (jinja template engine)
[<class 'jinja2.exceptions.UndefinedError'>, UndefinedError('dict object has no attribute LISTza_*XYZ*',)]
Different exception messages trigger different reports, in my case the merge was caused due to the lack of the Exception.message value.
Implementation:
class StorageException(Exception):
def __init__(self, value):
Exception.__init__(self)
self.value = value