By Sam Morgan, Head of Education at Makers Academy
Editor’s note: This is part two of our Makers Academy series for Ruby developers. Learn more about this free training on the Alexa Skills Kit and read the first module. Check out the full training course for free online.
Welcome to the second post in our series designed to take you from zero to hero using Alexa with Ruby. In our first module, we:
In this module, we'll handle variable data from users using slots. This module introduces:
This module uses:
We’re going to build a fact-checking mechanism so users can ask for facts about particular numbers. Here are some things users will be able to ask Alexa:
Alexa, ask Number Facts to tell me a trivia fact about 42.
Alexa, ask Number Facts to tell me a math fact about 5.
Users will be able to choose:
Alexa will respond with an interesting fact about that number that is specific to that type of fact.
By the end of this section, we’ll be able to interact with a simplified version of our final feature. Users will be able to ask:
Alexa, ask Number Facts about 42
Alexa will respond with a trivia fact about the number 42. Later, we’ll build on this feature to add trivia responses for all sorts of numbers.
Sign in to the Alexa Developer Portal and set up a new skill. Let’s call it Number Facts with an invocation name of Number Facts.
You could edit the skill you set up in module 1 or practice making a new one.
Set Up a Simple Skill
Let’s start by defining a minimal intent schema with a single intent:
{ "intents": [ { "intent": "NumberFact" } ] }
Let's add a simple utterance to invoke this intent:
NumberFact about forty two
Before we go any further, let's check that our utterance correctly invokes our intent. We will set up an HTTPS-tunnelled Sinatra application just as we did in module 1.
If you have a preferred way of providing an HTTPS endpoint to Alexa, by all means use that. We'll proceed under the assumption you're using ngrok with a locally-running Sinatra application.
Setting Up Sinatra with Tunnelling
In our Sinatra application, we'll set up a single POST route and respond with a minimal response:
require 'sinatra' require 'json' post '/' do { version: "1.0", response: { outputSpeech: { type: "PlainText", # Here's what Alexa will say text: "The number 42 is pretty cool." } } }.to_json end
Great! Once we start the local server, start ngrok, and provide our ngrok HTTPS endpoint to our skill, we can use the Service Simulator to test a basic interaction. Or, if you have an Alexa device registered to your account, you can test the connection using any Alexa-enabled hardware.
Struggling to remember how to set up a local, tunneled environment with Alexa? Check out the guide in the first module.
Connecting Them Together
When we ask:
Alexa, ask Number Facts about 42
Alexa responds with:
The number 42 is pretty cool.
We have a connection! Now, let’s upgrade our Ruby code to give us an interesting fact about the number 42.
Connect to the Number Facts API
In our Ruby code, let’s make a call to the Numbers API to grab an interesting fact about the number 42:
require 'sinatra' require 'json' # Since we're making a call to an external API, # we need Ruby's Net::HTTP library. require 'net/http' post '/' do # First, we encode the Numbers API as a URI number_facts_uri = URI("http://numbersapi.com/42") # Then we make a request to the API number_fact = Net::HTTP.get(number_facts_uri) { version: "1.0", response: { outputSpeech: { type: "PlainText", # We provide the fact we received as # plain text in our response to Amazon. text: number_fact } } }.to_json end
Head to the Service Simulator (or any Alexa-enabled device) and see that you can now make a request to Number Facts for random trivia about the number 42. This is a great start! But what about trivia for other numbers?
In section 1, we weren't able to pass any parameters to our application. As a result, we could only ask Number Facts to tell us about 42. If a user wanted to know about any other number, Alexa would just tell them about 42.
We’d like our users to receive trivia facts for all sorts of numbers. Users should be able to ask:
Alexa, ask Number Facts about {Number}
where {Number} is any number.
Our users should hear a response concerning that number, and that number only.
To do this, we need to provide a parameter to our intent and utterance. In Alexa terminology, we provide parameters as slots. Slots need a name and a type.
Using Built-In Slots to Pass a Number
Let's start from the utterance, or what we want our users to be able to say. To reference a slot in an utterance, we use curly brackets ({}) around the name we will use for that slot.
NumberFact about {Number}
Now let's add that slot into our intent schema with the same name we're referencing in our utterance: number.
{ "intents": [ { "intent": "NumberFact", "slots": [ { "name": "Number", "type": "AMAZON.NUMBER" } ] } ] }
Slots are an array of JSON objects, each with:
We’ve chosen the AMAZON.NUMBER type, which will convert any spoken numbers to digits. So this:
Alexa, ask Number Facts about fifteen
becomes this:
Alexa, ask Number Facts about 15
There are many built-in slot types. You can also define custom slot types (we’ll do this in the next section).
Built-in slot types are handy for capturing user input for common use cases such as numbers, dates, cities, and so on.
Handling the Slot Value in Sinatra
Before we change any Ruby code, let’s observe how adding a slot modifies the incoming request from Amazon’s Service Simulator. We can log the incoming request in our Sinatra application using request.body.read within our post '/' route.
Using the Service Simulator, let's prompt Amazon to send a request by using the phrase:
Alexa, ask Number Facts about 4
In our Sinatra application, notice the slot value passed in the request:
"intent": { "name": "NumberFact", "slots": [ "Number": { "name": "Number", "value": "4" } } ] }
At the moment, we're not doing anything other than logging the value for this slot; we're always returning information about the number 42. In our Sinatra application, let’s grab the slot value out of Amazon’s request and pass that to the Numbers API:
require 'sinatra' require 'json' require 'net/http' post '/' do # Grab the slot value from the incoming request number = JSON.parse(request.body.read)["request"]["intent"]["slots"]["Number"]["value"] # Pass that number to the numbers api number_facts_uri = URI("http://numbersapi.com/#{ number }") number_fact = Net::HTTP.get(number_facts_uri) { version: "1.0", response: { outputSpeech: { type: "PlainText", # And respond with the new fact text: number_fact } } }.to_json end
Testing in the Service Simulator shows users can now provide an arbitrary number to our Number Facts skill and receive trivia about that number. Great!
Let’s make one final upgrade: asking for facts of a specific type.
So far, users can ask Alexa:
Alexa, ask Number Facts about {Number}
where {Number} is any number. Users will hear an interesting fact about any given number.
However, the Numbers API can provide two different kinds of facts: trivia facts and math facts. We’d like users to be able to ask:
Alexa, ask Number Facts to tell me a {FactType} fact about {Number}
{FactType} is either a “trivia” fact or a “math” fact.
Using a Custom Slot to Pass in a Fact Type
Because we’re passing another piece of variable information to our intent, we’ll need to define another slot.
Slots do not restrict user input to certain values. Instead, they guide interpretation of the user's words toward those terms.
However, where the built-in AMAZON.NUMBER slot restricted us to just numbers, there are no built-in slots that will restrict us to the words “trivia” or “math.” We have to make our own.
Let's head to the interaction model pane in the Alexa Developer Console and add a new custom slot type. We'll call this custom slot type FACT_TYPE. There are two possible values for this custom slot type: trivia and math. They must be separated by a newline, like so:
trivia math
These values act as training data for Alexa's voice recognition. They don't restrict users to just the given words; users can say different words in addition to these two. For instance, if a user said, "Alexa, ask Number Facts to tell me a bicycle fact about 42," the word “bicycle” would be sent as part of the request.
Now that we've defined our custom slot type, we can go ahead and rewrite our utterance to include the slot:
NumberFact tell me a {FactType} fact about {Number}
{ "intents": [ { "intent": "NumberFact", "slots": [ { "name": "Number", "type": "AMAZON.NUMBER" }, { "name": "FactType", "type": "FACT_TYPE" } ] } ] }
Remember to hit “Save” in the Developer Portal to update the interaction model.
Handling the Custom Slot Value in Sinatra
Now that we're passing a fact type through to our Sinatra application, we can grab the fact type similar to how we grabbed the number. Once we have it, we can pass it directly to the Numbers API:
require 'sinatra' require 'json' require 'net/http' post '/' do parsed_request = JSON.parse(request.body.read) number = parsed_request["request"]["intent"]["slots"]["Number"]["value"] fact_type = parsed_request["request"]["intent"]["slots"]["FactType"]["value"] number_facts_uri = URI("http://numbersapi.com/#{ number }/#{ fact_type }") number_fact = Net::HTTP.get(number_facts_uri) { version: "1.0", response: { outputSpeech: { type: "PlainText", text: number_fact } } }.to_json end
It’s time to test this in the Service Simulator or in any Alexa-enabled device:
Alexa, ask Number Facts to tell me a math fact about 17
If we've hooked everything together, we should hear an interesting piece of knowledge about the mathematics of the number 17. And you’re done!
In the next module, we will learn how to handle contextual requests and conversation, using sessions. Stay tuned!
Extra Credit
1. Notice how entering an undefined phrase, e.g. "Alexa, ask Number Facts about 12 math", still invokes the intent. As a Ruby exercise, try to handle users' poorly-formed phrases gracefully via the Sinatra application.
2. Remember that slot values do not list the possible values a user can enter; it only lists the valid values your application accepts. Since users can say whatever they like in addition to "trivia" and "math," upgrade your Sinatra application to handle cases where a user asks for a fact type you don't recognise.
The Alexa Skills Kit (ASK) enables developers to build capabilities, called skills, for Alexa. ASK is a collection of self-service APIs, documentation, templates, and code samples that make it fast and easy for anyone to add skills to Alexa.
Developers have built more than 10,000 skills with ASK. Explore the stories behind some of these innovations, then start building your own skill. Once you publish your skill, mark the occasion with a free, limited-edition Alexa dev shirt. Quantities are limited.