How to develop a chatbot with Amazon Lex

Development
How to develop a chatbot with Amazon Lex

The popularity of chatbots these days is so important and the advantages their offer are so vast, that lots of companies have started using them in the last few years, leaving behind the need of a full-service call center (in some cases leaving behind the whole call center).

The capabilities they offer are endless, but to name some popular uses, you can: get support, order a pizza, book a flight, query about your schedule, and many more.

Among the advantages the chatbots offer, here are the main ones:

  • Automated responses: They can be programmed to give an automated response to the user based on the user input (voice or text) and the data stored in the system.
  • Reduction of call center costs: Some questions and their responses can be fully automated, so there is no need for a person answering those questions.
  • Queries and transactions: Chatbots offer not only the possibility of asking questions but also making transactions (e.g., placing an order, booking a flight, setting up a calendar event, etc.).
  • Direct interaction with APIs: The bot’s logic can be programmed to interact directly with our back-end API or a third-party API or service. This gives them much power to do great things.
  • Text and voice: Interaction with the chatbot is not limited only to text. They can also be developed to accept voice questions. This allows us to create beautiful virtual assistants.

One of the platforms that allow us to develop chatbots is Amazon Lex. Amazon Lex uses the same engine as Alexa, and it provides a very straightforward way to develop a chatbot in a few easy steps, having only to develop the logic that interacts with our back-end or third-party APIs. This means that you won’t have to deal with text or speech recognition, but Amazon Lex will do it for you. This reduces a lot of development time and costs. Amazon Lex also offers an SDK to deploy the bot in a mobile app (iOS or Android).

The costs associated with this service vary depending on the number of requests made to the bot. There is a free tier that allows a maximum of 10,000 text requests and 5,000 voice requests during the first year.

Chatbot structure in AWS Lex

Chatbots in Lex follow a structure that allows us to create them in very few easy steps. The structure is as follows:

Amazon Lex bot structure

Here’s a brief explanation of a chatbot’s components:

Intents: These are the main components of a bot. They manage the different tasks that can be done with it (e.g., book a flight) and can be configured separately. Each intent is independent of the others.

Utterances: Here, you can set the text expressions that are going to trigger the intents. As Amazon Lex uses a deep learning engine to recognize these expressions, the utterances don’t have to be the exact text you expect the user types/says. An intent can have multiple utterances, some of them can be used to increase the possibility for the intent to be triggered, by setting several similar expressions. On the other hand, utterances can also contain slots, which are variable values given by the user when asking the chatbot (for example: if you expect the user to type questions similar to “when is Mike’s birthday?”, “Mike” will be a variable value, so you can configure it as a slot in the utterance, by indicating it with brackets: “when is {name} birthday?”). We’ll talk more about this further in this article.

Confirmation prompt: (optional) A question was asked by the bot to confirm the user’s intention. If the user confirms the intention, then the intent is fulfilled. Otherwise, it is going to show the user a custom message.

Fulfillment: Here’s where the magic happens. After Amazon Lex processes user input, the data gathered will be passed to your AWS Lambda function, where you can develop the business logic to fulfill the user’s request.

Response: After the request is fulfilled, the user can receive a response about the status of their request (either fulfilled or failed). This response can be configured in the Amazon Lex console if you want a fixed response depending on the status or develop a more customized real-time response in the AWS Lambda function that handles the fulfillment.

Session attributes: These are attributes that are set by the application that holds the chatbot, to establish some context for it. For example: if the user that uses the chatbot logs into the system, we may want to tell the chatbot the ID attribute of the current user. Session attributes last until the session expires or until the attribute is erased.

Request attributes: Unlike session attributes, request attributes last only until the current request is made.

Developing the Lambda function

To handle the requests to the chatbot, if we need to interact with our back-end or a third-party service, we need to develop an AWS Lambda function that processes the bot’s input and makes the requests to the API/service.

For those who haven’t worked with AWS Lambda yet: it is an AWS service that deploys small pieces of code in a serverless infrastructure, what allows us to program our code and to deploy it very fast since we don’t have to worry about setting up servers and all that hassle. This is ideal if you intend to have an architecture based on microservices, for instance. The languages supported by AWS Lambda are Node.js, Java, Go, C#, and Python, and the code can be deployed accompanied by any third-party or own libraries you may need.

The code to be included in the Lambda function can be a single file (index) or multiple files compressed in a .zip file. Libraries must be included in the .zip file for them to be available for the AWS Lambda function.

The entry point to the Lambda function has to have the following format:

exports.handler = (event, context, callback) => { //Do stuff here (call your API, connect with external services, etc.) response = … callback(null, response); };

As you can see, it is effortless. Let’s see some of the parameters the handler function receives:

  • Event: this object contains all the info related to the event, such as session attributes, request attributes, the name of the intent being fulfilled, the utterance that triggered the intent, and the values of the slots in the utterance details.
  • Context: this parameter contains some info about the Lambda function, not very useful for our purpose.
  • Callback: It’s a function that receives our response. The response is what the bot is going to receive and may contain a message for the user.

Responses

Responses are useful to tell the user the result of the intent, whether it is a succeed (fulfilled) response or a fail response, all responses must be a JSON object with the following structure:

{ "sessionAttributes": { }, "dialogAction": { "type": "Close", "fulfillmentState": "Fulfilled", "message": { "contentType": "PlainText", "content": "Your response message here" } } }

Depending on the type of the bot you are developing, you may want to adjust the response elements according to your needs. As this topic is extensive, we will explain only the most common types of responses and their components.

The most common response is the one that won’t expect any other interaction with the user, so the intent task will finish after the response is sent. This kind of response will have the dialogAction.type attribute value set to “Close” (see below).

sessionAttributes object

In case you need to set new values for session attributes, you will have to send them back to the user within the response. That can be done through the sessionAttributes element, which will contain a set of key-value pairs.

dialogAction object

This object contains details about the response, the type of the response and indicates the bot the next steps to be performed (if any). It can contain (for our purpose of a single call and single response intent) the following attributes:

  • Type: Tells the bot the type of response and the actions to be performed by the bot (if any). The possible values are “Close”, “ConfirmIntent”, “Delegate”, “ElicitIntent”, “ElicitSlot”. We will cover only the “Close” response in this post, leaving the others for a future one.
  • FulfillmentState: Tells the bot whether the request has been fulfilled (“Fulfilled”) or that the bot has failed to fulfill the request (“Failed”).
  • Message: Contains the info of the message to be shown/played to the user. In order to give the user a response, the following attributes must be set:
  • contentType: The type of the message to be sent to the user. Valid values are “PlainText”, “SSML” (text converted to voice) and “CustomPayLoad” (custom format).
  • content: The text to be shown to the user, to be converted to voice, or to be processed by the custom format defined.

Creating a chatbot with Amazon Lex

Now that we know the basics of Amazon Lex and how to create an AWS Lambda function that handles the chatbot requests, let’s create a sample chatbot.

We will create a chatbot to ask for the birth date of anyone at the office, so, if I ask, “When is Mike’s birthday?” the bot is going to answer “January 3rd”, for example. We will name our bot, “OfficeBot.” Let’s see how to do the following.


2. Create and set up a bot

Open the AWS main console and find the Amazon Lex console

Click on “Create” and select the “Custom bot” option. In the “name” field type “OfficeBot”, complete all the other fields at will. Here’s an example of the values you can put:

Bot creation screen

Now we have to create an intent to handle the requests of the user. We will name our intent, “Birthday.” To do this, click on the button, “Create Intent.”

"Create an intent" button

For our intent to be triggered, we need to set up some utterances. Our utterance will have to have a slot, so the name of the person whose birth date we want to ask is variable. Here are some examples of utterances we can use to achieve this:

“When is {name} birthday?”

“Can you tell me when {name} birthday is?

“Would you be so kind as to tell me {name} birthday?

Having this in mind, we proceed to create the utterances. We also have to define a name slot so that Amazon Lex can recognize it as such. The name of the slot will be “name”. And for the type of the slot, we will choose AMAZON.Person, and we will define the slot as “required”. Since the slot is required, the prompt field will be useless in this case, but as Amazon Lex requires us to fill in something, let’s type “whose birthday?”.

  1. Create the AWS Lambda function

Now we have to create a Lambda function that handles the requests from the bot. We will develop it in Node.js.

To simplify the work, we will use some sample data to simulate access to a database, back-end, or external service. The sample data will be a set of key-value pairs, in which the keys will be office member names, and the values will be the birth date. One approach to our solution could be this:

First, we develop our Lambda function entry point:

'use strict' //Handler function. This is the entry point to our Lambda function exports.handler = (event, context, callback) => { //We obtain the sessionAttributes and the intent name from the event object, received as a parameter. var sessionAttributes = event.sessionAttributes; var intentName = event.currentIntent.name; //In order to use the same lambda function for several intents, we check against the intent name, which is unique. switch (intentName) { case "Birthday": //In case we triggered the Birthday intent, we'll execute the following code: //We obtain the 'name' slot var name = event.currentIntent.slots.name; //In case the slot value contains a 's at the end, we take it off. name = name.replace("'s", ""); //now we get the birth date of the person getBirthDate(name, function(error, birthDate) { var response = null; if (!error) { //By default we create a message that states that we didn't find the birthday for the given name. var message = "I'm sorry, I couldn't find " + name + "'s birthday."; if (birthDate !== null) //In case we found a birthday, we generate a message with the birthday info { message = name + "'s birthday is on " + birthDate.toLocaleDateString("en-US", { month: "long", day: "numeric", year: undefined, }); } //We generate a response that has a 'Fulfilled' value for the attribute 'dialogAction.fulfillmentState' and we pass our message string response = createFulfilledResponse(sessionAttributes, message); } else { //In case an error ocurred, we pass an error message in a response that has the 'dialogAction.fulfillmentState' attribute set to 'Failed' var message = "An error has occurred."; response = createFailedResponse(sessionAttributes, message); } //Finally, we trigger the callback to notify the bot callback(null, response); }); break; } };

Now that we got the name of the person whose birthday we want to know, we can create a function that gets their birthday from a data source.

//Function used to get the birth date of someone's by providing their name. //The content of this function can be replaced in order for the data to be gotten from an API, database or other service. function getBirthDate(name, callback) { //We will use sample data instead of accessing an API or service, for practical purposes. This code can be reprogrammed in order to change the behavior. var memberBirthDates = { "juan": new Date(1985, 02, 12), "mike": new Date(1992, 04, 23), "lisa": new Date(1994, 11, 01), "maria": new Date(1986, 10, 24) }; var birthDate = null; name = name.toLowerCase(); //As our keys in the set are in lower case, we convert our 'name' parameter to lower case if (name in memberBirthDates) { //If the name is in the set, we return the corresponding birth date. birthDate = memberBirthDates[name]; } callback(0, birthDate); //we return the value in the callback with error code 0. }

Finally, we create the responses following the format that Amazon Lex provides. Remember, we are using the “Close”-type response, and depending on the fulfillment, we will have a Fulfilled response or a Failed one.

//Function used to generate a response object, with its attribute dialogAction.fulfillmentState set to 'Fulfilled'. It also receives the message string to be shown to the user. function createFulfilledResponse(sessionAttributes, message) { let response = { sessionAttributes: sessionAttributes, dialogAction: { type: "Close", fulfillmentState: "Fulfilled", message: { contentType: "PlainText", content: message } } } return response; } //Function used to generate a response object, with its attribute dialogAction.fulfillmentState set to 'Failed'. It also receives the message string to be shown to the user. function createFailedResponse(sessionAttributes, message) { let response = { sessionAttributes: sessionAttributes, dialogAction: { type: "Close", fulfillmentState: "Failed", message: { contentType: "PlainText", content: message } } } return response; }

Once we have completed our Lambda function, we have to save it into Lambda. There are three ways to do this:

  • Copy/paste the code into the Lambda editor. This can only be done if you have just one file.
  • Compress the project folder contents (not the project folder) into a .zip file and upload it to Lambda.
  • Compress the project folder contents into a .zip file and upload it to AWS S3, then an S3 link to the file.

As our lambda function is simple and we can develop everything in just one file, we will copy and paste it into the editor. However, we would highly recommend that you split the code into a file structure to make the code more legible and maintainable. Besides, as you will probably need to include libraries if your Lambda function increases its complexity, copying and pasting code won’t be enough, since you will need to compress the files into a .zip file.

3. Configure the bot fulfillment method

We now want our bot to use our recently developed Lambda function to fulfill the user requests for the Birthday intent, so what we only need to do now is to tell our intent to fulfill its requests by calling our Lambda function. This can be done in the intent’s console:

If we did everything OK, our console should look like this:

4.  Build and test the bot

We are almost done! Now we have to build our bot, for it to be available to receive queries. To do that, we have to press the “Build” button. In case there is an error building the bot, we have to fix it and build the bot again.

Now it’s time to test our bot.

That can be done by using the bot console, located at the very right of the screen of the bot (you may read “Test Chatbot” at the top right corner of the screen). Having in mind the utterances you created for the intent, you can use them with different values for the slot name, and see what happens. Here are some examples:

How about that? Nice, huh? Try typing your text and see what happens.

Tips when creating a bot

  1. Use only one Lambda function. If you modularize the code wisely and keep your code maintainable, you won’t need more than one Lambda function to handle your bot’s requests. This has two significant advantages: you can have only one project folder for the entire bot, having libraries and shared code only in one place. On the other hand, Lambda requests can be costly after certain numbers and, if you need to make calls to other Lambda functions, that may increase your budget.
  2. Create a init/test intent to test more complex requests. You may have already noticed that, for example, session attributes cannot be set from the bot console (at least not yet), so a friendly and easy way to set these attributes to test your bot is to have a new intent that when called sets all the session attributes. This will be done in the Lambda function, of course. Remember to disable this intent when moving the bot to production.
  3. Test from the AWS Lambda console first. You can also simulate bot requests from the Lambda console. The main advantage of this is that it allows you to test the Lambda function from the console, where you can visualize the errors, logs, and other info you may want to log into the console.
  4. Create a script to compress and upload your project. As you may upload your project multiple times, it is an excellent idea to create a script that does all the job for you, so having one that compresses your project folder contents and uploads the .zip file to AWS Lambda will save you a lot of time.

Wrapping up

Creating chatbots has become extremely easy because of the technologies that provide machine learning-based engines to process text or voice, leaving us only the task of developing the connection of the bot with our API, database, or another external service. Amazon Lex is an excellent option to achieve this, and developing a chatbot by using this technology is very straightforward.

We are a a Clutch Champion for 2023!
Development
We are a a Clutch Champion for 2023!
Kreitech has been recognized as a 2023 Clutch Champion by Clutch, the leading global marketplace of B2B service provider
Read More
Navigating a Post-2020 World: A Time of Business Challenges and Opportunities
Development
Navigating a Post-2020 World: A Time of Business Challenges and Opportunities
2020 will be remembered as an unprecedented, challenging, and unique year, a significant opportunity for the global
Read More
How Hiring an External Team can Accelerate Development and save costs?
Development
How Hiring an External Team can Accelerate Development and save costs?
In today's fast-paced software industry, companies often face the challenge of meeting deadlines and achieving goals
Read More
Have a project or an idea?
Let's make it real!
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.