Hands On

Building Location-Aware Chatbots with Amazon Lex and HERE Location Services

By Nic Raboy | 27 June 2019

You might remember that I was recently on a Twitch live-stream with Michael Palermo talking about AWS Lex and how you can use it with HERE. While we do have a workshop on the subject as well, I thought I could add my own spin on the subject in an effort to make the process even easier for new developers to the material.

In this tutorial we’re going to see how to use Amazon Lex with HERE Location Services (HLS) to build a chatbot. This particular tutorial will depend on JavaScript knowledge since we’ll be using Node.js with AWS Lambda.

Creating the AWS Lex Sample Utterances, Intents, and Slot Variables

Before we start adding location logic or any logic for that matter, we need to configure our Lex chatbot. To do this we’ll need to design each of our intents, provide sample phrase data or sample utterance data and then provide any variables that we want the user to be able to use.

Go to the Lex dashboard in AWS and create a new custom chatbot. After creating the chatbot, you’ll be given the opportunity to create intents. An intent behaves like a route to do logic. Think of each intent as a different request for functionality.

We’re going to create two intents for our project, an AboutIntent and a PositionIntent.

Starting with the AboutIntent, we need to add some sample utterance data.


Sample utterances are possible phrases that can be used to access the intent. Think of everything your user might ask in an attempt to get information. I added the following:

give me information about this bot
who made this bot

I’ve added two, but you should probably add significantly more. The more the better, not because you’re guessing what the user might ask, but Lex actually gets smarter and can start making assumptions based on the data you’ve taught it.

Now let’s look at our PositionIntent which will eventually be connected to HERE.


This intent is a little different because we want the user to be able to provide data. These are known as slot variables and they aren’t difficult to configure.

Let’s assume we want the user to provide latitude and longitude information with the expectation they will receive an address in return. The user might ask the following:

what is the address at latitude 37 and longitude -121
where is longitude -121 and latitude 37

Of course we’re going to have a lot more potential phrases, but you can get the idea. However, the user likely won’t ask for the same position that I’ve requested. We can replace that data with our slot variables like so:

what is the address at latitude {Latitude} and longitude {Longitude}
where is longitude {Longitude} and latitude {Latitude}

We can then define those slots further down on the screen as AMAZON.NUMBER types. This tells Amazon to expect dynamic data that are numerics, so that way it can be processed correctly.

Developing the Response Logic with Node.js and AWS Lambda

When working with Lex, there are strict expectations on what the request objects and response objects look like. To be more clear, Lex will provide a request object to our backend and our backend must respond with an object.

To get an idea of our request object, Lex might give us something that looks like the following:

    "currentIntent": {
        "name": "ProfileIntent",
        "slots": {
            "Address": "California",
            "FullName": "Nic Raboy"
        "confirmationStatus": ""
    "bot": {
        "name": "nraboys_bot",
        "alias": "nics_bot",
        "version": "1.0.0"
    "userId": "nraboy",
    "inputTranscript": "",
    "invocationSource": "DialogCodeHook",
    "outputDialogMode": "Text",
    "messageVersion": "1.0",
    "sessionAttributes": {},
    "requestAttributes": {}

The plan is to work with this object directly with JavaScript. Only the currentIntent information is particularly important to us in this example. Based on this object, you can see Lex has already done the heavy lifting for us by determining the intent to use and the slot variables to use with it.

When it comes to a response, Lex expects the following format:

    sessionAttributes: {},
    dialogAction: {
        type: "Close",
        fulfillmentState: "Fulfilled",
        message: {
            "contentType": "PlainText",
            "content": "Hello World!"

Of course there are changes that can be made, but for this example the above response object is more than suitable. Knowing what we know now, we can build a simple Lambda function.

On your computer, create a new directory and execute the following from inside:

npm init -y
touch index.js

If you don’t have access to the touch command, create the index.js file manually. With the project created, we can add our code. Open the project’s index.js file and include the following:

const dispatcher = async (event) => {
    let response = {
        sessionAttributes: event.sessionAttributes,
        dialogAction: {
            type: "Close",
            fulfillmentState: "",
            message: {
                "contentType": "PlainText",
                "content": ""
    switch(event.currentIntent.name) {
        case "AboutIntent":
            response.dialogAction.fulfillmentState = "Fulfilled";
            response.dialogAction.message.content = "Created by Nic Raboy at HERE";
            response.dialogAction.fulfillmentState = "Failed";
            response.dialogAction.message.content = "I don't know what you're asking...";
    return response;

exports.handler = (event, context) => {
    return dispatcher(event);

Notice in the above example that we have one intent which is accessible through our dispatcher function. If we are provided any other intent, we’ll visit our fallback statement.

Remember, our goal is to process that Lex object in the request.

Including Location Logic with HERE Location Services

Now that we have our basic Lambda function created, we can start making requests to HERE. For this example, we’re assuming the user has provided latitude and longitude information in a chat. The user provides this information because they want address information which can be determined through the reverse geocoder.

This is not a terribly exciting example, but it works well and can easily be changed to be more useful.

Before we can start using the HERE, we need to install a package for making HTTP requests. From the command line, execute the following:

npm install request --save
npm install request-promise --save

If we go back into our index.js file, we can import the package that we just downloaded and then make use of it within our dispatcher method:

const Request = require("request-promise");

const dispatcher = async (event) => {
    // ...
    switch(event.currentIntent.name) {
        // ...
        case "PositionIntent":
            let slots = event.currentIntent.slots;
            let result = await Request(
                    qs: {
                        "app_id": "APP_ID_HERE",
                        "app_code": "APP_CODE_HERE",
                        "mode": "retrieveAddresses",
                        "prox": slots.Latitude + "," + slots.Longitude
                    json: true
            response.dialogAction.fulfillmentState = "Fulfilled";
            response.dialogAction.message.content = result.Response.View[0].Result[0].Location.Address.Label;
            // ...
    return response;

In the above code, I commented out the things we had added prior. Instead, take focus of the PositionIntent that we’ve added to the dispatcher function. In this statement, we extract the Latitude and Longitude slot that we defined in Lex. Using this information, along with the HERE token information, we can make a request to the HERE Reverse Geocoder API.

The HTTP request is an asynchronous operation, which we can use with an async / await in JavaScript. When complete, we can add the Label from the response and then return our object to Lex.

Packaging the AWS Lambda Function for Deployment

Because we’re using an external package and not just a single vanilla JavaScript file, we need to package our project before submitting to Lambda. To do this we can execute the following:

zip -r handler.zip *

The above command will recursively ZIP our entire working directory, including our source code and node_modules directory. If you don’t have a zip command line function, do it manually.

If your project includes native dependencies designed specifically for macOS, Windows, or Linux, you’ll need to include more steps in your packaging process. You can learn more about this in my previous tutorial titled, Deploying Native Node.js Dependencies on AWS Lambda. This project does not currently use native dependencies, so we are fine.

At this point in time the Lambda project can be added to AWS Lambda.

Connecting Lex Intents to the AWS Lambda Function

While we do have Lex configured and our Lambda function developed, the two are not yet communicating with each other. To do this you need to point each intent to Lambda from within the Lex dashboard.


Within each of the intents, choose AWS Lambda function as the Fulfillment option. When you do this, you can save your intent and then rebuild your project. Instead of responding with information about the request, the response from Lambda will be used.


You just saw how to build a chatbot with Amazon Lex that has location functionality through HERE Location Services. If you haven’t already seen the episode of AWS Connections featuring Lex, I encourage you to watch it. For further learning, you could also check out another getting started tutorial I wrote on the subject.