10
Jarvis, I mean Alexa, order me a pizza

This chapter covers

  • Creating a serverless SMS chatbot
  • The challenges of having different serverless chatbots
  • Creating an Alexa skill using Claudia and AWS Lambda

Chatbots are useful for businesses because they significantly reduce the need for customer support while enabling your customers to interact with your applications in a convenient and interesting way. Serverless chatbots improve the equation even more, because you can support great user fluctuation, with many request peaks, without requiring server configuration. The only limitation of chatbots is that they’re tied to their respective messaging platforms, and there are many, which vary greatly from market to market. For example, Facebook has been available for more than 10 years, but a significant percentage of people still don’t use Facebook at all, and they may be your customers. How should you approach that?

On the other hand, human-computer interaction is constantly evolving, and recently we’ve witnessed the rise of voice assistants such as Apple’s Siri, Amazon’s Alexa, Google’s Home, Microsoft’s Cortana, and many others. Instead of writing to your chatbot, now you can just talk to it. And that technology is embraced completely by the other, opposing end of your customer base: the tech-savvy pioneers, who easily adopt and promote new technologies. To target these two types of consumers, writing just one chatbot isn’t enough. This chapter shows you how to handle both ends of the spectrum by creating both an SMS (Short Message Service) chatbot and an Amazon Alexa skill as serverless services using Claudia.js.

10.1 Can’t talk right now: sending an SMS with Twilio

Aunt Maria’s business has begun to flourish again, and that’s great news! Pierre, her mobile application developer, has reported a big number of app downloads, and Julia’s high school friends have spread the message about your Facebook chatbot, resulting in hundreds of orders coming in. Naturally, Aunt Maria is happy because you’ve helped get her business back on its feet, so she’s asked you to come for a free dinner to meet with her and your Uncle Frank.

Uncle Frank is Maria’s brother. He’s an old, short, bulky guy, usually wearing his dark shirt, sleeves up to his elbows. He owns a well-known bar just down the street from the pizzeria. He loves to eat and frequently calls Maria to order pizzas for himself or his customers. But he’s an old-school guy who doesn’t bother that much with technology.

You go to the pizzeria and meet with Aunt Maria and Uncle Frank. They’re happy, and Uncle Frank congratulates you. He’s heard about your success, especially the case of your Facebook Messenger chatbot. But as the meal proceeds, you find that “there’s no such thing as a free dinner.” Aunt Maria and Uncle Frank explain that even though they think you’ve done a great job with the younger customers, you may not be reaching a significant portion of people, such as Uncle Frank’s customers and friends, who are part of an older generation and don’t have a Facebook account. Some don’t have any social media accounts at all. Aunt Maria’s pizzeria has become incredibly busy and at the moment she can’t hire new workers just to answer the phone, so they ask if you can build an SMS chatbot. All her customers own mobile phones and know how to send text messages, so this might be a good solution. But where should you start?

Many cloud communications platforms are available, but Twilio is one of the best known and most widely used. Twilio enables customers to make and receive phone calls and text messages using its APIs.

Luckily, Claudia Bot Builder supports Twilio SMS chatbots, too. You can set up a Twilio chatbot as easily as the Facebook chatbot. To start smoothly, you’ll first create a greeting SMS chatbot for Aunt Maria’s pizzeria to get a grasp of the basic concepts. Then you’ll continue with the complete pizza listing and ordering process.

First, create a separate project folder named sms-chatbot. Navigate to the folder, and inside it create a file called sms-bot.js.

Because initially you are just writing a greeting bot, it will return only a single line of text stating “Hello from Aunt Maria’s pizzeria!” First, you’ll import Claudia Bot Builder to help you out with chatbot creation. Then you’ll create your chatbot api, which will use the Claudia Bot Builder callback function to process messages. Within the function you’ll return a single-line string, Hello from Aunt Maria’s pizzeria!. After the function you’ll need to specify an object containing platforms that represents an array of platforms you want your chatbot to support. Because you only want to support Twilio, you’ll put twilio in the array. Your sms-bot.js file should look like the following listing.

Listing 10.1 A simple SMS chatbot that says hello

'use strict'
const botBuilder = require('claudia-bot-builder')       ①   

const api = botBuilder(() => {     ②  
  return `Hello from Aunt Maria's pizzeria!`    ③  
}, { platforms: ['twilio'] })    ④  

module.exports = api    ⑤  

This code is quite simple, but before seeing it in action, you’ll need to create a Twilio account and provide a phone number from which it can send and receive SMS messages. After that, you’ll set up the Programmable SMS service on your Twilio dashboard and assign it this phone number. For instructions about creating and setting up your Twilio account, see appendix B.

After that setup, you’ll need to use Claudia to create your new AWS Lambda and deploy your SMS chatbot to it. To do so, you run the following command: claudia create --region <your-region> --api-module sms-bot.

As you probably remember, this command will return your newly created chatbot’s URL. It should end with the /twilio suffix. Copy that URL and open your Twilio Programmable SMS service page, and paste it in the Inbound URL box. Don’t forget to save your new Programmable SMS service configuration.

The last step remaining before trying your SMS bot is to run the command claudia update --configure-twilio-sms-bot. This command configures Twilio as your chatbot’s platform. That’s it.

Now try sending a “Hello” message to your Twilio phone number.

10.1.1 An SMS pizza list

In the previous two chapters, your Facebook chatbot first returned a greeting message to customers. When the customer asked for the menu, the chatbot displayed a horizontal pizza list. The customer clicked a pizza item, and the ordering process started.

This seems to be a good reasoning model for other chatbot platforms, too, but SMS uses a different communication protocol that can’t send images. You’ll have to send your pizza list as text, with an explicitly specified text reply for ordering each pizza. For example, the specified reply to order a funghi pizza would be FUNGHI.

SMS sometimes also incurs hidden costs. Although in many countries it’s almost free, in others it’s still a bit expensive. If you’re expecting thousands of users, the message cost can rise quickly. Therefore, try to minimize the number of sent SMS chatbot messages, while taking care not to break the message flow.

For your pizzeria SMS chatbot, minimizing messages means that you’ll have to join certain steps. For example, at the start of the conversation, you should join the greeting and the pizza menu in a single message. It doesn’t break the flow, and it’s quite convenient for the customer. You need only to go through the pizza list and concatenate the pizza names, with their specified replies, into a single multiline string and send it back to the customers.

In the previous chapters you learned that you should separate your handlers for better application organization, so you’ll extract the pizza menu greeting to a handlers folder from the start. You’ll need to create a folder named handlers inside your project root folder, and inside it create a file named pizza-menu.js. Inside this file, you’ll first import the static pizza list from the pizzas.json file into a pizzas variable. Then you’ll create your pizzaMenu function, and within it a greeting variable with Hello from Aunt Maria’s pizzeria! Would you like to order a pizza? This is our menu: as its value. Then you’ll go through each of the loaded pizzas and concatenate to the greeting variable each pizza name with its short code on a new line. Finally, you should return the greeting variable as the result of your pizzaMenu function and export the function as your module. The full code is shown in the following listing.

Listing 10.2 The pizza menu greeting

'use strict'
const pizzas = require('../data/pizzas.json')    ①  

function pizzaMenu() {    ②  
  let greeting = `Hello from Aunt Maria's pizzeria!
  Would you like to order a pizza?
  This is our menu:`

  pizzas.forEach(pizza => {
    greeting += `
 - ${pizza.name} to order reply with ${pizza.shortCode}`    ③  
  })    ④  

  return greeting
}

module.exports = pizzaMenu    ⑤  

This handler always returns the pizza list when invoked. The pizza list shows the pizzas' names and short codes. To order, the user sends a text command instead of tapping a button, because interactions with an SMS chatbot are limited to text messages.

You then need to change your sms-bot.js file to invoke the pizzaMenu handler whenever a customer sends your chatbot an SMS. Because you don’t have any other commands at this point, you can return just the imported pizzaMenu handler. It should look similar to the following listing.

Listing 10.3 The SMS chatbot entry

'use strict'
const botBuilder = require('claudia-bot-builder')    ①  
const pizzaMenu = require('./handlers/pizza-menu')    ②  

const api = botBuilder((message, originalApiRequest) => {
  return [
    pizzaMenu()
  ]    ③  
}, { platforms: ['twilio'] })    ④  

module.exports = api

Now, redeploy the project with the claudia update command. If you try it out, you should receive the greeting from the chatbot, along with the list of pizzas and their short codes.

10.1.2 Ordering a pizza

At this point, if a customer were to send your SMS chatbot a message, it would reply with a greeting and the pizza list. But if the customer were to send one of the pizza short codes as a response, the SMS chatbot would again reply with the pizza menu. In the following section, you’ll enable your SMS chatbot to recognize the chosen pizza’s short code and process the pizza order.

To start, you’ll first need to check if the received message contains a pizza shortCode. You’ll have to load the available pizzas inside your sms-bot.js file and check the message contents against each shortCode. If it finds a shortCode, it should ask the customer for the delivery address.

Your sms-bot.js file should now look like the following listing.

Listing 10.4 Recognizing a pizza order

'use strict'
const botBuilder = require('claudia-bot-builder')
const pizzas = require('./data/pizzas.json')    ①  
const pizzaMenu = require('./handlers/pizza-menu'),
    orderPizza = require('./handlers/order-pizza')    ②  


const api = botBuilder((message, originalApiRequest) => {

  let chosenPizza
  pizzas.forEach(pizza => {
    if (message.indexOf(pizza.shortCode) != -1) {    ③  
      chosenPizza = pizza
    }
  })

  if (chosenPizza) {
    return orderPizza(chosenPizza, message.sender)
  }    ④  

  return [
    pizzaMenu()
  ]
}, { platforms: ['twilio'] })

module.exports = api

This code completes the first step, checking if the customer sent a pizza’s short code and then passing the pizza and the sender (the customer) to the order pizza handler. The remaining step is writing the handler. The order-pizza.js handler should receive the chosen pizza object and the sender, and then store a new pizza order to the pizza-orders database table. For the new pizza order’s orderId, you’ll make use of the uuid module, and for the pizza you’ll use the chosen pizza’s ID. You’ll set the orderStatus to in-progress, because you don’t want the order to be delivered before you know the delivery address. Also, for platform you’ll specify twilio-sms-chatbot, because if you’re using the same database with multiple chatbots you want to have a way to differentiate the orders from each chatbot. Finally, you want to store the sender as the user attribute to be able to know which customer ordered it. The code for the order-pizza.js handler is shown in the following listing.

Listing 10.5 The order pizza handler

'use strict'
const AWS = require('aws-sdk')    ①  
const docClient = new AWS.DynamoDB.DocumentClient()    ②  
const uuid = require('uuid/v4')    ③  

function orderPizza(pizza, sender) {
  return docClient.put({    ④  
    TableName: 'pizza-orders',
    Item: {
      orderId: uuid(),    ⑤  
      pizza: pizza.id,
      orderStatus: 'in-progress',    ⑥  
      platform: 'twilio-sms-chatbot',    ⑦  
      user: sender    ⑧  
    }
  }).promise()
    .then((res) => {
      return 'Where do you want your pizza to be delivered? You can write your address.'    ⑨  
    })
    .catch((err) => {
      console.log(err)

      return [    ⑩  
        'Oh! Something went wrong. Can you please try again?'
      ]
    })
}

module.exports = orderPizza    ⑪  

message.sender in this case represents the phone number of the customer that requested the pizza. The missing piece of the puzzle at this point is the reply with the customer’s address.

Handling SMS messages is not an easy task. SMS messages are plain text, so capturing the customer’s address input is not provided as an additional option. With these limitations, you really have to think a lot about your implementation.

Currently, an order can only reach the in-progress status. Until you know the customer’s address, you can’t send the order to the delivery company. You’ll need to obtain the address and save it—but at the moment, if the message does not contain a pizza’s shortCode, your SMS chatbot will always reply with the greeting and pizza menu. You’ll need to override that behavior and somehow manage to handle the address input properly.

Luckily, there is a solution. You are already storing the sender’s phone number with the in-progress order, so you can first check if there is an in-progress order in your database with a matching phone number. If that’s the case and there is no saved address, you can then save the sent message as the address. Figure 10.1 shows the message parsing process.

figure-10.1.eps

Figure 10.1 The serverless SMS chatbot message parsing process

Understanding the process is the most important part, but you also need to learn how to implement it.

First, you need to check for an in-progress order. It’s best to have a separate handler file, check-order-progress.js, inside the handlers folder. Inside this file, implement the logic for scanning your DynamoDB table for an order that belongs to the sender and has an in-progress status. Because the DynamoDB scan command always returns an array of found items, you’ll need to check if the scan result has any Items. If yes, return the first one. If not, return an undefined value, because nothing was found. Your check-order-progress.js file should look like the following listing.

Listing 10.6 The check order progress handler

'use strict'
const AWS = require('aws-sdk')    ①  
const docClient = new AWS.DynamoDB.DocumentClient()    ②  

function checkProgressOrder(sender) {

  return docClient.scan({    ③  
      ExpressionAttributeValues: {':user': sender, ':status': 'in-progress'},    ④  
      FilterExpression: 'user = :user and orderStatus = :status',    ⑤  
      Limit: 1,    ⑥  
      TableName: 'pizza-orders'    ⑦  
    }).promise()
    .then((result) => {
      if (result.Items && result.Items.length > 0) {
        return result.Items[0]
      } else {
        return undefined
      }    ⑧  
    })
    .catch((err) => {
      console.log(err)
       return [
         'Oh! Something went wrong. Can you please try again?'    ⑨  
         ]
        })
    });
}

module.exports = checkProgressOrder    ⑩  

Now you need to update the main sms-bot.js file to check if there is an in-progress order status and, if there is, save the location. If there isn’t, show the pizza menu. To start, you’ll first need to import the save-address.js and check-order-progress.js handlers. Then you’ll use them to write the order status check. Your sms-bot.js file should look like the following listing.

Listing 10.7 The updated sms-bot.js file

'use strict'
const botBuilder = require('claudia-bot-builder')
const pizzas = require('./data/pizzas.json')
const pizzaMenu = require('./handlers/pizza-menu'),
  orderPizza = require('./handlers/order-pizza'),
  checkOrderProgress = require('./handlers/check-order-progress'),    ①  
  saveAddress = require('./handlers/save-address')    ②  

const api = botBuilder((message, originalApiRequest) => {

  let chosenPizza
  pizzas.forEach(pizza => {
    if (message.indexOf(pizza.shortCode) != -1) {
      chosenPizza = pizza
    }
  })

  if (chosenPizza) {
    return orderPizza(chosenPizza, message.sender)
  }

  return checkOrderProgress(message.sender)    ③  
    .then(orderInProgress => {
      if (orderInProgress) {
        return saveAddress(orderInProgress, message)    ④  
      } else {
        return pizzaMenu()    ⑤  
      }
    })
}, { platforms: ['twilio'] })

module.exports = api

You’re now missing only the save-address.js handler. Create the save-address.js file in the handlers folder, open it, and write the code to update an order in your DynamoDB table using the provided order ID as its key. You should also update the address and change the status from in-progress to pending. The handler is shown in the following listing.

Listing 10.8 The save-address handler

'use strict'
const AWS = require('aws-sdk')
const docClient = new AWS.DynamoDB.DocumentClient()
function saveAddress(order, message) {
  return docClient.put({
    TableName: 'pizza-orders',
    Key: {
          orderId: order.id    ①  
    },
    UpdateExpression: 'set orderStatus = :o, address = :a',    ②  
    ExpressionAttributeValues: {
      ':n': 'pending',
      ':a': message.text    ③  
    },
      ReturnValues: 'UPDATED_NEW'    ④  
    }).promise()
  });
}

module.exports = saveAddress    ⑤  

Now run the claudia update command and send a message to your Twilio phone number to try it out. That’s it! You’ve managed to build your first serverless SMS chatbot with Claudia.js and Twilio.

10.2 Hey Alexa!

Your SMS chatbot did its thing, and now even more people are ordering Aunt Maria’s pizzas! Her pizzeria is getting crowded even on Mondays, so she’s thinking of opening a second place in another neighborhood. Uncle Frank is happy, too, though he’s a bit upset because he played with the SMS chatbot so much that his phone bill went sky-high.

Everything seems good—then your cousin Julia comes to you with a present, smiling smugly.

She gives you an Amazon Echo.

Julia explains that the she got it last Christmas, but was bored with it until she realized that you can use it to order pizzas. She wants Aunt Maria to dominate the market and surpass even Chess’s pizzeria (probably because they don’t give her free pizzas like Aunt Maria, but you go along). Julia thinks that having pizza-ordering voice commands for Echo before Chess’s pizzeria does will help on the marketing side and win Aunt Maria many customers. It’s not a bad idea, so you decide to help her out.

But what is Amazon Echo, and how do you use it? Julia shows you that you need to call the device “Alexa.”

The most interesting and powerful feature of Alexa is its custom skills. Skills are new commands that Alexa can learn, and they can be published to Amazon’s Marketplace. At the time of writing, more than 20,000 custom skills are available in the Marketplace. These skills are analogous to computer applications.

Building a custom skill is quite simple. As shown in figure 10.2, an Alexa-enabled device forwards the audio file to the cloud, where Alexa parses it to a common format with intents and slots and then passes it as JSON to your Lambda function or HTTP webhook. Intents tell your skill what the user is trying to accomplish, and slots are the variables, or dynamic parts, of the given intent. Then your Lambda function or HTTP webhook replies with a JSON file that defines the Alexa voice reply that the user will hear.

Before building your first skill, let’s see how a skill works and how it differs from your Facebook Messenger and Twilio chatbots.

figure-10.2.eps

Figure 10.2 A custom Alexa skill

Anatomy of an Alexa skill

Alexa and other voice assistants operate a bit differently from most of the chatbot platforms. Some notable differences are the following:

  • Instead of just passing the message to your webhook, Alexa has a built-in natural language processing (NLP) engine, and it will pass only a parsed request to your webhook in the JSON format.
  • Alexa conversation is command-based, and unlike most of the chatbot platforms, it doesn’t allow free conversations. Your message must be recognized as one of the predefined commands for Alexa to understand and process it.
  • Voice assistants typically require a wake word or phrase—a sound that tells them to expect a command immediately after.

As shown in figure 10.3, a typical Alexa command consists of the following:

  1. Wake word
  2. Launch phrase
  3. Invocation name
  4. Utterance with optional slots
figure-10.3.eps

Figure 10.3 Alexa skill invocation

Other examples include “Alexa, start Aunt Maria’s Pizzeria” and “Alexa, tell Aunt Maria’s Pizzeria to order a pizza.”

The default wake word is “Alexa,” but it can be customized in the device settings. At the time of writing, available wake words are “Alexa,” “Amazon,” “Echo,” and “Computer.”

The launch phrase tells Alexa to trigger a certain skill. Launch phrases include “ask,” “launch,” “start,” “show,” and many others.

The invocation name is the name of the skill you want to trigger. To build a good skill, choosing a good invocation name is important.

Finally, unless your launch phrase is “start,” you need to tell Alexa what the skill should do. Those instructions are known as utterances. Having static utterances would not give you much flexibility, so Alexa allows you to add some dynamic parts to the instructions; those dynamic parts are called slots.

A user invokes the skill, and Alexa parses it and passes it to your AWS Lambda function or a webhook.

As shown in figure 10.4, once a voice command is processed by Alexa’s NLP, it is converted to a recognized intent. If there are any slots in the invoked command, they are converted to objects that contain a slot name and value. Once a voice command is successfully parsed, Alexa builds a JSON object that contains the request type, intent name, and slot values along with other data, such as session attributes and metadata.

figure-10.4.eps

Figure 10.4 Alexa skill invocation and parsing flow

Alexa can receive a few request types (table 10.1).

Table 10.1 Alexa request types
Request typeDescription
LaunchRequestSent when a skill is triggered with the “start” or “launch” phrases, such as “Alexa, launch Aunt Maria’s Pizzeria”; does not receive custom slots
IntentRequestSent whenever a user message is parsed that contains an intent
SessionEndedRequestSent when a user session ends
AudioPlayer or PlaybackController (prefixes)Triggered when a user uses any of the audio player or playback functions, such as pausing audio or playing the next song

Another important part of the Alexa command flow is the session. Unlike Facebook Messenger, Alexa can save some session data between commands, but you need to keep them in the session explicitly. An Alexa session is a conversation between a user and Alexa. If the session is active, Alexa waits for the user’s next command after it replies. While the session is active, subsequent commands don’t require a wake word, because Alexa expects a reply in the following few seconds.

Before building the Alexa skill, you’ll need to design it. Designing voice assistant skills is not about the UI, of course, but about designing interactions and the intent schema. We cover that next.

10.2.1 Preparing the skill

The design is the most important part of building a skill. Voice assistants are often called “smart assistants,” but in reality they’re still far from HAL 9000 from 2001: A Space Odyssey, and NLP capabilities are still a limiting factor.

Interaction design is beyond the scope of this book, but there are many good resources on the internet. As a good starting point, see Amazon’s official voice design guide at https://developer.amazon.com/designing-for-voice/.

The skill you’ll build in this chapter will be simple. It should do the following:

  1. Allow the user to get the list of the available pizzas.
  2. Allow the user to order the selected pizza.
  3. Ask the user for a delivery address.

The basic flow of the skill you’ll build is shown in figure 10.5.

To build an Alexa skill you need to prepare the following:

  • Intent schema
  • Custom slot types, if they exist
  • List of sample utterances
figure-10.5.eps

Figure 10.5 Aunt Maria’s Pizzeria Alexa skill flow

The intent schema is a JSON object that lists all the intents, or actions, that fulfill a user’s spoken requests. Each intent can have slots, and each slot must have one type. Slot types can be custom or built-in. Amazon offers many built-in slot types, such as names, dates, and addresses. For the full list of built-in slot types, see https://developer.amazon.com/docs/custom-skills/slot-type-reference.html.

In addition to the built-in slot types, you can define custom slot types. A custom slot type consists of a name and a list of available values. The value list is a text file, in which each row represents a single value your custom slot type can have.

The sample utterances list is a set of likely spoken phrases mapped to the intents. It should include as many representative phrases as possible, and Alexa will use them to train its NLP for your skill. Similar to custom slot types, sample utterances are defined as a text file, where each sample utterance is entered on a new line. Each line starts with the intent that text should be parsed into, followed by a space and the sample text, as shown in figure 10.6.

figure-10.6.eps

Figure 10.6 Sample utterances for an Alexa skill

Let’s prepare everything you’ll need, starting with the intent schema. This is a JSON object that contains an intents array with a list of intent objects. Each of the intent objects has an intent key, with the intent name as a value.

Your skill should have OrderPizza and DeliveryAddress intents, both with slots. OrderPizza should have a pizza name as a slot value, and DeliveryAddress should have an address as a slot value. There’s a built-in slot type for addresses, but not for pizza names, so you’ll need to create a custom slot type for these. Call it LIST_OF_PIZZAS—you’ll define it later.

To add slots, both intent objects should have another key, slots, with a slots array as a value. The slots array will in both cases have just one slot object, with the slot name and type as a key-value pair.

For the OrderPizza intent, the slot name should be Pizza and the slot type should be LIST_OF_PIZZAS. For the DeliveryAddress intent, the slot name should be Address, and for the slot type you can use the AMAZON.PostalAddress built-in type, which accepts postal addresses.

Let’s also add one more intent: ListPizzas. This intent doesn’t have any slots, and it should allow a user to ask Alexa for the list of all pizzas. It will trigger the same action as LaunchRequest.

When you’ve finished, your intent schema should look similar to the following listing.

Listing 10.9 Intent schema

{
  "intents": [
      {    ①  
        "intent": "ListPizzas"    ②  
      }, {
        "intent": "OrderPizza",    ③  
        "slots": [
            {    ④  
              "name": "Pizza",
              "type": "LIST_OF_PIZZAS"    ⑤  
            }
        ]
      }, {
        "intent": "DeliveryAddress",    ⑥  
        "slots": [
            {    ⑦  
              "name": "Address",
              "type": "AMAZON.PostalAddress"    ⑧  
            }
        ]
      }
  ]
}

The next step is to define the LIST_OF_PIZZAS slot type. As mentioned previously, a custom slot type definition is a simple text file, where each possible slot value is on a separate line. Your LIST_OF_PIZZAS slot should be a list of all the pizzas, as shown in the following listing.

Listing 10.10 Custom slot type: LIST_OF_PIZZAS

Capricciosa

Quattro Formaggi
Napoletana
Margherita

The final step is to prepare the sample utterances list. This list is, again, a simple text file, where each of the sample utterances is on a separate line.

Each line should start with an intent name, followed by a space and a sample phrase; for example, ListPizzas Pizza menu. Having more than a few sample phrases is better, but Alexa will parse many other similar phrases, too. For example, if you define ListPizzas Pizza menu, Alexa will recognize phrases such as “Show me the pizza menu” or “What’s on the pizza menu?”

Your sample utterances list should look similar to the following listing. You can leave some lines blank for readability.

Listing 10.11 Sample utterances

ListPizzas Pizza menu    ①  
ListPizzas Which pizzas do you have
ListPizzas List all pizzas

OrderPizza {Pizza}    ②  
OrderPizza order {Pizza}
OrderPizza I want {Pizza}
OrderPizza I would like to order {Pizza}

DeliveryAddress {Address}    ③  
DeliveryAddress Deliver it to {Address}
DeliveryAddress address is {Address}

10.2.2 Ordering pizza with Alexa

Now that you have the intent schema and sample utterances list, it’s time to write the code for your Alexa skill.

As mentioned earlier, Alexa can trigger your API or AWS Lambda function. Claudia Bot Builder supports Alexa skills, and you can reuse the same AWS Lambda function you used for your Facebook Messenger or Twilio chatbot. But that adds an API Gateway layer between Alexa and your Lambda function, which increases cost and complexity. (It could also make maintenance easier in some cases, because you can reuse parts of the code.)

Your Alexa skill is simple at the moment, so let’s create a separate AWS Lambda function for it. Creating this additional Lambda function doesn’t have an initial cost—unlike with traditional servers, where you would need to pay for and set up an instance, both the setup cost and the deployment cost are zero.

Another big advantage of using Claudia Bot Builder is that it parses input in a common and simple format; it also removes the boilerplate for the answer. Input for the Alexa skill is automatically parsed into JSON, and for formatting the reply message, you can use the same thing that Claudia Bot Builder is using: alexa-message-builder is published as a separate NPM module, so you can use it without importing the full Claudia Bot Builder.

Create another folder at the same level as your pizza-api and pizza-fb-bot folders. You can name it pizza-alexa-skill to be consistent.

Then enter the folder and initialize an NPM project. Also install alexa-message-builder as a dependency by running the command npm install alexa-message-builder --save. Then create a file named skill.js and open it in your favorite editor.

Your skill.js file should be a standard AWS Lambda file that exports a handler function with event, context, and callback as parameters. It should also require the alexa-message-builder module you just installed.

Because you are not using Claudia Bot Builder, you need to check if the event your handler function receives is a valid Alexa request. You can check if event.request exists and if its type is LaunchRequest, IntentRequest, or SessionEndedRequest. Your skill will not have playback control or audio files, so you don’t need to check event.request for those request types.

If the event is not a valid Alexa request, you need to return an error with the callback function.

Next, you need to add an if…else statement to determine which intent was triggered. You want to check the following states and provide the appropriate responses:

  1. If event.request.type is LaunchRequest, or if it’s IntentRequest with the ListPizzas intent, return the list of pizzas.
  2. If the intent is OrderPizza and the Pizza slot is one of your pizzas, ask for the delivery address.
  3. If the intent is DeliveryAddress and it has an Address slot, tell the user that the order is ready.
  4. Otherwise, tell the user that there was an error.

If request.type is IntentRequest, you can get the intent from event.request.intent.name. If it has slots, they will be in the event.request.intent.slots object. For example, checking if the intent is DeliveryAddress and if the Address slot exists would look like this:

if (
  event.request.type === 'IntentRequest' &&
  event.request.intent.name === 'DeliveryAddress' &&
  event.request.intent.slots.Address.value
) { /* ... */ }

You can create an instance of AlexaMessageBuilder before your if…else statements with the following snippet:

const AlexaMessageBuilder = require('alexa-message-builder')

That snippet allows you to have just one callback after the if…else statements, by adding the following:

callback(null, message)

Then add the messages in each block of your if…else statements. For LaunchRequest and the ListPizzas intent, you should return the list of all pizzas, ask the user to pick one, and keep the session open. Keep in mind that the question you are asking must be clear and simple, so the user knows how to answer it in a way Alexa can process. For example, the code might look like this:

const message = new AlexaMessageBuilder()
  .addText('You can order: Capricciosa, Quattro Formaggi, Napoletana, or Margherita. Which one do you want?')
  .keepSession()
  .get()

The question used here is not perfect, because a user might answer with “the first one,” and Alexa will not be able to understand this reply. It will be good enough to illustrate how the Alexa skill works, however.

Similar to Facebook Messenger templates, AlexaMessageBuilder is a class and its methods return this to allow chaining. To keep the session open, you can use the .keepSession method, and in the end you need to use the .get method to transform the reply to a plain JavaScript object with the format requested by Alexa.

Replying to the OrderPizza intent should be similar. You can reply with “What’s the address where your pizza should be delivered?” and keep the session open. The main difference is that you want to save the selected pizza in the session attributes. You can do that by adding the following code:

.addSessionAttribute('pizza', event.request.intent.slots.Pizza.value)

At this point, your skill.js file should look similar to the following listing.

Listing 10.12 Alexa skill

'use strict'
const AlexaMessageBuilder = require('alexa-message-builder')    ①  

