Building a Serverless IVR

“The critical ingredient is getting off your butt and doing something. It’s as simple as that. A lot of people have ideas, but there are few who decide to do something about them now. Not tomorrow. Not next week. But today. The true entrepreneur is a doer, not a dreamer.”

Vineet Bajpai

Those who know me know that I get obsessed about things and over the past few months this blog has very clearly demonstrated that. A few weeks ago, I couldn’t get enough of artificial intelligence. I wrote a number of applications that used AWS Rekognition to analyze images and videos for classification and text transcription. I learned quite a bit from my research and wanted to share my work with the world as well as tuck it aside for future reference. As odd as it might sound, I regularly refer back to my blog articles to remember concepts that have slipped from my mind. Sadly, my brain can only hold so much before something has to be tossed overboard.

My current obsession is serverless computing. That led to four articles about using AWS Lambda, Google Cloud Functions, and Azure Functions to build microservices that I ultimately integrated into Avaya Virtual Agent. I am not quite finished exploring that obsession and this week I used Lambda and Avaya CPaaS to build a speech recognition IVR (Integrated Voice Response) application. The Lambda function ran the IVR workflow logic while CPaaS provided the voice and speech to text functionality.

I’ve written similar applications, but was forced to run the workflow logic on my Linux server as an Express node.js application. That method required me to manage scalability, redundancy, security, and a host of other platform issues. By moving the logic to Lambda, all those headaches go away. I worry about the IVR parts of the application and Lambda does the rest.

Before sharing the source code, here are a few things I need to note.

I use Avaya CPaaS inboundXML to invoke my Lambda function. CPaaS informs my application when a call arrives, an my application tells CPaaS what to do with that call. The telling it what to do part involve instructing CPaaS to collect speech from the caller and report back to me what was said. I then tell CPaaS to repeat those words back to the caller before asking it for the next spoken words.

I need to instruct CPaaS what to invoke upon the arrival of a new call and the subsequent sending of new instructions. I do that in two place — inside CPaaS (which I will show later) and inside the code. Inside the code I set the REQUEST_URL constant to the URL of my Lambda function. This is passed to CPaaS each time I sent it new instructions. The result is that after CPaaS has executed my instructions it comes back to my Lambda function for the follow-up steps.

Lambda does some funny stuff with the data CPaaS sends. You will see in the code where I convert event.body from base64 encoding to ASCII text.

After that, it’s pretty straightforward. I used Gather and Say inboundXML elements for most of the application. The one exception is when the caller says “Goodbye.” In that case, I bid the caller farewell and release the call.

Here is the source code.

const cpaas = require('@avaya/cpaas'); //Avaya cloud
var enums = cpaas.enums;
var ix = cpaas.inboundXml;

const REQUEST_URL = "Your Lambda function URL goes here";

exports.handler = async (event) => {
	var speechResult = null;
	const response = {
		statusCode: 200,
		headers: {
			'Content-Type': 'text/xml'
		},
		body: null,
	}

	// CPaaS parameters arrive encoded in base64
	let buff = Buffer.from(event.body, 'base64');
	let body = buff.toString('ascii');

	// Separate the query parameters and look for a SpeechResult value
	let queryData = body.split("&");
	for (var i in queryData) {
		if (queryData[i].includes("SpeechResult")) {
			if (!queryData[i].includes("SpeechResultError")) {
				let substr = queryData[i].split("=");
				speechResult = substr[1].replaceAll("+", " ");
			}
		}
	}

	var hangup = false;
	var prompt = "Say something";
	if (speechResult != null) {
		prompt = "I heard you say " + speechResult;
		if (speechResult == "goodbye") {
			hangup = true;
			prompt = "Thank you for calling.  Goodbye.";
		}
	}
	var xmlResponse = await sendResponseToCPaaS(prompt, hangup);
	response.body = xmlResponse;
	return response;
};

async function sendResponseToCPaaS(prompt, release) {
	var xmlDefinition = generateXMLSpeech(REQUEST_URL, prompt, release);
	var serverResponse = await buildCPaaSResponse(xmlDefinition);
	return serverResponse;
}

function generateXMLSpeech(request_url, prompt, hangup) {
	var xml_content = [];
	if (hangup == false) {
		var gather = ix.gather({
			action: request_url,
			method: "POST",
			input: "speech",
			timeout: "20",
			content: [
				ix.say({
					language: enums.Language.EN,
					text: prompt,
					voice: enums.Voice.FEMALE
				})
			]
		});
		xml_content.push(gather);
	} else {
		var say = ix.say({
			language: enums.Language.EN,
			text: prompt,
			voice: enums.Voice.FEMALE
		});
		var hangup = ix.hangup();
		xml_content.push(say);
		xml_content.push(hangup);
	}
	var xmlDefinition = ix.response({
		content: xml_content
	});
	return xmlDefinition;
}

async function buildCPaaSResponse(xmlDefinition) {
	var result = await ix.build(xmlDefinition).then(function(xml) {
		return xml;
	}).catch(function(err) {
		console.log('The generated XML is not valid!', err);
	});
	return result;
}

Once I have created my Lambda function, I use the function URL in the code above and in Avaya CPaaS.

In CPaaS, I attach the URL to one of my telephone numbers as a Post Weblink in the Voice Configuration section. This instructs CPaaS to invoke my Lambda function when a new call arrives on the number.

Mischief Managed

That’s all she wrote. While I did this with AWS, I could have just as easily written my IVR application using Google or Azure. The code would have required a few tweaks, but not more than a line or two would need to be changed.

The beauty of serverless computing is that I can write powerful applications without having to buy/rent and manage my own server. The IVR in this article is very simple, but I wrote a much more sophisticated version that I deemed too complicated for this explanatory article.

Happy programming!

2 comments

  1. Sharma · · Reply

    Amazing to see the use of Lambda usage in Avaya Systems.

    1. Not just Lambda. Google and Azure are there, too.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: