Alexa Skills Kit (ASK) SDK for Node.js V2: what's new, what's changed, what's gone
This was a surprise: today the Alexa team released V2 of the Node.js Alexa Skills Kit SDK. Unlike the new Actions on Google SDK V2, the new ASK SDK is not created to handle new request or response formats. Instead, the interface is the major focus of the new version, placing an emphasis on modularity (as pointed out by @nickschwab, @marktucker, and others) and extendability. If you’re used to the existing SDK, you’ll have to change your mental model to build skills. (Most code samples taken from the SDK wiki. That will have some details that aren’t here, and vice-versa.)
Note: thanks to the folks at Amazon, via Paul Cutsinger, for alerting me to a few things I missed or got wrong below. Corrections italicized or crossed out.
Changed in V2
- Modularity
- Handler definitions
- Response builder
- Lambda handler
- Attributes
- State handling
- Data persistence
- Template helpers
- App validation
New in V2
Removed in V2
- Localization
Unhandled
handlerNewSession
handleremit
andemitWithState
APP ID validation
Moving to ASK SDK V2 from V1
First: you do not need to move to V2 if V1 is working for you, or if you want to build a new skill and don’t want to use the newest SDK. As of right now, the ASK CLI doesn’t even use SDK V2. (And, quickly, the ASK CLI was updated before I even published this post.) If you want to use the SDK that will have the newest functionality (e.g. notifications, gadgets), go ahead and switch to V2.
The easiest way to switch is by using the ask-sdk-v1adapter
. The adapter works the same as before, and adds a registerV2Handlers
method. Note that V1 handlers are used before any V2 handlers; if you have two that conflict, V1 will always take precedence.
Changed in ASK SDK V2
Modularity
The biggest conceptual change is the new focus on modularity. I say this is the biggest change, because despite others like the new way to handle requests, a number of changes come as a result of the modularity. Data persistence via the PersistenceAdapter
, service clients, and ApiClient
all come about due to the focus on modularity. The best illustration of this is in the skill builders. These are two different ways to present handlers (either on Lambda or otherwise): the custom and the standard skill builders. With the standard skill builder, you’re using the ApiClient
that comes from the Alexa team and the DynamoDB persistence adapter. The custom skill builder allows for an injection of an API client and persistence adapter of your choosing.
Handler definitions
Whoah, this is a big one, and, after the initial “I don’t like new things” reaction, I think I like the new way better. Reminder: in V1, handlers were tied to a state, and used the event emitter pattern by looking for all of the registered event listeners for an intent + state
pairing. In V2, handlers are defined sequentially (that is to say, with a hierarchy) and each handler has a function that determines if it is valid for the current request.
In this snippet, canHandle
will run first, and only if that returns true (in this case, the current request is for the HelloWorldIntent
) will handle
run. This opens handlers up to be able to handle more within a single function. For example, canHandle
can check that the current state is IN_GAME
and the request is either AMAZON.MoreIntent
or AMAZON.ScrollDownIntent
. In V1, you would need to declare both of these separately, perhaps calling the same function or emitting the “canonical” handler. Another example is handling AMAZON.StopIntent
the same for all states. Because intent handling isn’t as closely tied to states as it was in V1, you can have this cascade where an intent is handled by a fallback handler unless it was handled higher in the hierarchy. The hierarchy is defined by the order in which the handlers are passed to addRequestHandlers
.
In this case, if a request matches HelpIntent
and AnswerIntent
, the HelpIntent
handler will fulfill the request, because it comes first in the argument list. Again, do not downplay what this opens up. The code can route handlers based off intent, slot values, attributes, or even external API calls. Although… ignore that last one. These checks are sequential. @LaunchRequest@’s canHandle
function is checked, then HelpIntent
, then AnswerIntent
, and on down the line. This processing time can add up.
The handlerInput
argument is an object containing:
RequestEnvelope
, the inbound requestAttributesManager
, handles the attributes (stored data)ServiceClientFactory
, can connect to Alexa services (like requesting address)ResponseBuilder
Context
, thecontext
argument to an AWS Lambda function
Response builder
Response builder has changed, with a new interface and no need to emit :responseReady
but no other significant changes on first glance.
Lambda handler
The exported lambda handler function is very different compared to V1, and abstracts away what is happening when the function is called.
This new code makes it less clear that the incoming event and context are sent to the SDK and used to build the response. If you’re familiar with how Alexa works, it’s obvious, but not if you’re coming to it the first time. But also, most people coming to it the first time might overlook that part of the set up anyway. Interestingly, the SDK no longer uses the context.fail
and context.succeed
methods, and instead uses the callback (third argument to the function, which is not present in most skill code/examples you’ll find). This is likely related to an earlier issue with the SDK where attributes failed to write to DynamoDB, fixable by relying on the callback rather than the context methods.
There is another way of creating the Lambda handler that I glossed over. This way both makes it more clear that the handler is retrieving arguments, and makes it easier to log out the inbound event.
Attributes
Attributes now have three scopes: request, session, and persistent. Request attributes can be used to store temporary data or helper functions. Session attributes go from the skill, to the Alexa service, and back in the next request so long as the session is alive. Persistent attributes are sent to a data store (most commonly this will by DynamoDB) via the PersistenceAdapter
. All attributes are handled through the AttributesManager
and the methods for getting all three types of attributes, setting all three, and saving persistent attributes.
One huge implication is that V2 doesn’t have the concept of “state.” This rises out of how both attributes and intents are handled. Intents no longer flow from a top-level state, they “self select,” and attributes are now more granular. You can invoke a handler based on the value of an attribute that you call STATE
, or you can choose any attribute or other criteria.
Data persistence
Another example where modularity shows its impacts: connecting a lambda skill to DynamoDB is no longer as simple as setting the table name. (I overspoke here: with the standard handler, you can create the table by providing the name and specifying that the table needs to be automatically created. Not as simple as before, but not far off, either.) Consequently, skills can now use other data stores. This, however, is only available when using CustomSkillBuilder
.
The above is an example of using a custom persistence adapter. Maybe you want, instead, to use DyanmoDB. Compare how you would do it in V1 compared to V2.
Template helpers
Gone are the template builders, replaced with addRenderTemplateDirective
, which accept objects of options.
New in ASK SDK V2
Error handlers
New in V2 is the idea of error handlers of the same stature as request handlers. Accepts the incoming handler details, and the current error. Registered with addErrorHandlers
and “self-select” via the same canHandle
function (again, returning a boolean).
Interceptors
These are functions that are called at certain parts in the lifecycle; specifically, for the request or the response. The request interceptor come into play before the selected request handler runs. The response interceptor runs right after that handler runs. Add them to the skill builder via addRequestInterceptors
and addResponseInterceptors
. The request interceptors are useful for adding data to the request attributes, while response interceptors can be used to validate data. Each returns a promise that resolves to nothing.
ApiClient
SDK V2 now has a built-in client for communicating with external APIs (when using the standard skill builder), or can accept a custom one if so desired. I won’t drill too far into this, but check out the implementation for yourself.
Also new, similar to ApiClient
, are the Alexa Service Clients. These must be used in conjunction with either the default API client or a custom one, and are used to get lists, device address, or send directives (i.e. queueing audio).
Removed in ASK SDK V2
Localization
No more baked-in i18next
. Good new if you’re not using it and you want to reduce the size of the skill you’re uploading to Lambda. One method to add this back into the skill is through a request interceptor. Check out this example from the Alexa team, where .t
is hopping a ride with the request attributes, and is thus available in all handlers.
Unhandled and NewSession handlers
The Unhandled
and NewSession
handlers are no more. Instead, rely on the handler hierarchy and canHandle
function. For example, take the Unhandled
handler. In V1, that was invoked when there was no handler for the request plus state combination. V2 places less of an emphasis on state, and more of an emphasis on the “self selection” of handlers. You can still have multiple handlers catching unhandled intents for a given state (though, again, “state” doesn’t really exist like it did in V1), but you can also have one singular unhandled intent. Make sure that it’s last in the argument list and that canHandle
always returns true. That way, anything not otherwise handled will fall on through.
emit
and emitWithState
Both gone. Now simply call the function you want to invoke.
APP ID Validation
Gone as well, this is now handled directly inside Lambda if you use it, or can be done in a request interceptor.
I got this one wrong. There is still ID validation, using the .withSkillId()
method on the skill builders.
Overall, there are a large number of cosmetic (interface) changes with the Alexa Skills Kit for Node.js SDK V2, but there are a lot of functionality changes as well. Perhaps the most important are the modularity changes, reducing the size of the SDK, how handlers are selected, and request and response interceptors. Give it a try, or dip your toe in with the V1 adapter.