We recently released version two of the Alexa Skills Kit (ASK) Software Development Kit (SDK) for Node.js, which introduced a fresh approach to handling requests and responses (among many other enhancements to the SDK). In our new code deep dive series, we’ll show you the ins and outs of the new SDK by providing a step-by-step code overview of the new features. We will also build a listening retention memory game skill from scratch, bit by bit, as we progress through the series.
In our first code deep dive, we covered the basic concepts of the new ASK SDK for Node.js, like `canHandle()`, `handle()`, and Response Builder. Be sure to check out the first post in this series. We will use the final code from the last deep dive as the starting point for today’s post on slots and session attributes.
Today's deep dive will teach you how to capture user input using slots and cater responses accordingly. You’ll also learn about session attributes and how to add request handlers for built-in intents like AMAZON.StartOverIntent, and AMAZON.YesIntent.
Here's a scenario that shows the capabilities we want to build into our listening retention memory game skill with this deep dive:
Customer: Alexa, ask Memory Challenge to start the challenge [This will trigger StartStoryIntent].
Alexa: Welcome to Memory Challenge. I will read you a short passage, and then ask you questions based on that. Are you ready?
Customer: Yes [This will trigger AMAZON.YesIntent].
Alexa: Jeff loves sports. His favorite sports in the Olympics are ice skating and skiing for the Winter Olympics, and basketball and volleyball for the Summer Olympics. What are Jeff's favorite games for the Winter Olympics?
Customer: Skiing [This will trigger AnswerIntent].
Alexa: Yup! Skiing is correct. Here's your 2nd question - While traveling, Samantha likes to take her tooth brush, hair brush, face cream, and hair dryer. What does Samantha like to carry when she travels?
Customer: Wallet [This will trigger AnswerIntent]
Alexa: Nope! Wallet is incorrect.
....
....
Alexa: Thank you for playing Memory Challenge. Your final score is 2 out of 5.
Before we begin writing code for our skill, it’s a good idea to think through the conversation, and determine the three main acts that our skill will be handling. Much like writing a script for a play or a movie, this exercise helps figure out the request handlers we would need. For example, the three main acts of our skill will be –
With that out of the way, let’s start building.
We will start off by creating a new handler that will be responsible for reading the memory challenge stories to the customer. We'll call it `StoryHandler`.
Step 1: Add a new handler - StoryHandler
As we did with the `LaunchRequestHandler`, we will start off with the basic scaffold for our new handler.
Step 2: Set conditions for the requests this handler is capable of handling
As we mentioned in the last deep dirve, in the new Alexa SDK, the request routing is based on a “can you handle this?” concept, which means that every handler lays out the specific condition/s that it is capable of handling.
Let's think about the conditions for our StoryHandler here. As you can see from the conversation above, our skill should read a new question in the following scenarios:
Because all of these scenarios require the same execution logic, we can merge them all together to be handled by our `StoryHandler` request handler.
Step 3: Add the logic that should execute if canHandle returns true
Get the story by calling the function getNextStory()
As we established in the last step, if the canHandle() function returns true for `StoryHandler`, our skill should begin narrating the story. To get the story, we make a call to a little helper function `getNextStory()`, which returns the story to be narrated as an object of the form:
```
{
"question":"Jeff loves sports. His favorite sports in the Olympics are ice skating and skiing for the Winter Olympics, and basketball and volleyball for the Summer Olympics. What are John's favorite games for the Winter Olympics?",
"answer":["skating","ice skating","skiing"]
}
```
Narrate the story to the customer by using the responseBuilder
Next, we assign the question to the speechOutput variable, and generate a speech response by using the `speak()` method of `responseBuilder`.
Keep the session open
Since we are expecting our customer to respond back, we add the `reprompt()` method to our responseBuilder. This will keep the session open for us, so the customer can respond back with an answer.
Generate the JSON response
Finally, we add the `getResponse()` method to generate the JSON response back with our `speechOutput`.
We have accomplished the first act of our skill, which is to narrate the story to the customer. We are now ready for the second act, which is capturing the answer from the customer, checking if the answer is correct, and responding back accordingly. Then, asking the next question.
Step 1: Set up a new handler - AnswerHandler
Create a new handler called `AnswerHandler`. This will be used when the customer provides an answer to our question and will trigger the `AnswerIntent`.
Like we've done before, we start off by capturing the `request`.
Step 2: Set conditions for the requests this handler is capable of handling
This will be a pretty straight forward canHandle() function. We just need to check if the intent name is `AnswerIntent`.
Step 3: Capture User Input and respond back
Get session attributes
We will be using session attributes to keep track of session-level data, like counter, number of correct answers, last question asked, etc. We will use attributes manager provided by the SDK to get and set the session attributes using the attributes manager's `getSessionAttributes`, and `setSessionAttributes` methods.
Capture the user input through the answer slot
We navigate through the JSON request sent to our skill, and grab the user input provided through the `answer` slot defined in our interaction model.
Check answer
We use our helper function `checkAnswer()` to check if the answer provided by the customer (answerSlot) matches the correct answer.
Get the next story
We then get the next story using our helper function `getNextStory()`.
Let's take a quick diversion to take a look at the `getNextStory` function. As you can see in the code below, we set up some attributes that will come in handy for us to keep track of number of questions asked (`attributes.counter`), number of correct (`attributes.correctCount`), number of incorrect answers (`attributes.wrongCount`). We increment these counters accordingly in the `checkAnswer()` function.
Generate speechOutput using session attributes
Back in the AnswerHandler, we set up the speechOutput to read the result of the answer (correct/incorrect) as returned by the `checkAnswer()` function. We also pull the counter from the session attributes and include that in our response.
```const speechOutput = result.message + "Here's your " + (attributes.counter + 1) + "th question - " + story.question;```
Respond back with the status of the answer, and then the question.
Store the result of the last question in session attributes
We store the result of the last question as a session attribute, and call attributes manager's setSessionAttributes() method to save the attributes, so they're available in the next interaction.
Generate the JSON response
Since we are expecting the customer to respond back, we add the `reprompt()` method to our `responseBuilder`. This will keep the session open for us, so the customer can respond back with an answer. Finally, we add the `getResponse()` method to generate the JSON response back with our `speechOutput`.
We will be using the session attribute counter (`attributes.counter`) to determine if the last question has been asked, which would mean that our skill should now respond with the final score. To deal with that, we will add one more handler - FinalScoreHandler, and add a condition to check if the number of questions asked (`attributes.counter`) equals the total number of questions available. If yes, we respond back with the final score.
Set up the FinalScoreHandler
Add counter condition to AnswerHandler
If the counter is not equal to the number of questions yet, Alexa should hit the `AnswerHandler` again. But, to make sure that happens, we will make a small change to our condition for `AnswerHandler` - check if the number of questions asked (`attributes.counter`) is less than the total number of questions available.
We will be using session attributes to keep track of session-level data, like counter, number of correct answers, last question asked, etc. We will use attributes manager provided by the SDK to get and set the session attributes.
If you would like to build this skill with us throughout the series, follow the steps below to kick-start your skill: