photo by rawpixel / Pixabay
On our latest project we have added a chat bot to a website for an alternative way to get input parameters from user in natural language. We’ve got a library of objects we can visualise based on user’s parameters, and it’s stored in the database. When new objects are added to the database, we want Lex to know about them.
I am using Node.js to do this job. It is easy to plug AWS SDK library in and call Lex API functions from the code. It’s been a while since I have written a single line in Javascript. Also the last time it happened, Javascript still was a language for website improvements and was executed on the client side by browser. To say that I was astonished by the fact that one can program some server-side functions on Javascript these days is a bit of understatement, I was shocked, but keen to give it a go. As Javascript is an asynchronous language, it didn’t exactly help in writing this fully synchronous code; I had to put in extra effort to make sure all pieces are executed one after another and not concurrently or in random order. I am using Typescript as it makes Javascript look more like object-oriented strongly typed language, which is much more suitable for the server-side code.
Update Slot Type
Step one is to get the data from the database. You can find some examples on the internet for how to do it. I’m using MongoDB, it is also quite easy to plug in the library and get data with just a few lines of code. For our purpose returned result should be an array of slot type values and their synonyms:
[code][
{
"value": "string",
"synonyms": ["string", …] }
…
][/code]Synonyms allow chat bot to be smarter and better understanding of users’ requests. I use thesaurus.com to get a relevant list of synonyms for slot type values.
Amazon has built a good mechanism to prevent unintentional updates. One can only update an object if they pass a checksum of the latest version of this object as a part of update request. So, step two is to get the current slot type version checksum from Lex with getSlotType function. Consider getting description as well if you use this field, otherwise it’ll be removed on the next step. Slot type could be called by its name and version number, which should be set to the “$LATEST”, i.e. there is no need to know the exact version number, which is very convenient.
[php]let AWS = require(‘aws-sdk’);
AWS.config.update({region:’us-east-1′});
let lexmodelbuildingservice = new AWS.LexModelBuildingService({apiVersion: ‘2017-04-19’});
const SlotTypeLatest = {
name: "VisualObject",
version: ‘$LATEST’
};
//Enforce the AWS Lex promise to execute before moving forward
let data = await lexmodelbuildingservice.getSlotType(SlotTypeLatest).promise();
console.log("Objects slot checksum: " + data.checksum);
[/php]I don’t set any credentials here, they are automatically pulled from the shared credential file while code runs on my computer and will be handled when deployed to AWS Lambda. Check the documentation on other ways of managing credentials. At the moment Amazon Lex is not available in my region, so I need to specify it in my code. The rest of the code is just using the well documented Lex API. Documentation describes AWS functions in callback style, but for my purpose I promisify them and pause the execution on the main stack to wait for the checksum returned from AWS Lex.
Step three is to combine the results from database and the checksum in one object that is passed as a parameter to the putSlot function. Put the slot type name into name field; we’ve got the latest checksum and optionally description on the previous step; enumerationValues is the array of values and their synonyms we’ve got from the database. I use valueSelectionStrategy = “TOP_RESOLUTION” because extra values could not be added to slot type by users of my bot, all the inputs should be resolved to one of the existing slot type values with the help of synonyms, or failed if I don’t have such object in my library of objects. The function returns new slot type definition. If there was no change to the slot type definition, Lex won’t create new slot type version and the checksum will stay the same.
Update Intent
Intents could use specific version of slot type, not necessarily the latest one. But as we want to propagate new slot type values to be used by the latest version of bot, we need to make intents using the latest version of slot first. Step four is to get the current definition of intent with the getIntent function. It returns an intent object; it’s quite big compared to the slot type object as intent has more complex structure. We need all the returned fields except for version, createdDate and lastUpdatedDate.
[php]let data = await lexmodelbuildingservice.getIntent(params).promise();
let intentDefinition: Intent = {
name: data.name,
checksum: data.checksum,
slots: data.slots,
sampleUtterances: data.sampleUtterances
};
//Optional fields
if (data.description) {IntentDefinition.description = data.description;};
if (data.rejectionStatement) {IntentDefinition.rejectionStatement = data.rejectionStatement;};
if (data.confirmationPrompt) {IntentDefinition.confirmationPrompt = data.confirmationPrompt;};
if (data.fulfillmentActivity) { IntentDefinition.fulfillmentActivity = data.fulfillmentActivity;};
if (data.followUpPrompt) { IntentDefinition.followUpPrompt = data.followUpPrompt;};
if (data.conclusionStatement) { IntentDefinition.conclusionStatement = data.conclusionStatement;};
if (data.dialogCodeHook) { IntentDefinition.dialogCodeHook = data.dialogCodeHook;};
if (data.parentIntentSignature) { IntentDefinition.parentIntentSignature = data.parentIntentSignature;};
[/php]Step five is to manipulate with the slots array. In my case intent uses more than one slot type updated from the database, so I have a loop to change the slot type version to “$LATEST” for all of them (check a catch below), except for the built-in Amazon slot types, they don’t have versions. Once slot version is updated, call putIntent function and pass the updated intent definition as a parameter. It returns the updated intent. Note that all intents in one bot should use the same version of slot type, so it makes sense to put this into function and execute it for all intents in the bot which are using updated slot types.
Update Bot
This step is very similar to the intent update. Get the JSON object of bot definition first with the getBot function. Make sure intents versions are the “$LATEST” and pass the bot object to putBot function call. If the bot definition has changed, by default the bot build will be triggered by this function. Note that this function returns the result immediately reporting the bot status as “BUILDING”. Building a bot takes time.
Unfortunately, you can’t use the latest version of bot while it’s building. If you use processBehavior property and set it to “SAVE”, it won’t help much as bot could not be used until it’s build. To make sure there’s no outage for users while you are building the new version, use bot aliases. Your production alias should never point to $LATEST version, which is your working copy.
A Catch to Be Aware Of
The documentation states: “the $LATEST version of an intent can use the $LATEST version of a slot type”. This is true as far as you use API only. Every time you open a built bot in Amazon Console, it replaces the “Latest” version of slot type to a numbered version, i.e. the $LATEST intent cannot use the $LATEST slot on console. If you leave the page now, even if you didn’t click “Save intent”, it does update intent behind the scenes, which changes the bot status to “NOT_BUILT”. It took me a while to understand what is going on, when I updated the bot from my code, then opened it in console, tested the latest version – it worked fine and used new slot values, then I got back to the list of bots only to find out that my bot is “Not Built”. I have raised this issue with Amazon a few days ago, mid-February 2019, they have promised to investigate. Until Amazon aligns API and console behaviour, your options are: 1) remember to rebuild or publish the bot every time you open it in console; 2) never use console, which is reasonably ok if you are used to the command line interface – I wasn’t, but now I do it like a pro; or 3) publish the bot on the last step – it will resolve all the references between object versions, but can cause having more versions than you ever wanted.
Kate
Data masseuse