Amazon Lex V2 Lambda not progressing to next slot - node.js

I have a LEX V2 bot configured, which has two slots and a confirmation slot. The two slots ask for dateFrom and dateTo. Now I have a Lambda that I use for fulfilment but also needs to fire on every single event. After the initial utterance, the Lambda takes the event.proposedNextState.dialogAction.slotToElicit and returns that inside dialogAction.slotToElicit along with dialogAction.type set to "Delegate". Now that works fine, however after providing the dateFrom value, even though the lambda responds with
{
sessionState: { dialogAction: { type: 'Delegate', slotToElicit: 'dateTo' } }
}
the next slot is not dateTo, but rather dateFrom again. Anyone has any ideas why it might not work like that? I don't understand why it is like that, maybe I am supposed to send a different type on dialog action? Below is the full Lambda function which handles events that are of type DialogCodeHook:
const response: LexResponse = {
sessionState: {
dialogAction: {
type: LexDialogActionType.Delegate,
slotToElicit: event.proposedNextState.dialogAction.slotToElicit,
},
},
};
console.log("EVENT", event);
console.log("RESPONSE", response);
callback(null, response);

Intent is mandatory in the Lambda response if dialogAction.type=Delegate.
However, AWS documentation about response format is misleading. It says "intent – The name of the intent that Amazon Lex V2 should use". Actually, it's a structure. Set it to event.sessionState.intent.
Although not mandatory, consider forwarding sessionAttributes too.

Related

Using the Twilio API, how can I check if a number is in use by a service?

I am trying to create a new messaging service using the Node.js twilio sdk. To do so, I have devised the following workflow.
I've created a new service like so.
client.messaging.v1.services.create({
friendlyName: 'test service,
inboundRequestUrl: 'https://someUrl.com',
inboundMethod: 'POST',
usecase: 'discussion'
})
I list all the numbers I own like so:
client.incomingPhoneNumbers.list()
I assign a number to my service like so (where the serviceSid is the sid of the service created in step 1 and the phoneNumberSid is the sid of one of phone numbers returned in step 2):
client.messaging.v1.services(<serviceSid>)
.phoneNumbers
.create({ phoneNumberSid: <phoneNumberSid> })
I am happy with this workflow, with the exception of one problem. You cannot assign the same number to two different messaging services, so I need to make sure the phone number whose sid I pass into step 3, doesn't already have a service. The problem is that the response I get back from step 2 doesn't tell me whether the numbers are used by another service.
All of this to say, can anyone suggest some way to modify this workflow to be more robust? Ideally, is there some way I can tell from step 2 whether or not a number is already being used by a service, so I know not to pass it in to step 3?
Thanks
Yes, there is a way to do this. To be honest, it's not very nice, but you can iterate over all messages services and test if your phone number (SID) belongs to a mapping of one of the services and then remove this mapping. Once removed, you can assign the phone number to any other messaging service.
async function unbindPhoneFromMessagingServices(phoneNumberSid) {
const allServices = await client.messaging.v1.services.list();
await Promise.all(
allServices.map(async (service) => {
const mapping = client.messaging.v1
.services(service.sid)
.phoneNumbers(phoneNumberSid);
try {
await mapping.fetch();
} catch (e) {
const RESOURCE_NOT_FOUND = e.code === 20404;
if (RESOURCE_NOT_FOUND) {
return;
}
throw e;
}
await mapping.remove();
console.log(
`The phone number was decoupled from messaging service ${service.sid}.`
);
})
);
}
PS: This snippet is taken from one of my repositories. Feel free to check out the complete code on GitHub.

Is it safe to store public keys/policies in a node.js constant in Lambda

I am writing a AWS lambda Authorizer in node.js. We are required to call Azure AD API to fetch the public keys/security policies to validate the incoming the Access Token.
However, to optimize the performance, I decided to store the public keys/security policies in node.js as a constant (this will be active until the Lambda is running or TTL of the keys expire).
Question : Is it safe from a security perspective ? I want to avoid "caching" it in DynamoDB as calls to DynamoDB would also incur additional milliseconds. Ours is a very high traffic application and we would like to save any millisecond possible for optimal performance. Also, any best practice is also higly appreciated
Typically, you should not hard-code things like that in your code. Even though it is not a security problem, it is making maintenance harder.
For example: when the key is "rotated" or the policy changed and you had it hard-coded in your Lambda, you would need to update your code and do another deployment. This is often causing issues, because the developer forgot about this etc. causing issues because your authorizer does not work anymore. If the Lambda loads the information from an external service like S3, SSM or directly Azure AD, you don't need another deployment. In theory, it should sort itself out depending on which service you use and how you manage your keys etc.
I think the best way is to load the key from an external service during the initialisation phase of the Lambda. That means when it is "booted" for the first time and then cache that value for the duration of the Lambdas lifetime (a few minutes to a few hours).
You could for example load the public keys and policies either directly from Azure, from S3 or SSM Parameter Store.
The following code uses the AWS SDK NodeJS v3, which is not bundled with the Lambda Runtime. You can use v2 of the SDK as well.
const { SSMClient, GetParameterCommand } = require("#aws-sdk/client-ssm");
// This only happens once, when the Lambda is started for the first time:
const init = async () => {
const config = {}
try {
// use whatever 'paramName' you defined, when you created the SSM parameter
const paramName = "/azure/publickey"
const command = new GetParameterCommand({Name: paramName});
const ssm = new SSMClient();
const data = await ssm.send(command);
config["publickey"] = data.Parameter.Value;
} catch (error) {
return Promise.reject(new Error("unable to read SSM parameter '"+ paramName + "'."));
}
return new Promise((resolve, reject) => {
resolve(config);
reject(new Error("unable to create configuration. Unknown error."));
});
};
const initPromise = init();
exports.handler = async (event) => {
const config = await initPromise;
console.log("My public key '%s'", config.key);
return "Hello World";
};
The most important point of this code is the init "function", which is only run on once, creating a "config" which should contain your AWS SDK clients and all the configuration you need in your code. This way, you don't have to get the policy for every request that the Lambda is processing etc.

