AWS Lex Chat Bot
Creating the Lex bot
Amazon makes it fairly straightforward to create a chatbot. If you do not already have an AWS account make sure you make one so that you can follow along. The resources that we will be using during the post will not incur any charges on your AWS account. Log into your AWS account and navigate to the Lex service. If it is your first time using Lex, click on the Get Started button in the middle of the screen. If it is not your first time click on the Create button. Click on Custom Bot and choose a name and output voice for your bot. You could also just choose the option for “None this is only a text-based app”. For the Session timeout field, 1 minute will be fine and for the COPPA field choose the option, No. Once you have filled everything out click Create.
Adding an Intent to the Bot
Click on Create Intent then Create new intent. For the name just type MakeUsername then Add. The bot will create a username using the name and number that you provide. Now that our intent has been added to the bot, we need to define the utterances that a user can say to invoke the intent. In the Sample, Utterances use “MakeUsername”.
For our MakeUsername intent, we are going to need two slots to be filled. A slot that will hold a name and a slot that will hold a number. In the Slots section fill it out to look like this:
The slot type for the Name slot is AMAZON.US_FIRST_NAME and the slot type for the Number slot is AMAZON.NUMBER. Now click on the gear icon in the Prompt field for both the Name slot and Number slot and make them look like these:
Make sure you click save! The prompts are what the bot will say to the user to try to fill the required slot. The corresponding utterances are the possible things a user can say to fill in that slot/answer the bot’s prompt. Once you are back to the page where our MakeUsername intent is, scroll to the bottom and click SaveIntent. We will have to fill out the Fulfilment section, but we will come back to it later.
Creating Our Lambda Function
We will be using Amazon’s Lambda to handle the logic for the bot. Before we create our function we need to give it an IAM role that allows it to you use other AWS services on our behalf. To do this navigate to the IAM console and click on roles. From there click on Create role, choose Lambda from the AWS service options, and then click Next: Permissions. In the search bar type in “lambda basic” and you should see permission named “AWSLambdaBasicExecutionRole”. Once selecting the checkbox for that permission click Next: Review then gives your role a name like “basic-lambda-execution” and finally click Create role. To create our function, first navigate to the Amazon Lambda console, click Create function, and then Author from scratch. For the functions name type in “BotHandler” and for the role choose the role that you just created in IAM, once complete click Create function. All the default settings are fine, but to be safe change the Timeout to 1 min. Since it is a simple bot we will write the code inline in Node.js. If Node.js isn’t your primary language, just adapt the code to your language of choice.
// Helper function used to create the proper response back to Lex
function close(message) {
return {
dialogAction: {
type: 'Close',
fulfillmentState: 'Fulfilled',
message,
},
}
}exports.handler = (event, context, callback) => {
console.log(event.currentIntent)
console.log(context)
let { Name: name } = event.currentIntent.slots
let { Num: number } = event.currentIntent.slots
callback(null, close({
contentType: "PlainText",
content: `Your username is ${name}${number}`
}))
}
All the code does is place the slot values in variables and returns the response back to Lex with a message of “Your username is {name}{number}”, where name and number are the values that were given to Lex to fill the slots.
Finalizing the Bot
All that is left now is to let our bot know which Lambda function to use for its fulfillment. Navigate back to the Lex Console and into the bot that you created following this post. In the MakeUsername intent scroll to the bottom until you reach the Fulfillment section. Choose AWS Lambda Function and choose the “BotHandler” function we created earlier. We do not need a goodbye / follow-up message so choose none. Lastly, make sure you click Save intent, then at the top click Build. Now you can test it out by using one of the sample utterances we defined earlier. When you test it out it should look something like this:
Setting up Amazon Cognito
In order for us to talk to our bot from an external source like a mobile app, we are going use Amazon Cognito to give our React Native app permissions to do so. From the Amazon Cognito console, click Manage Federated Identities, and then Create new identity pool. Provide a pool name like “LexBotPool”, select Enable access to unauthenticated identities, and then click Create Pool. On the following page leave all the defaults and click Allow. Change the environment to Javascript and copy the sample code, we will need it later.
Now that we created a pool we need to add specific permissions to allow the pool to use Lex. Navigate to the IAM console and select Roles. In Roles find both the Cognito roles you created and attach these policies: AmazonPollyReadOnlyAccess and AmazonLexRunBotsOnly.
Adding the Bot to a React Native App
If you don’t already have the react-native-cli installed, install it with this command npm install react-native-cli -g
. Once installed we can create a new project with react-native init SampleBotApp
. The installation of all the dependencies does take a little time, so be patient. After the react-native dependencies are installed we need the aws-sdk to be installed, so navigate into the directory SampleBotApp from your terminal and run the command npm install aws-sdk --save
Once everything is installed open up the folder that was created in your favorite text editor / IDE. The only file we will be changing is App.js. Change it to look like this:
import React, { Component } from 'react'
import {
Text,
View,
StyleSheet,
TextInput,
FlatList,
} from 'react-native'
import AWS from 'aws-sdk/dist/aws-sdk-react-native'
// Initialize the Amazon Cognito credentials provider
AWS.config.region = 'us-east-1' // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'Your-Pool-ID-8673',
})let lexRunTime = new AWS.LexRuntime()
let lexUserId = 'mediumBot' + Date.now()const styles = StyleSheet.create({
container: {
flex: 1,
},
messages: {
flex: 1,
marginTop: 20,},
botMessages: {
color: 'black',
backgroundColor: 'white',
padding: 10,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 20,
borderTopLeftRadius: 20,
marginBottom: 0,
borderTopRightRadius: 20,
alignSelf: 'flex-start',
bottom: 23,
textAlign: 'left',
width: '75%'
},userMessages: {
backgroundColor: '#40AD4D',
color: 'white',
padding: 10,
marginBottom: 10,
marginRight: 10,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 0,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
width: '65%',
alignSelf: 'flex-end',
textAlign: 'left'
},textInput: {
flex: 2,
paddingLeft: 15
},
responseContainer : {
flexDirection: 'row',
marginTop: 20,
marginBottom: 0,
},
inputContainer: {
flexDirection: 'row',
backgroundColor: '#EEEFFA',
},
})export default class App extends Component {
constructor(props) {
super(props) this.state = {
userInput: '',
messages: [],
inputEnabled: true,
}
}// Sends Text to the lex runtime
handleTextSubmit() {
let inputText = this.state.userInput.trim()
if (inputText !== '')
this.showRequest(inputText)
}// Populates screen with user inputted message
showRequest(inputText) {
// Add text input to messages in state
let oldMessages = Object.assign([], this.state.messages)
oldMessages.push({from: 'user', msg: inputText})
this.setState({
messages: oldMessages,
userInput: '',
inputEnabled: false
})
this.sendToLex(inputText)
}// Responsible for sending message to lex
sendToLex(message) {
let params = {
botAlias: '$LATEST',
botName: 'Your-Bot-Name',
inputText: message,
userId: lexUserId,
} lexRunTime.postText(params, (err, data) => {
if(err) {
// TODO SHOW ERROR ON MESSAGES
}
if (data) {
this.showResponse(data)
}
})
}showResponse(lexResponse) {
let lexMessage = lexResponse.message
let oldMessages = Object.assign([], this.state.messages)
oldMessages.push({from: 'bot', msg: lexMessage})
this.setState({
messages: oldMessages,
inputEnabled: true
})
}renderTextItem(item) {
let style,
responseStyle
if (item.from === 'bot') {
style = styles.botMessages
responseStyle = styles.responseContainer
} else {
style = styles.userMessages
responseStyle = {}
}
return (
<View style={responseStyle}>
<Text style={style}>{item.msg}</Text>
</View>
)
}render(){
return(
<View style={styles.container}>
<View style={styles.messages}>
<FlatList
data={this.state.messages}
renderItem={({ item }) => this.renderTextItem(item)}
keyExtractor={(item, index) => index}
extraData={this.state.messages}
/>
</View>
<View style={styles.inputContainer}>
<TextInput
onChangeText={(text) => this.setState({userInput: text})}
value={this.state.userInput}
style={styles.textInput}
editable={this.state.inputEnabled}
placeholder={'Type here to talk!'}
autoFocus={true}
onSubmitEditing={this.handleTextSubmit.bind(this)}
/>
</View>
</View>)
}
}
At the top of the file you need to import the aws-sdk and set the cognito credentials using the sample code we copied down earlier. Once this is done you can initialize the Lex Runtime to a vairable which will allow you to call the different methods from the Lex Runtime. To see more documenation on the Lex runtime visit here. The stylesheet and specific React-Native components are not too important. In this example a Flatlist is used to lazily render the data, in this case this.state.messages. For a Flatlist component we also need to specify how we want to render new data, in this case it is with the function renderTextItem. The important functions to take note of are showRequest, sendToLex, and showResponse. The function showRequest takes in the input and sets the state with the new input added to this.state.messages. When the state is changed our renderTextItem function renders the text bubble of what the user typed in, onto the screen. The showRequest function also calls the function sendToLex with the inputted message. The Lex runtime has a method called postText which takes in an object for its first parameter which holds our message, bot name, and other required data. The callback in the second parameter holds the response that our bot sends back to us. At this point we call showResponse to change the state of our messages which then causes renderTextItem to render the text bubble with the response from our bot. The way we distinguish between a user message and a message from our bot in this example is with a from field. Running the app will be different depending if you are on a Mac or Windows and whether you want to run the app on an iPhone or android emulator. If you have an android emulator running simply type this command in your terminal: react-native run-android
. If you need help getting the app running follow these instructions and click on the “Building Projects with Native Code” tab. Once you run the app you can invoke the intent just like how we did in the Lex console. The app should look as follows: