Part of my role at Rubrik is to educate and help customers and technologists who want to develop scripts and solutions utilizing APIs. One of my favorite ways to do that is through the creation of a chatbot, at Rubrik, we use Roxie, Rubrik’s intelligent personal assistant. Roxie is built utilizing two AWS services; Lex and Lambda. Lex handles the conversational pieces, while Lambda goes out and fulfills the question (or, you know, gets the answer). While it’s most definitely a great exercise to go through the process of creating Roxie manually (I promise, you’ll learn lots about AWS and consuming APIs in general), it’s also not something I enjoy doing over, and over, and over, and over (you get the picture). So with that said, during my last little development stint with Roxie I decided to figure out how to automate the complete creation of both the Lex Bot itself and the Lambda function! My weapon of choice, Python – the results, well, I think they are pretty good! So with that said, let me walk through the process of automating the creation of a Lex and Lambda chatbot with Python.
Prerequisites
As with anything tech/developer-related, there are a number of prerequisites that must be met in order to successfully follow along here, some obvious, some are not…
- Python – yeah, you’ll need python installed on your workstation
- AWS Account – pretty obvious, but you will need an AWS account to play with
- AWS CLI – Having the AWS CLI installed and configured just makes things easier, that way we can just grab access and secret keys for the python code directly from your credentials file.
- Boto3 SDK – We will be interacting with a number of AWS services via Python, so boto3 is a must! (pip install boto3)
- AWS Lambda Role – An IAM role within AWS to assign to the Lambda function
- AWS Lex Role – An IAM role within AWS to allow the Lex bot to run under. This is autocreated when you manually create a bot, but not when you programatically do it. You are best to simply go and manually create a test bot, get the Role ARN, then delete the bot 🙂
With these in place, let’s just jump right into the code!
Step 1 – Create the Lambda Function
Sometimes it’s just easier to see all the code and then break it down further afterward.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import boto3 import json import sys config = { "aws_lambda_function_name": "roxie", "aws_region": "us-west-2", "aws_lambda_subnet_ids": "subnet-0bd3e627ba5bdb539,subnet-07a7efd2e2b097d64", "aws_lambda_security_group_ids": "sg-0cf9d4ed6393e46e7", "aws_lambda_runtime": "python3.7", "aws_lambda_role_arn": "arn:aws:iam::1233456789:role/service-role/roxie_lambda", "aws_lambda_environment_var_1": "10.8.107.34", "aws_lambda_environment_var_2": "BIG_LONG_AUTH_TOKEN" } from botocore.config import Configboto_config = Config( region_name = config['aws_region'] ) lmda = boto3.client('lambda', config=boto_config) lambda_function = lmda.create_function( FunctionName=config['aws_lambda_function_name'], Runtime = config['aws_lambda_runtime'], Handler = 'lambda_function.lambda_handler', Role = config['aws_lambda_role_arn'], Code = {'ZipFile': open('lambda-function.zip','rb').read()}, Timeout = 45, VpcConfig = { 'SubnetIds': config['aws_lambda_subnet_ids'].split(","), 'SecurityGroupIds': config['aws_lambda_security_group_ids'].split(",") }, Environment = { 'Variables': { 'ENVIRONMENT_VAR_1': config['aws_lambda_environment_var_1'], 'ENVIRONMENT_VAR_2': config['aws_lambda_environment_var_2'] } } ) |
Alright, let’s break it down
- Lines 1-3 – These simply import packages
- Lines 5-14 – This defines a variety of variables that we will need for the configuration
- Lines 16-18 – This ensures that we are working within the proper AWS region
- Line 21 – Establishes a Lambda client
- Lines 23-40 – Code to actually create the Lambda function.
- Line 27 – This defines the role under which the fuction will run, this will need to be created a head of time.
- Line 28 – This points to a zip file containing the code for the Lambda function
- Lines 30-33 – The VPC configuration the function will run within
- Lines 34-39 – Any environment variables you want to live within your Lambda function.
Now we aren’t actually completely done with Lambda as of yet, we will need to add permissions to our function allowing our Lex Bot to execute it. That said, we don’t have a Lex Bot yet, so let’s create that, and we’ll come back to Lambda.
Step 2 – Create the Lex Bot
So Lambda was pretty quick, but Lex is a little more involved and has a few more moving parts! Again, let me show you the complete code, and further break it down… Just a note, the following code utilizes the Lex v2 APIs!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
import boto3 import json import sys import time from botocore.config import Config config = { "aws_lambda_function_name": "roxie", "aws_region": "us-west-2", "aws_lex_bot_name": "roxie", "aws_lex_role_arn": "arn:aws:iam::123456789:role/aws-service-role/lexv2.amazonaws.com/AWSServiceRoleForLexV2Bots", "aws_lambda_environment_var_1": "10.8.107.34", "aws_lambda_environment_var_2": "BIG_LONG_AUTH_TOKEN" } from botocore.config import Configboto_config = Config( region_name = config['aws_region'] ) lex = boto3.client('lexv2-models',config=boto_config) lmda = boto3.client('lambda', config=boto_config) print("Creating Bot") response = lex.create_bot( botName=config['aws_lex_bot_name'], description="Roxie, Rubrik's intelligent personal assistant", dataPrivacy={ 'childDirected': False }, idleSessionTTLInSeconds=300, roleArn = config['aws_lex_role_arn'] ) aws_lex_bot_id = response["botId"] print("Waiting for bot to be created...") response = lex.describe_bot( botId=aws_lex_bot_id ) bot_status = response['botStatus'] while bot_status != "Available": print("Bot status is "+ bot_status) time.sleep(2) response = lex.describe_bot( botId=aws_lex_bot_id ) bot_status = response['botStatus'] response = lex.describe_bot( botId=aws_lex_bot_id ) print ("Bot created...") print("Creating en_US Bot Locale") response = lex.create_bot_locale( botId=aws_lex_bot_id, botVersion = "DRAFT", localeId = "en_US", nluIntentConfidenceThreshold=0.40, voiceSettings={ 'voiceId': 'Ivy' } ) response = lex.describe_bot_locale( botId = aws_lex_bot_id, botVersion = "DRAFT", localeId = "en_US" ) locale_status = response['botLocaleStatus'] while locale_status != 'NotBuilt': print ("Bot Locale Status is " + locale_status) time.sleep(2) response = lex.describe_bot_locale( botId = aws_lex_bot_id, botVersion = "DRAFT", localeId = "en_US" ) locale_status = response['botLocaleStatus'] print("Bot Locale en_US created!") print("Creating Intents...") with open('intents.json') as f: data = json.load(f) for intent in data['Intents']: aws_lex_intent_utterances = intent['utterances'] aws_lex_intent_name = intent['name'] aws_lex_intent_description = intent['description'] response = lex.create_intent( intentName=aws_lex_intent_name, description=aws_lex_intent_description, sampleUtterances=aws_lex_intent_utterances, fulfillmentCodeHook={ 'enabled': True }, botId = aws_lex_bot_id, localeId="en_US", botVersion="DRAFT", ) print("Done adding intents!") print("Attaching Lex bot to Lambda function") response = lex.list_bot_aliases( botId = aws_lex_bot_id ) aws_lex_bot_alias_id = response['botAliasSummaries'][0]['botAliasId'] aws_lex_bot_alias_name = response['botAliasSummaries'][0]['botAliasName'] # Get lambda Arn response = lmda.get_function( FunctionName=config['aws_lambda_function_name'] ) aws_lambda_function_arn = response['Configuration']['FunctionArn'] # Update alias w/ Lambda response = lex.update_bot_alias( botAliasId = aws_lex_bot_alias_id, botAliasName = "TestBotAlias", botVersion = "DRAFT", botAliasLocaleSettings={ "en_US": { "enabled": True, "codeHookSpecification": { "lambdaCodeHook": { "lambdaARN": aws_lambda_function_arn, 'codeHookInterfaceVersion': "1.0" } } } }, botId = aws_lex_bot_id ) print("Done attaching Lex to Lambda") print("Adding permission for Lex to call Lambda function") aws_account_id = boto3.client('sts').get_caller_identity().get('Account') aws_lex_bot_arn = "arn:aws:lex:{0}:{1}:bot-alias/{2}/{3}".format(config['aws_region'], aws_account_id,aws_lex_bot_id,aws_lex_bot_alias_id) response = lmda.add_permission( FunctionName=config['aws_lambda_function_name'], StatementId="AllowLexBot-"+config['aws_lex_bot_name']+"-AccessToLambdaFunction-"+config['aws_lambda_function_name'], Action="lambda:InvokeFunction", Principal="lexv2.amazonaws.com", SourceArn=aws_lex_bot_arn ) print("Done with permissions") print("Beginning initial build of bot") response = lex.build_bot_locale( botId=aws_lex_bot_id, botVersion="DRAFT", localeId="en_US" ) print("Build has began on bot.") |
Alright, again, let’s break this all down
- Lines 1-5 – import things
- Lines 7-14 – provide some configuration items
- Lines 16-18 – establish we are in the proper region
- Line 20 – Creates a Lex client
- Line 21 – Creates a Lambda client
- Lines 23-32 – This is the code to initially create the Bot
- Line 27 – Sets the COPPA value to false
- Line 31 – Specifies the proper AWS Lex IAM role (created automatically when manually creating a bot)
- Line 33 – Retrieves the newly created Bot ID
- Lines 35-50 – Polls the bot creation status to ensure it is indeed created before continuing
- Lines 52-78 – Creates the en_US locale within the bot, and waits until complete
- Lines 80-99 – Creates some intents within the bot. In this case, intents are specified in a separate file (intents.json). I’ll provide a sample of that file below.
- Lines 101 – 106 – This grabs the bot alias ID as we need it further down
- Lines 108-112 – This grabs the ARN for our Lambda function, as we need it further down
- Lines 113-131 – This instructs our Lex bot to utilize the specified Lambda function to fulfill intents
- Lines 133-144 – This adds a permission to the Lambda function, allowing the Lex Bot to execute it.
- Lines 146-152 – This simply just kicks off a build process on our bot.
And that’s basically it! Hopefully, these two chunks of code may help you through the process of automating the creation of a Lex and Lambda chatbot with Python I know this has saved me a ton of time and has helped to simplify things for our customers looking to simply get a chatbot up and running, If you are interested, check out the Roxie project. The python script located there goes through the complete process of even generating the configuration. Thanks for reading!