function alexaSkill(event, context, callback) {    ②  
  if (
    !event ||
    !event.request ||
    ['LaunchRequest', 'IntentRequest', 'SessionEndedRequest'].indexOf(event.request.type) < 0
  ) {    ③  
    return callback('Not valid Alexa request')
  }

  const message = new AlexaMessageBuilder()    ④  

  if (
    event.request.type === 'LaunchRequest' ||
    (event.request.type === 'IntentRequest' && event.request.intent.name === 'ListPizzas')
  ) {    ⑤  
    message
      .addText('You can order: Capricciosa, Quattro Formaggi, Napoletana, or Margherita. 
      Which one do you want?')
      .keepSession()    ⑥  
  } else if (
    event.request.type === 'IntentRequest' &&
    event.request.intent.name === 'OrderPizza' &&
    ['Capricciosa', 'Quattro Formaggi', 'Napoletana', 
    'Margherita'].indexOf(event.request.intent.slots.Pizza.value) > -1
  ) {    ⑦  
    const pizza = event.request.intent.slots.Pizza.value

    message
      .addText(`What's the address where your ${pizza} should be delivered?`)
      .addSessionAttribute('pizza', pizza)    ⑧  
      .keepSession()    ⑨  
  } else if (
    event.request.type === 'IntentRequest' &&
    event.request.intent.name === 'DeliveryAddress' &&
    event.request.intent.slots.Address.value
  ) {    ⑩  
    // Save pizza order    ⑪  

    message
      .addText(`Thanks for ordering pizza. Your order has been processed and the pizza 
      should be delivered shortly`)    ⑫  
  } else {    ⑬  
    message
      .addText('Oops, it seems there was a problem, please try again')    ⑬  
  }

  callback(null, message.get())    ⑮  
}

export.handler = alexaSkill    ⑯  

The next step is to deploy the Lambda function using the claudia create command. The two main differences in this case are the following:

  • The supported regions are eu-west-1, us-east-1, and us-west-1.
  • The default latest stage is not allowed, so you need to set some other version name, such as skill.

The command should look similar to the one in the following listing.

Listing 10.13 Deploy the skill with Claudia

claudia create     ①  
  --region eu-west-1     ②  
  --handler skill.handler     ③  
  --version skill    ④  

After your Lambda function is deployed, you’ll need to allow Alexa to trigger it. You can do that with the claudia allow-alexa-skill-trigger command. Don’t forget to provide the version you defined with the claudia create command—in our example this is skill, so you’ll need to run the claudia allow-alexa-skill-trigger --version skill command.

After you upload your Lambda function and allow Alexa to trigger the skill, make sure you follow the setup instructions described in appendix B. If you successfully configured your skill, you can simply say “Alexa, start Aunt Maria’s Pizzeria.”

10.3 Taste it!

Chatbots and voice assistants are fun! Now it’s time for you to try to improve the skill on your own.

10.3.1 Exercise

Your exercise for this chapter is to send a welcome message on Alexa LaunchRequest. The message can be the following: “Welcome to Aunt Maria’s Pizzeria! You can order pizza with this skill. We have Capricciosa, Quattro Formaggi, Napoletana, and Margherita. Which pizza do you want?”

To make this challenge a bit more fun, add a reprompt for both LaunchRequest and the ListPizzas intent. A reprompt is a repeated question that is sent if the session is still open but the user doesn’t answer within a few seconds.

Tips:

10.3.2 Solution

As you can see in the following listing, just a small part of the skill.js file should be changed. You need to separate LaunchRequest and the ListPizzas intent into separate if blocks and use the .addRepromptText method in both of the replies.

Listing 10.14 Modified skill.js file

  if (event.request.type === 'LaunchRequest') {    ①  
    message
      .addText('Welcome to Aunt Maria's Pizzeria! You can order pizza with this skill. 
      We have: Capricciosa, Quattro Formaggi, Napoletana, or Margherita. Which pizza do you want?')    ②  
      .addRepromptText('You can order: Capricciosa, Quattro Formaggi, Napoletana, or Margherita. 
      Which pizza do you want?')    ③  
      .keepSession()
  } else if (event.request.type === 'IntentRequest' && event.request.intent.name === 'ListPizzas') {    ④  
    message
      .addText('You can order: Capricciosa, Quattro Formaggi, Napoletana, or Margherita. 
      Which pizza do you want?')
      .addRepromptText('You can order: Capricciosa, Quattro Formaggi, Napoletana, or Margherita. 
      Which pizza do you want?')    ⑤  
      .keepSession()
  }    ⑥  

After you’ve updated your code, deploy the code with the claudia update command and your skill will be ready for testing.

10.4 End of part 2: special exercise

You’ve now come to the end of part 2 of this book. You’ve learned many things related to serverless applications and chatbots, and it’s time to consolidate that knowledge. The special exercise is to connect both your SMS chatbot and your Alexa skill to the database and the delivery service. Keep in mind that it’s not possible to notify the user about pizza delivery status changes in Alexa.

Summary

  • Claudia Bot Builder offers an easy and quick way to build SMS chatbots using Twilio.
  • Providing a short and clear way for users to reply to your SMS chatbot is important because of its limitations.
  • You can reuse chatbot code for multiple platforms, but sometimes splitting it into more Lambda functions is easier.
  • Claudia Bot Builder supports Alexa skills, but because Alexa can trigger a Lambda function, you can save money and decrease latency if you deploy the skill without an API Gateway.
  • Even though Alexa skills are easy to develop, designing the voice interaction in a bulletproof way is difficult.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset