Serverless technology is here to stay, and the benefits of a serverless architecture for your app or product are huge. In this tutorial, we will deploy a small chatbot with AWS Lex to help users set various kinds of budgets, and to check in on their budget. We will explore the combination of AWS services here and how they can be stitched together to create a powerful, scalable and secure application. To complete this tutorial, you will need: An AWS Account, basic knowledge of the AWS UI, and some basic understanding of node/express.
In your AWS console, find Amazon Lex and click on it. Click on the blue Get Started button and then on the next screen, click Custom Bot. Note there are other examples here if you’d like to explore them!
On the next screen you will see an opportunity to Create Intent. An Intent is a goal or action the user wants to achieve. Think of an intent as the specific reason a user would want to send a message to your bot. Lets create an intent and call it SetBudget. Once we’ve done that, we’re greeted with another screen with a whole bunch of input options. Lets focus on Slots first.
Slots are the variables of Lex. If someone was ordering a pizza, the Slots to include would be things like {SIZE}, {PIZZA_TYPE} and {DELIVERY_ADDRESS}. For our chatbot, we will have two slots. {AMOUNT} and {BUDGET_CATEGORY}. There is a lot to discuss regarding slots and different slot types, so I encourage you to see what amazon already provides before defining your own.
2. On the left side of the dashboard, you will see an option to create a slot type. Click on that, and fill out the values to create a new slot variable with values that we will set.
Note that you can add synonyms for values, and the expand values option. Expand Values lets Lex use machine learning to add similar words to your slot list, letting it grow beyond your list of associated words.
Set a few budgets that you would like to include in the Slot. I have included Family, Vacation, Savings and Car. Notice the Expand Values and Restrict to Slot Values and Synonyms checkboxes. Expand Values allows you to let Lex use its natural language processing and amazons machine learning to fill in your slot with words that it thinks should be included. For our purposes we wont need it, but it is an important feature that can come in handy as your application grows.
Set a budget of {AMOUNT} for {BUDGET_CATEGORY}Create budget {AMOUNT} for {BUDGET_CATEGORY}make a budget for {AMOUNT}
Add in any other variations of that utterance you can think of, but keep it relatively short to 2–3 different variations. Notice I didn’t include BUDGET_CATEGORY in the last utterance. If this variable is not included, we will create a budget under the “general” category.
3. Add a Confirmation Prompt if you wish. I did not include one, but depending on the nature of your intent this could be important.
4. Notice for Fulfillment, you have the option to either send the intent to a lambda function or return parameters to client. For now, lets click Return Parameters to Client and click save. We’re going to check on our bot before moving over to Amazon Lambda and creating the business logic of our chatbot. Scroll down and save your intent.
5. And now for a bit of fun! On the top right of the dashboard, click Build. This will initialize your chatbot and allow you to begin testing it. A chat widget should pop up on the right side of the screen. Try it out!
As you can see, Amount for 50 and Category of Family is successfully captured by the chatbot. Wow! Make sure to try with the required prompts as well to validate they are being requested.
Awesome, our Lex chatbot is almost ready to go. Our next step is to attach a Lambda function to it. For the rest of this tutorial, we will simply receive the slots and their values in lambda and return them in a way that Lex understands. Part II of this tutorial will cover the inclusion of a Serverless Amazon Aurora Database to store the data and an event alarm to trigger another lambda function if they set a budget thats very high. This second lambda function will add the user to a list of High Rollers we can presumably pester to sell things to. For our purposes though, it will demonstrate chaining of Lambda functions and one of many patterns in serverless.
In your AWS console, go to Amazon Lambda and select Build a Function from Scratch. In the next screen, you will have the option of building your custom function or selecting a blueprint. The blueprints are great examples and I highly recommend checking them out to enhance your knowledge.
Choose a name for your function, a runtime (we will be using Node.js 8.10) and select “New role” when specifying the role.
For our purposes, Select Author From Scratch and fill out the form with your function name, runtime environment (Node.js 8.10 for this tutorial), and Role. This is a good time to mention that Lambda functions are born into this world with no permissions. To allow your lambda function to interact with other AWS services, you will need to choose a role for that function. For our setBudget function, you will need a custom role that can read/write to RDS so we’re going to take a quick detour to create that role!
Follow the instructions for creating a role, and assign it to this function. When thats done, open a new tab and head over to services and look for IAM. in IAM, find the roles dashboard and you should see the new role you created. Attach a policy to this role which allows for full RDS access.
Lambda Functions take on an IAM Role during execution. This role can have specific policies attached outlining exactly which AWS services it can interact with and how. Don’t forget Roles and IAM users always start off with zero permissions.
Once your role is created and the policy is attached, check back on your Lambda function. You should now be able to select that role from the dropdown to create your function!
On this next Screen you will be treated to your lambda function, the designer tool, and some dashboard items for testing and monitoring your function. The first time you hit Test, you will be asked to supply a JSON snippet describing what the test input should be.
For our purposes, lets focus on the Test function in the top right corner which allows us to invoke our function. We can also edit the index.js document which contains the entirety of the function and modify it as we need. Note the function appears very similar to a standard express endpoint, returning a JSON response with a status code (200) and a stringified JSON body.
For now, all we will do is add the following:
console.log(event)
as the first line in our function. The event
variable includes the data we receive from lex, so we’re going to log it and see what it looks like!
So now our function is created, it has the required roles, it can return a response, we’re looking good! Lets check back on our Lex dashboard, and revisit our setBudget intent.
If we scroll down our setBudget Intent to fulfillment, we can now select AWS Lambda Function and choose our setBudget function. Make sure your version/Alias is set to latest or you may not see updates! (This is a terrible bug which I wish upon nobody). Build the bot, and lets test out our integration!
Type in an intent, and lets see what we get.
Oh no! Our Lambda has returned an invalid response!
Oh no! As you can see, an error has ocurred and we have not received a proper response from Lambda that Lex can interpret. We are going to rectify that! But first, lets also check in on cloudwatch to see just what our Intent, with its (hopefully) populated Slots, actually looks like in lambda.
2. Clicking on that log group, and then clicking on the latest log will show you the latest output from our lambda function. Cloudwatch captures everything and is a very powerful tool to ensure you are optimizing your lambda and serverless architecture appropriately. We will be doing more with Cloudwatch integrations in the next tutorial.
3. Notice that we have lines which read something like Start Request ID:
and End Request ID:
. These represent the beginning and ending of your Lambda invocation, and all logging per invocation is sandwiched between those lines.
4. Since we logged the event
value to console, we can now see it in cloudwatch. It should look like this:
{ messageVersion: '1.0',invocationSource: 'FulfillmentCodeHook',userId: 'bn0r1odqm2ce0ayze5n5xrlfo6lkeno1',sessionAttributes: {},requestAttributes: null,bot: { name: 'BudgetBud', alias: '$LATEST', version: '$LATEST' },outputDialogMode: 'Text',currentIntent:{ name: 'setBudget',slots: { AMOUNT: '40', BUDGET_CATEGORY: 'Family' },slotDetails: { AMOUNT: [Object], BUDGET_CATEGORY: [Object] },confirmationStatus: 'None' },inputTranscript: 'set budget of 40 for family' }
Aha! Thats our Lex Intent, along with some beautiful supplemental data including which intent version we’re on, userId (more on that later), botname and currentIntent. As you can see, current intent parses our slots and slot types so we have a defined amount key and value, as well as a budget_category key/value. Our data is in a friendly JSON format and we can now do more with it.
Lets finish off by returning a meaningful response to Lex, we’ll save all the database stuff for Part II.
According to the AWS docs, the response needs to be in the following format:
{"sessionAttributes": {"key1": "value1","key2": "value2"},"dialogAction": {"type": "Close","fulfillmentState": "_Fulfilled_
","message": {"contentType": "_PlainText_
","content": "_Your budget has been confirmed._
"},}}
Lets try pop this into our lambda function as the response object and see what happens. For reference, your lambda function should now look like this:
exports.handler = async (event) => {console.log(event);let lambda_response = {"sessionAttributes": {"amount": event.currentIntent.slots.AMOUNT,"current_budget": event.currentIntent.slots.BUDGET_CATEGORY},"dialogAction": {"type": "Close","fulfillmentState": "Fulfilled","message": {"contentType": "PlainText","content": "Your budget of " + event.currentIntent.slots.AMOUNT + " has been confirmed for category " + event.currentIntent.slots.BUDGET_CATEGORY},}}return lambda_response;};
As you can see, our response has sessionAttributes, which return context to lex, and a dialogAction. DialogAction can be used to elicit further prompts and responses from a user, so be sure to check out its many option types. For our purposes, we’re simply going to return a plainText message to the user letting them know their budget has been set for the correct amount. Once you’ve saved that version of the lambda function, check back on lex and run a query. If everything worked you should see the following:
Beautiful.
To review, we set up an Amazon Lex bot with an Intent (set budget) and Slots (amount, category). This intent was handed off to a Lambda function which could then parse the value of those slots in JSON, and return a response object to Lambda. IAM was used to create an execution role for the lambda function, and Cloudwatch was used for logging.
In Part II of this tutorial, we will add a serverless Amazon Aurora database to store and query this data, another intent+lambda function to see our budgets, as well as a couple more pieces of interesting architecture. Time permitting, I may include a Part III React Native tutorial to show how to directly integrate this into an app as well, so stay tuned!
If you are interested in building a chatbot or any other kind of application, please reach out to me at [email protected] or at www.apricotstudios.ca