Lex Bots as Code – The Rise of the Machines

In this post I explore the basics on creating a Lex Bot as code. AWS Lex is a service that allows you to create a bot to respond to certain chat scripts. You essentially define the various ways people will talk to the bot, and then define AWS Lambda functions to actually perform the action requested and respond with the result of that action, or possibly continue on in the script to gather more information or merge into a new conversation altogether.

This isn’t my first soiree into Chat Bots. The astute observer will remember the post IBM BlueMix Cognitive Services – Conversation API and the Ships Computer  where we went through the process of creating a chatbot using IBM Bluemix that mimic’d the functions of the USS Enterprise Computer. While that was a silly endeavor, this one is more fundamental.

The Problem with Chat Bot Versioning

AWS Lex and AWS Lambda, unfortunately, have a versioning system that is linear in nature. With Lambda, it sort of makes sense. Lambda’s are small units of script that usually perform very few functions. One developer can work on them and manage the simple versioning without much thought. If each lambda is a contained development process – with small stable team working on it – a linear versioning model with the alias tagging system works okay.

But the problem comes in with the Chat Bot. Some chat bots are quite complex. They require modifications, updates, as they experience more and more inputs. They are trained by a manual review process – a buggy interface that reports all the “utterances” that it did not recognize. This interface is updated once per 24 hours, but even that is buggy.

The interface as a whole to manage the chat bot, has a lot of versioning on a lot of different pieces. As a result – you might find yourself unsure what version exactly you are modifying of each part of the bot. All edits in the interface are done to a version dubbed $LATEST. This means – definitively – the bot is a one person at a time interface. If two people go to work on the lex bot simultaneously, they are unknowingly overwriting each other, possibly on the same intent or slot. If one builds their version, the $LATEST could have been updated since they last refreshed the interface. As a result, what they end up testing fails, and huge delays are induced in development.

Now, to compound this, the lambda’s that are used to fullfill the Lex bot intents, are independently versioned. They have a $LATEST too, which of course suffers the same clobbering problem. However, once you go to setting alias’s to lambda versions – you cannot select between these in the Lex interface. The whole mechanism to set these things up with more than a single developer is cumbersome.

Lex Bot as Code – Separating model from Deployment

Lex, like Lambda, is essentially a configuration that is built/packaged and served at a versioned endpoint. Alias’s can be assigned to each of those versions. All of the configuration and the deployment is part of the Lex interface. Its not clear from the interface, a good way to parallelize development. One developer can clobber another without even knowing.

For example, lets suppose we want to write a bot that is comprised of over 40 intents – each having 40 lambda fulfillment functions.  You might enlist the help of 10 developers working in parallel to build this bot out quickly. Unfortunately, 10 folks working in this singular interface will not work. The state is constantly being overwritten and there is no medium for a developer to seperate the state long enough for them to complete and commit a feature without another erasing their work.

So we pull the model out of the Lex interface. Lex, at its core, is made of Slots, Intents, Bots, and Lambdas. So we specify the Slots, Intents, and Bots as YAML definitions, and the lambdas as a YAML and associated script file. Using those YAML definitions (which collectively represent an unclobberable state a developer can reliably develop on) the developer can edit the state, and then automatically commit that state to version, assign alias to it, and then automate test cases on the bot. These things all happen in concert and partition off development to their individual alias – such that they can work insulated from others.

Since the YAML and scripts can be checked into a version control system, we can now use git branches tracked to various alias. This allows features to be worked on and tested and eventually merged into the master branch, which will then update the PROD alias on the bot. Without scaffolding to manage this parallel development, it would be impossible to develop large chatbots without significant effort in development effort synchronization.

HelloWorldBot – a walkthrough of the LBaC repo

(Note – not all of the above is currently implemented in the LBaC repo – it is a work in progress. If you want to contribute – please do!)

The LBaC HelloWorldBot source code is available here. You will need Python2.7 and boto3


The main parts of the LBaC repo are

  • deploy.py – this is a python script that will deploy the LB model to the $LATEST version of Lex and Lambda
  • slots/*.yaml – These are the model definition files for Slot Types in Lex. In the HelloWorld boilerplate we define a single slot type – Name
  • intents/*.yaml – These are  the model definition files for the Intents in Lex. In the HelloWorld boilerplate we define a single intent – HelloWorld

  • bots/*.yaml – These are the model definition files for the Bots in Lex. In the Hello World boilerplate we define a single bot – HelloWorldBot

  • lambda/*.yaml – These are the model definition files for the Lambdas in Lex. In the HelloWorld boilerplate we definte a single lambda – SayHello

  • lambda/code/* – these are the scripts that actually make up the Lambda. In the HelloWorld boilerplate we define a script for the SayHello lambda defined in the above directory

The model files are all parameter matches from the corresponding boto3 methods. If you are unsure what a parameter means – or the values it can take – refer to the LexModelBuildingService documentation for boto3

Deploying the model

Once the model is correctly set, the developer simply runs the deploy.py script. Remember to set your AWS credentials in your ~/.aws directory so that boto can actually perform the actions. The deploy script translates your model – to become the active $LATEST version of the bot and lambdas on AWS.

From there you can immediately build and version the bot.

Next Steps

In the next steps we are looking to make a release and development pipeline around the deployments – automatically building, publishing, assigning alias, and running automated test cases around the robotos and their various channels.