Bringing Amazon Lex into your Amazon Connect flows

In this blog we’ll continue our discussion around Amazon Lex. Talk about a few things to keep in mind when integrating your Amazon Lex bot with your Amazon Connect flow. In my particular use case I wanted to use Amazon Lex to look at my Gmail calendar and book a meeting if I’m available. If you want to skip to the very end you can see the end result via video. You’ll see one video of the voice interaction and one of the Facebook Messenger interaction.

First, you might want to reference my previous post around Lex validation. Now let’s talk about our use case:

  • Lex easily allows you to build a bot which understand both voice and text, so our bot needs to handle calls into our call center as well as Facebook Messenger interactions.
  • Bot needs to to ask a few question in order to find out what time the user would like to meet.
  • Bot should only schedule calls between Monday-Friday and 10 AM – 4 PM Easter Time
  • Bot (using Lambda) should schedule a meeting and if slot already taken then suggest an alternate time to meet.

Second, let’s take a quick look at the Lex screen. The bot I created is very simple and it follows closely the Flowers example provided by Amazon. These are the slots I’m requiring my bot to confirm.

image

I used two different Lambda functions. One for validation and one for fulfillment. While most examples seem to focus on using the same function for both, for me it was easier to have different code bases for each with the added benefit of keeping the code manageable. As it is both validation and fulfillment both came in at around 250 lines of code, but fulfillment had around 9 megabytes of dependencies.

image

Finally, here are sample utterances I used for the main intent.

image

What this gets us is the following. The first video is the voice interaction. I went about it the long way to show some of the validation rules being set by the bot, such as no weekend meetings and no meetings too early in the day. At the end of the video you see I refresh the Gmail calendar to show the new appointment has been saved.

In the second video I go through the same Lex bot using Facebook Messenger and then show the calendar to prove that the appointment was saved.

Ultimately, Amazon makes it extremely easy to create a mutli channel bot, however the integration to back end systems is the tricky part. This bot needs a lot of tuning to make it more natural, but for just a few hours of work there’s very little out there that can get your call center to have some bot integration for self service.

~david

Creating a Lambda Function to Validate Lex Input

In this blog we’re going to step a bit away from Amazon Connect and focus on building a conversational interface using Amazon Lex. As you can probably guess down the line, this interface/bot is going to be connected to Amazon Connect for even more contact center goodness. Here we’re going to focus on creating a Lambda function strictly for validation, not for fulfillment.

First, let’s talk about what I’m building. I’m building a bot which can schedule a time to have a call with me. You tell your intention to the bot “schedule a meeting/call” and the bot will then ask you a few questions using directed language to figure out when you want to meet. Once Lex has all the information it needs it goes out to my calendar to figure out if I’m free or busy. Second, the validation code I have is mainly based on one of Amazon’s great blueprint for ordering flowers. I recommend you start with that before trying to write your own from scratch. Finally, read through the code and pay close attention to the comments marked in bold as these were the biggest gotchas as I went through.

A couple of things to keep in mind when building a conversation interface with Amazon Lex and you’re using validation.

– Have a clear scope of the conversation. I’m not a VUI designer by any means, but if you’re planning on going with an open-ended prompt “How may I help you?” you will be working on this for a long time. Instead try to focus on the smallest possible outcome. Ultimately, it is my opinion that no IVR is really NLU and they are all just directed speech apps with a lot more money sunk into them so they can be called NLU IVRs.

– If you’re going to use input validation, every user input will be ran through Lambda. This means that you must account for people saying random things which aren’t related to what your bot does and these random things will be processed through the validation function and might generate errors. Thus, you need to ignore this input and direct the customer to answer your question, so you can move on.

– Separating validation from fulfillment makes the most sense. Other than making your code easier to read and manage, you’re also able to separate responsibilities and permissions between your two Lambda functions.

– Play around with the examples Amazon provides. They are a great tool to get started and give you a ton of building blocks you can use in your own bot.

Here’s the validation code as well as some notes, hopefully this helps someone else along the way.

'use strict';

// --------------- Helpers to build responses which match the structure of the necessary dialog actions -----------------------

//elicitSlot is in charge of building the request back to Lex and tell Lex what slot needs to be re-filled.
function elicitSlot(sessionAttributes, intentName, slots, slotToElicit, message) {
return {
sessionAttributes,
dialogAction: {
type: 'ElicitSlot',
intentName,
slots,
slotToElicit,
message,
},
};
}

function close(sessionAttributes, fulfillmentState, message) {
return {
sessionAttributes,
dialogAction: {
type: 'Close',
fulfillmentState,
message,
},
};
}

function delegate(sessionAttributes, slots) {
return {
sessionAttributes,
dialogAction: {
type: 'Delegate',
slots,
},
};
}

function confirm(sessionAttributes, intentName, slots){
return{
sessionAttributes,
dialogAction:{
type: 'ConfirmIntent',
intentName,
slots,
message: {
contentType: 'PlainText',
content: 'We are set, do you want to schedule this meeting?'
}
},
};
}
// ---------------- Helper Functions --------------------------------------------------

function isDateWeekday(date) {
const myDate = parseLocalDate(date);
if (myDate.getDay() == 0 || myDate.getDay() == 6) {
console.log("Date is a weekend.");
return false;
} else {
console.log("Date is a weekday.");
return true;
}
}

function parseLocalDate(date) {
/**
* Construct a date object in the local timezone by parsing the input date string, assuming a YYYY-MM-DD format.
* Note that the Date(dateString) constructor is explicitly avoided as it may implicitly assume a UTC timezone.
*/

const dateComponents = date.split(/\-/);
return new Date(dateComponents[0], dateComponents[1] - 1, dateComponents[2]);
}

function isValidDate(date) {
try {
return !(isNaN(parseLocalDate(date).getTime()));
} catch (err) {
return false;
}
}
function buildValidationResult(isValid, violatedSlot, messageContent) {
if (messageContent == null) {
return {
isValid,
violatedSlot,
};
}

return {
isValid,
violatedSlot,
message: { contentType: 'PlainText', content: messageContent },
};
}

function validateMeeting(meetingDate, meetingTime, meetingLength) {

if (meetingDate) {
if (!isValidDate(meetingDate)) {
return buildValidationResult(false, 'MeetingDate', 'That date did not make sense. What date would you like to meet?');
}

if (parseLocalDate(meetingDate) < new Date()) {
return buildValidationResult(false, 'MeetingDate', 'You can only schedule meetings starting the next business day. What day would you like to meet?');
}

if (!isDateWeekday(meetingDate)) {
return buildValidationResult(false, 'MeetingDate', 'You can only schedule meetings during the normal weekday. What day would you like to meet?');
}

if (meetingTime) {
if (meetingTime.length !== 5) {
// Not a valid time; use a prompt defined on the build-time model.
return buildValidationResult(false, 'MeetingTime', null);
}
const hour = parseInt(meetingTime.substring(0, 2), 10);
const minute = parseInt(meetingTime.substring(3), 10);
if (isNaN(hour) || isNaN(minute)) {
//Not a valid time; use a prompt defined on the build-time model.
return buildValidationResult(false, 'MeetingTime', null);
}
if (hour < 10 || hour > 16) {
//Outside of business hours

return buildValidationResult(false, 'MeetingTime', 'Meetings can only be scheduled between 10 AM and 4 PM. Can you specify a time during this range?');

}
}

if(!meetingLength){
return buildValidationResult(false, 'MeetingLength', 'Will this be a short or long meeting?');
}
}
return buildValidationResult(true, null, null);
}

//--------------- Functions that control the bot's behavior -----------------------
function orderFlowers(intentRequest, callback) {
const source = intentRequest.invocationSource;
//get appointment slots
const meetingDate = intentRequest.currentIntent.slots.MeetingDate;
const meetingTime = intentRequest.currentIntent.slots.MeetingTime;
const meetingLength = intentRequest.currentIntent.slots.MeetingLength;

//For fullfilment source will NOT be DialogCodeHook
if (source === 'DialogCodeHook') {
//Perform basic validation on the supplied input slots. Use the elicitSlot dialog action to re-prompt for the first violation detected.
const slots = intentRequest.currentIntent.slots;
const validationResult = validateMeeting(meetingDate, meetingTime, meetingLength);

if (!validationResult.isValid) {
slots[`${validationResult.violatedSlot}`] = null;
callback(elicitSlot(intentRequest.sessionAttributes, intentRequest.currentIntent.name, slots, validationResult.violatedSlot, validationResult.message));
return;
}

//Pass the price of the flowers back through session attributes to be used in various prompts defined on the bot model.
const outputSessionAttributes = intentRequest.sessionAttributes || {};
callback(delegate(outputSessionAttributes, intentRequest.currentIntent.slots));
return;
}
}

// --------------- Intents -----------------------
function dispatch(intentRequest, callback) {
const intentName = intentRequest.currentIntent.name;

//Dispatch to your skill's intent handlers
if (intentName === 'MakeAppointment') {
return orderFlowers(intentRequest, callback);
}
throw new Error(`Intent with name ${intentName} not supported`);
}

//--------------- Main handler -----------------------
//Route the incoming request based on intent.
//The JSON body of the request is provided in the event slot.
//Execution starts here and moves up based on function exports.handler => dispatch =>orderFlowers=>validateMeeting=>buildValidationResult is the most typical path a request will take.

exports.handler = (event, context, callback) => {
try {
dispatch(event, (response) => callback(null, response));
} catch (err) {
callback(err);
}
};

Right way to block ANIs using Amazon Connect

In this blog I’ll cover a potential financial issue you might face if you try to ANI block customers and they are calling you through a SIP trunk.

As I continue my journey of getting familiar with Amazon Connect I ran into an interesting and a bit worrisome issue. The use case I was working on was to create a table which blocks or allows specific ANIs to call in. Ultimately, when a blocked caller came in I wanted to just hang up on the call. My original flow looked like this:

image

Pretty straight forward, invoke Lambda, check attribute and if blocked = true, disconnect the call. When calling from my cellphone this worked great. However, when calling from my home phone (using a Flowroute SIP trunk) I got a nice surprise in the logs:

image

What you’re seeing is a partial log of my home phone constantly retrying to connect to Amazon Connect and generating a new call each time. Since there was no prompt play and no ring back heard I assume the network believes there as a connection issue and continues to try and connect. Which means that you could easily incur a huge expense both on your phone provider and on your Amazon AWS bill.

The way to fix this was to play a 1 second of silence prompt before disconnecting the call.

image

~david

Amazon Connect and Sticky Queue

In this blog I’ll discuss how to achieve a sticky queue using Amazon Connect.

When a customer calls back within a short amount of time, it’s fairly safe to assume they are calling back for the same reason as before. This is often referred as sticky agent or sticky queue. Because you’re trying to “stick” a queue or agent to a specific customer. As a best practice avoid using anything sticky as it could force your customer down the wrong path or create long hold times when too many callers are stuck to a single agent or queue, but for my use case it’s safe to use because I want to use it. :)

I assume you already have a Lambda function or two working with your flow, if you don’t then you might want to skip this functionality until you get that working. The first thing you need to do is find the ARN for your queues. I’m going to be honest, this is not intuitive at all and I wish the Connect team would allow you to retrieve this information via the flow without having to do the following steps.

To get your Queue ARN go to your queues via https://<your instance>.awsapps.com/connect/queues, select a single queue, and notice the bold section of the url https://<your instance>.awsapps.com/connect/queues/edit?id=arn:aws:connect:us-east-1:64:instance/4d92ab25-8XXX-4bXX-aXXX-XXXXXXX/queue/XXXXXX-2022-XXXX-a275-xxXXXXxxxXXX That’s your queue’s ARN.

– Set an attribute (e.g. Queue) with your ARN.
– Set your queue name to the attribute you just set.

image

– Save your attribute to your DB.

Next time your customer calls, you can retrieve the last queue they went through and give them the option to go to that queue again, hopefully saving your customer some frustration.

image

~david

Amazon Connect Flow Designer Review

I’m trying to capture my initial notes and reactions to Amazon’s contact center offering. In this blog I’m going to focus solely on their flow designer tool. I’ll provide a brief overview of the tool, some best practices I’ve come up with, as well as some things I wish were different. Remember that I come from the Cisco contact center world, so my view is slightly tainted and what I’ve lived and loved has been the Cisco tools.

Amazon Connect provides a web based call flow tool called flow designer. Those of you familiar with ICM Script Editor and CVP Studio will feel at home. Below is one of the flows I’ve created. Note that the designer allows you to snap steps or what Amazon calls “action blocks” into the grid for cleaner looking flows.

Flow designer with flow

In the left hand side of the designer is your “palette” you can find an explanation of each action block here.

Flow Designer Palete

Building your first flow is truly easy and requires very little technical knowledge. The Play prompt block allows both playing audio files as well as text-to-speech (TTS) in a variety of voices and languages. Setting a queue and building a queue is just as easy.

Now a few items which bother me about contact flow as well as some best practices I’ve found. I touched on a few of these in my earlier post.

  • DO NOT hit the back button or navigate away from the flow designer without saving. There is no auto save!
  • You can’t copy and paste a block. You must build a block from scratch every time. I keep a file with Lambda names and variables I’m using for easy copy/paste.
  • You can’t have the block properties of multiple blocks open at the same time.
  • There is no move of multiple blocks. You must move each one at a time.
  • Build your flow strictly with TTS and only add audio files once you’re happy with the product. If you’re using dynamic speech you’ll have a better sense of what the audio files need to say.
  • Plan your error conditions flow early. This is important when handling error/default/timeout from menus, but applies across multiple different types of blocks. You should come up with a few standard error correction flows and branch out all your error conditions appropriately based on where you are in the flow. This will also avoid a spider web flow.
  • No easy way to get from flow to flow. Once you’re in the designer, you click the back button in your browser or go through the main menu to jump to another flow. Ideally Amazon provides a drop down in the designer to switch between flows to save a few clicks.
  • No infinite scroll. Specifically you can’t scroll and build your flow up or to the left. This means that you should think of starting your flows somewhere in the middle of your screen to give you a bit of real estate for last minutes changes/branches. When you create a new flow Amazon “conveniently” starts you off like in the left image, but you should move your fist block off to the right a bit, like right image. Also make sure you immediately enable “Snap to grid” for cleaner looking flows.

image image

  • You are able to move blocks behind the unmovable left hand margin. The only reason I discovered this is because I wanted to add a log block and didn’t want to pile up blocks on top of each other.

Flow designer with nodes hidden.

  • You need to be aware of where your lines are going and try to avoid overlap and tight spaces, specially when using the Get customer input block. Trying to modify a line in the middle of the block can be difficult and will require for you to delete other lines to get to the line you want to delete or modify.

image

  • DO NOT hit the back button or navigate away from the flow designer without saving. There is no auto save! Yes, it’s a repeat, happened to me multiple times.
  • When saving a flow or publishing a flow you get the same confirmation. It would be nice to be reminded what was the last action you took for those of us who are jumping from screen to screen.

Flow designer save message.

~david