In this tutorial, I’ll walk through how to leverage AWS Amplify, AWS Lambda, & Amazon Lex to build a functioning chatbot!
To see the final code, click here.
As voice and messaging channels become more and more important, conversational interfaces as a platform are emerging to be another important target for developers who want to extend their existing skill set to create applications that are becoming more and more in demand. Amazon Lex is service for building conversational interfaces into any application using voice and text.
In this tutorial, we’ll use the AWS Mobile CLI to create a new cloud-enabled project and add an Amazon Lex bot to our project. We’ll then create a React application and use the AWS Amplify library to connect to and interact with the bot.
We’ll also connect the bot to an AWS Lambda function and show how we can work with the parameters passed to our Lambda function if we would like to then connect to any external services or APIs using our chatbot.
The bot that we will be creating will be a booking service to book hotels and car rentals!
AWS Amplify provides some preconfigure chat UI that we’ll start off with, but we will also be using React Chat UI written by Brandon Mowat for any custom chat UI that we end up building.
The first thing we need to do is create a new React application using Create React App. (If you don’t already have it installed, go ahead and install if before going to the next step).
We’ll create a new application called react-chatbot:
create-react-app react-chatbot
Next, we’ll change into the directory for the react-chatbot application:
cd react-chatbot
Now, we’ll need to create our new AWS project using the AWS Mobile CLI
To do so we’ll first need to install the CLI.
npm i -g awsmobile-cli
Next, we need to configure the CLI with our AWS IAM credentials:
awsmobile configure
If you’ve never worked with IAM credentials, this video will walk you through the entire process as well as configuring the CLI.
Now that the CLI is installed and configured, let’s create a new AWS project:
awsmobile init -y
Once the project is created, we’ll need to add Amazon Cognito functionality in order to interact with Lex. To do so we can run the following command:
awsmobile user-signin enable
Then, we can push the new configuration to AWS:
awsmobile push
Now, our project is created & we can move on to the next step:
To create a chatbot we’ll need to go ahead and open the project that we are working with in the AWS console. To do so, we can run the following command:
awsmobile console
Here, we should see our project in the AWS Console:
If we scroll down this window, we will see a feature called Conversational Bots. Click on this feature.
Next, we’ll choose the Book a trip sample bot, give the bot the name of BookTripBot and then click Create Bot:
Now, we should see the bot that was created in our console! To edit the bot, click on the blue edit button:
Now, we should see the Lex console:
On the left hand side you will see all of the Intents associated with the current chatbot.
In our case we have two intents: BookTripBotBookCar & BookTripBotBookHotel.
Each intent can be thought of as its own unique application and configuration. Each intent also has it’s own set of Sample Utterances & Slots (which we’ll look at next).
The two main areas of the Lex console to take note of as of now are the Sample Utterances & the Slots.
Sample utterances are basically triggers to instantiate the bot. The idea here is to give enough sample utterances to cover most situations for the intent of the bot.
Slots are the responses that you will be giving to the user once the bot is instantiated. The order of the slots is important, as it is the order that the responses will come to the user and the order in which the responses from the user will be gathered.
For now, we will leave everything as it is and go ahead and connect our React app to the chatbot!
Before connecting our React app to our new chatbot we need to pull down the new configuration that we updated in the console down to our local project. To do so we can run the following command:
awsmobile pull
When prompted with sync corresponding contents in backend/ with #current-backend-info/, choose yes.
Now, we’re ready to go to the next step.
To connect the React Application to the chatbot we need to do two things:
To configure the app with the AWS resources we have just created we’ll need to open index.js
and add the following code below the last import:
// other imports omitted
import config from './aws-exports' // new
import Amplify from 'aws-amplify' // new
Amplify.configure(config) // new
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
Next, in App.js
, we’ll be using the ChatBot component from aws-amplify-react
to render some preconfigured UI for now that will interact with the bot.
import { ChatBot } from 'aws-amplify-react';
handleComplete(err, confirmation) {
if (err) {
alert('Bot conversation failed')
return;
}
alert('Success: ' + JSON.stringify(confirmation, null, 2));
return 'Reservation booked. Thank you! What would you like to do next?';
}
<ChatBot
title="My React Bot"
botName="BookTripBotMOBILEHUB"
welcomeMessage="Welcome, how can I help you today?"
onComplete={this.handleComplete.bind(this)}
clearOnComplete={true}
/>
Now, we should see the chatbot and be able to interact with it!
So far what we have is a great intro to creating a bot, but in reality we’ll want the bot to do much more. Much of the power behind using Lex comes with the ease of integration with Lambda functions, allowing us to take the results of the bot conversation and do things with them, like interact with external APIs & services.
If we look at the Fulfillment section of the Lex bot, we’ll see that the current setting is Return parameters to client.
Let’s update our bot to send the response to a Lambda function instead of just returning the response. To do so, we first need to create a new Lambda function.
To create a Lambda function, we need to go to the AWS Lambda console in the AWS dashboard. We can get there by either visiting https://console.aws.amazon.com/lambda or by choosing Lambda from the list of services in the AWS Dashboard.
From the AWS Lambda dashboard, click on the orange Create Function button.
From here, choose Author from Scratch, give the function the name of lambdaBookTripBot, & leave the runtime at Node.js 6.10.
For the Role, choose Create a new role from template(s).
You can give the role any name, I’ll give it the name lambdaBookTripBotRole.
In Policy Templates, choose Basic Edge Lambda permissions as the policy, then click Create Function.
Once the function has been created, we’ll update the function code to the following, and then click Save to update our function code:
exports.handler = (event, context, callback) => {
callback(null, {
"sessionAttributes": JSON.stringify(event.slots),
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": "We booked your reservation yo!"
}
}
});
};
The code we used will handle the final output of our Lex bot, and return the output as the final response of the Lex interaction. In this function, we will be accessing the data from the bot in the event object: event.slots.
Because we will be attaching this Lambda function to the BookTripBotBookHotel intent, event.slots will have the following data structure:
event.slots: {
BookTripBotCheckInDate: "<SOMEVALUE>"
BookTripBotLocation: "<SOMEVALUE>",
BookTripBotNights: "<SOMEVALUE>",
BookTripBotRoomType: "<SOMEVALUE>"
}
Amazon Lex expects a response from a Lambda function in the following format:
{
"sessionAttributes": {
"key1": "value1",
// other attributes
},
"dialogAction": {
"type": "ElicitIntent, ElicitSlot, ConfirmIntent, Delegate, or Close",
// other values depending on the dialogAction "type"
}
}
Depending on the dialogAction type
value, we we will then also be able to pass in other configuration / values. (View full documenation around using Amazon Lex with AWS Lambda).
In our case, we are returning the event.slots as the sessionAttributes, then we’re setting the dialogAction type of Close, a fullfillmentState of Fulfilled, and a message:
{
"sessionAttributes": JSON.stringify(event.slots),
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": "We booked your reservation yo!"
}
}
}
Now that our Lambda function has been created, we can update the Lex bot to use the Lambda function.
In the Lex console of the bot that we’ve already created, click on the BookTripBotBookHotel intent in the left menu.
Next, in the Fulfillment options, update the configuration to AWS Lambda Function. Now, you should be able to choose the lambdaBookTripBot Lambda function that we created a moment ago as the Lambda function we would like to use.
For the version, just choose latest.
Next, we need to save, build, & publish the Intent:
To get started working with the Interactions category, we’ll first need to go ahead and install the UI library we will be using to display the messages:
npm i react-chat-ui
Now, we’ll go ahead and import the dependencies we’ll need to implement everything:
import { Interactions } from 'aws-amplify';
import { ChatFeed, Message } from 'react-chat-ui'
Next, we’ll go ahead and set some initial state:
state = {
input: '',
finalMessage: '',
messages: [
new Message({
id: 1,
message: "Hello, how can I help you today?",
})
],
}
The initial message we will be showing displays a greeting of “Hello, how can I help you today?” to the user, and is set as the first item in the array of messages being held in our state.
For this app to work we will need three class methods:
onChange(e) {
const input = e.target.value
this.setState({
input
})
}
_handleKeyPress = (e) => {
if (e.key === 'Enter') {
this.submitMessage()
}
}
async submitMessage() {
const { input } = this.state
if (input === '') return
const message = new Message({
id: 0,
message: input,
})
let messages = [...this.state.messages, message]
this.setState({
messages,
input: ''
})
const response = await Interactions.send("BookTripBotMOBILEHUB", input);
const responseMessage = new Message({
id: 1,
message: response.message,
})
messages = [...this.state.messages, responseMessage]
this.setState({ messages })
if (response.dialogState === 'Fulfilled') {
if (response.intentName === 'BookTripBookHotel') {
const { slots: { BookTripCheckInDate, BookTripLocation, BookTripNights, BookTripRoomType } } = response
const finalMessage = `Congratulations! Your trip to ${BookTripLocation} with a ${BookTripRoomType} rooom on ${BookTripCheckInDate} for ${BookTripNights} days has been booked!!`
this.setState({ finalMessage })
}
}
}
input
value from the state, then check to see if it is a valid string before we continue.input
value as the message
property.state.messages
, and passing in the new message to the array. We then update the state with the new messages array and the input
value to be an empty string.Interactions.send
, passing in two arguments: The bot name and the value we are passing to it.dialogState
value is Fulfilled
(meaning the interaction is complete), if it is we create a finalMessage
string and update the state with the new value of finalMessage
. We use the response.slots
to build the finalMessage
string.Now, we can update our render
method to use these methods as well as the ChatFeed component from react-chat-ui
:
render() {
return (
<div className="App">
<header style={styles.header}>
<p style={styles.headerTitle}>Welcome to my travel bot!</p>
</header>
<div style={styles.messagesContainer}>
<h2>{this.state.finalMessage}</h2>
<ChatFeed
messages={this.state.messages}
hasInputField={false}
bubbleStyles={styles.bubbleStyles}
/>
<input
onKeyPress={this._handleKeyPress}
onChange={this.onChange.bind(this)}
style={styles.input}
value={this.state.input}
/>
</div>
</div>
);
}
const styles = {
bubbleStyles: {
text: {
fontSize: 16,
},
chatbubble: {
borderRadius: 30,
padding: 10
}
},
headerTitle: {
color: 'white',
fontSize: 22
},
header: {
backgroundColor: 'rgb(0, 132, 255)',
padding: 20,
borderTop: '12px solid rgb(204, 204, 204)'
},
messagesContainer: {
display: 'flex',
flexDirection: 'column',
padding: 10,
alignItems: 'center'
},
input: {
fontSize: 16,
padding: 10,
outline: 'none',
width: 350,
border: 'none',
borderBottom: '2px solid rgb(0, 132, 255)'
}
}
To see the final code, click here.
Now, we should be able to ask the bot any of the sample utterances from our BookTripBotBookHotel hotel bot!
Hear me out - most JavaScript newsletters suck. That's why we made Bytes.
The goal was to create a JavaScript newsletter that was both insightful and entertaining. Over 80,000 subscribers later and well, reviews don't lie
I pinky promise you'll love it, but here's a recent issue so you can decide for yourself.
Delivered to over 80,000 developers every Monday