Lost ability to capture unique Conversation_ID for each new session

Using Bot Builder 4.11.1 .Net and seemed to have lost the ability to capture any unique identifier for each new session. I need a unique identifier to keep state with the AI engine I am using to respond to input. Any suggestions? So, to expand, if I have a slack bot, for example, each time a user logs into Slack and starts a conversation with MyBot, I need a new unique identifier.
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var welcomeText = "Hello and welcome!";
Random rnd1 = new Random();
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
}
}
}
}
Unless I'm missing something, you should be able to get the information you need from TurnContext. In my code I'm running this in onMessage only (as in my case I only will message the user if they have sent the bot a message), but I don't see why you couldn't use this in onMembersAdded as well. I don't know how channels like Slack work, but in Microsoft Teams the user is just "added" when they first talk to the bot, and you don't end up in onMembersAdded unless they remove and read the bot in Teams. So if you may want to grab the conversation reference in the future, you may want to have it in onMessage or in both places. Also if you need the activity ID for some reason, as this will obviously update with each activity (though I haven't had any need for this information). Here is how you get the conversation reference. I am storing this in my conversation state and have assumed you are familiar with that but let me know if you need further help there. I also store in an Azure table to be accessed outside of the bot (e.g. I have an Azure Function that uses this to send proactive followups).
const { TurnContext } = require('botbuilder');
const conversationData = await this.dialogState.get(context, {});
conversationData.conversationReference = TurnContext.getConversationReference(context.activity);
await this.conversationState.saveChanges(context);
And that's it! Here is a sample conversation reference. Note that if you are storing this in Azure Tables or similar, you'll likely need to stringify it and re-parse when you pull it out.
{
"activityId":"ACTIVITY_ID",
"user": {
"id":"USER_ID",
"name":"USER_NAME",
"aadObjectId":"AAD_OBJECT_ID",
"role":"user"
},
"bot": {
"id":"BOT_ID",
"name":"BOT_NAME"
},
"conversation": {
"conversationType":"personal",
"tenantId":"YOUR_TENANT_ID",
"id":"YOUR_CONVERSATION_ID"
},
"channelId":"msteams",
"locale":"en-US",
"serviceUrl":"https://smba.trafficmanager.net/amer/"
}
What you are looking for (I think) is conversationReference.conversation.id. Different channels are going to have different attributes in the conversation reference, but this ID should always be there.

AWS SNS - Invalid Parameter - Phone Number is not valid to publish to. On specific phone number only

We are using AWS SNS to send SMS messages. In testing, it works for all but one of our devs who isn't receiving messages, the logs show the following:
Invalid parameter: PhoneNumber Reason: +1‬207XXXXXXX is not valid to publish to
I left his area code in case it's relevant. Again, this is the only number we've had issues with, it's an iPhone. It works fine for all the other numbers we've tried. I can also successfully SMS that number via the AWS SNS Console without issue.
I should note, we're only sending a 6 character string (for 2 factor auth).
We're doing this from a Lambda. Here's the relevant portion of the code:
export function sendSNS(PhoneNumber, Message) {
return new Promise<boolean>((resolve, reject) => {
const sns = new AWS.SNS({ region: 'us-east-1' })
const params = {
MessageStructure: 'String',
Message,
PhoneNumber
}
sns.setSMSAttributes({
attributes: {
DefaultSenderID: 'mycompany',
DefaultSMSType: 'Transactional'
}
})
sns.publish(params, function(err, data) {
if (err) {
console.log(err)
reject(false)
} else {
console.log(`Sent this SMS via Amazon: ${Message} to ${PhoneNumber}`)
console.log(data)
resolve(true)
}
})
})
}
I was able to fix this by updating the user's phone number directly in our MySQL DB by hand. Not sure if it was a character encoding issue or similar, but am assuming it must've been. I'll post back if I determine the exact cause.
UPDATE:
This was definitely caused by an encoding issue, paste the code below into jsfiddle, and mouseover to see the warning on the first plus sign which reads:
This character may get silently deleted by one or more browsers
var x = '+1‬207XXXXXXX'
var y = '+1207XXXXXXX'
You can also try deleting/backspacing the + or 1 in the offending string (var x). Some weird results.
This data was initially entered into the MySQL DB via a GraphQL mutation from Prisma Playground using Chrome on Mac.
If I convert both strings above to hex to inspect, you can see they are indeed different:
2b31202c32303758585858585858 (bad)
2b3132303758585858585858 (good)
Also be aware that not all AWS regions support sending SMS's and you'll see this same error "InvalidParameter: Invalid parameter: PhoneNumber Reason: +614##### is not valid to publish" when sending messages to a region that doesn't support it (in my case us-west-1).
For a list of regions that do support sending SMS's, see the sns amazon docs on supported regions.
Credit to user RichPeaua in this comment of the AWS forums.

Calling Another Alexa intent from within LaunchRequest

I am working on an Alexa skill where I would like to redirect user to one intent when he opens the skill via LaunchRequest.
User says Open xyz skill
LaunchRequest receives this and forwards the request to another intent.this.emit('AnotherIntent')
AnotherIntent has a SLOT which is required for its functioning.
I have setup the SLOT as required and AnotherIntent does work perfectly fine when invoked on its own.
However when I Launch the skill and tries to call AnotherIntent from within it as mentioned in Step 2 above, Alexa skill fails to launch.
One more thing to note is when I Test the same Alexa skill from within the skill builder for this scenario, the output json correctly shows me the SLOT being elicited. Not sure what is happening when I am trying to run it from Echo device.
I am using NodeJS and Lambda to deploy my Alexa handlers.
Any help would be appreciated.
Further reference based on the comment -
Handlers -
var handlers = {
'LaunchRequest': function () {
console.log("LaunchRequest::" + JSON.stringify(this.event.request));
this.emit('fooIntent');
},
'SessionEndedRequest': function () {
console.log('session ended!');
},
'fooIntent': function () {
const intentObj = this.event.request.intent;
if (!intentObj || (intentObj && !intentObj.slots.WHEN.value)) {
console.log('fooIntent::UserId' + this.event.session.user.userId);
const slotToElicit = 'WHEN';
const speechOutput = 'Ask a question here ?';
const repromptSpeech = speechOutput;
console.log("emitting elict now");
this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
} else {
// SLOT is filled, let's query the database to get the value for the slot.
console.log("fooIntent intent:something is requested for " + this.event.request.intent.slots.WHEN.value);
// All the slots are filled (And confirmed if you choose to confirm slot/intent)
fooIntentFunction(this,this.event.request.intent.slots.WHEN.value);
}
},
'AMAZON.HelpIntent': function () {
var speechOutput = "Need to come up with one.";
var reprompt = "What can I help you with?";
this.emit(':ask', speechOutput, reprompt);
},
'AMAZON.CancelIntent': function () {
this.emit(':tell', 'Goodbye!');
},
'AMAZON.StopIntent': function () {
this.emit(':tell', 'Goodbye!');
}
};
JSON Request captured in LaunchRequest
{
"type": "LaunchRequest",
"requestId": "amzn1.echo-api.request.972bb705-d181-495e-bf60-991f2bd8fbb1",
"timestamp": "2017-12-30T21:35:21Z",
"locale": "en-US"
}
JSON Request captured in the Intent when invoked from LaunchRequest. It shows that intent is not set then. I believe that is why it keeps failing when I try to emit elicit in the fooIntent implementation as shown above.
{
"type": "LaunchRequest",
"requestId": "amzn1.echo-api.request.972bb705-d181-495e-bf60-991f2bd8fbb1",
"timestamp": "2017-12-30T21:35:21Z",
"locale": "en-US"
}
Amit
As specified in Amazon's documentation, the dialog interface is meant to fill all required slots of a specific intent:
The questions and answers are intended to gather and confirm values for all of the intent’s required slots. The conversation continues until all slots needed for the intent are filled and confirmed.
In your case, the user's request is not an intent (i.e. of the IntentRequest type), but a LaunchRequest. As such,it has no interaction model and no slots to elicit, even though it is processed by an intent handler.
You should get your Skill to work as intended with a deep invocation, like
Alexa, tell xyz skill to make me a sandwich
(if the sandwich intent, or in your case the fooIntent, has a WHEN slot to elicit).
Hope this helps!
try this,
this.emitWithState("IntentName");
Hope this helps. all the best :)
Calling an intent from another intent can be simply done using the following snippet - \
this.emit("Intent name")
In the response of your first intent, give the name of the intent you wish to call.

Resources