Amazon Alexa
The Amazon Alexa platform integration allows you to build custom Alexa Skills using Jovo.
Introduction
Apps for Alexa are called Alexa Skills. The official suite of services, frameworks, and APIs to build Alexa Skills provided by Amazon is called Alexa Skills Kit (ASK). You can find a general introduction into building Alexa Skills in the official Alexa documentation.
In the installation section, we're going to set up a Jovo project that works with Amazon Alexa.
An Alexa Skill usually consists of two parts:
- The Alexa Skill project in the Alexa Developer Console
- The code that handles the logic of your Skill
In the Alexa Developer Console, the Alexa Skill project is configured, including an Alexa Interaction Model that trains Alexa's language understanding service. Learn more about how to use the Jovo CLI to create and deploy Alexa Skill projects in the Alexa Developer Console project section.
If a user converses with your Skill, Alexa sends API requests to your Skill's code endpoint. The code is then responsible for returning an appropriate response. Learn more about how you can build this with the Jovo Framework in the Alexa Skill code section.
Jovo is a framework that allows you to build apps that work across devices and platforms. However, this does not mean that you can't build highly complex Alexa Skills with Jovo. Any custom Alexa Skill that can be built with the official ASK SDK can also be built with the Jovo Framework. In the platform-specific features section, we're going to take a look at building
Installation
To create a new Alexa project with Jovo, we recommend installing the Jovo CLI, creating a new Jovo project, and selecting Alexa as platform using the CLI wizard. Learn more in our getting started guide.
# Install Jovo CLI globally $ npm install -g @jovotech/cli # Start new project wizard # In the platform step, use the space key to select Alexa $ jovo new <directory>
If you want to add Alexa to an existing Jovo project, you can install the plugin like this:
$ npm install @jovotech/platform-alexa
Add it as plugin to your app configuration, e.g. app.ts
:
import { App } from '@jovotech/framework'; import { AlexaPlatform } from '@jovotech/platform-alexa'; // ... const app = new App({ plugins: [ new AlexaPlatform({ intentMap: { 'AMAZON.StopIntent': 'END', 'AMAZON.CancelIntent': 'END', }, // ... }), // ... ], });
The Alexa platform comes with its own intentMap
that gets merged into the global intentMap
. This maps incoming Alexa intents to Jovo intents or handlers.
You can also add the CLI plugin to your project configuration in jovo.project.js
. Learn more about the Alexa-specific project configuration here.
const { ProjectConfig } = require('@jovotech/cli-core'); const { AlexaCli } = require('@jovotech/platform-alexa'); // ... const project = new ProjectConfig({ // ... plugins: [ new AlexaCli(), // ... ], });
The Alexa CLI plugin uses the official ASK (Alexa Skills Kit) CLI provided by Amazon for deployment. For the deployment to work, you need to at least set up a default
ASK profile using the ASK CLI. Follow the official Alexa docs to install and configure ASK CLI.
# Install ASK CLI globally $ npm install -g ask-cli # Configure ASK profile $ ask configure
After the successful installation, you can do the following:
- Use the Jovo CLI to create a project in the Alexa Developer Console
- Use the Jovo Framework to build the Alexa Skill code
Alexa Developer Console Project
Jovo helps you manage your Alexa Skill project in the Alexa Developer Console using the Jovo CLI.
You can use the build
command to turn the Alexa project configuration into Alexa specific files.
$ jovo build:platform alexa
These files can be found in a folder called platform.alexa
in the build
directory of your project. They include the Alexa Interaction Model that is responsible for training Alexa's natural language understanding service.
Since Alexa requires certain built-in intents, make sure that the files in your models
folder contain the following elements before running build
. They are added by default if you select Alexa as platform in the new
CLI wizard.
{ "alexa": { "interactionModel": { "languageModel": { "intents": [ { "name": "AMAZON.CancelIntent", "samples": [] }, { "name": "AMAZON.HelpIntent", "samples": [] }, { "name": "AMAZON.StopIntent", "samples": [] } ] } } } }
The resulting files can then be deployed to the Alexa Developer Console using the deploy:platform
command.
$ jovo deploy:platform alexa
Learn more on the following pages:
Alexa Skill Code
The Jovo Alexa platform package is a platform integration that understands the types of requests Alexa sends and knows how to translate output into an Alexa response. To learn more about the Jovo request lifecycle, take a look at the RIDR documentation.
When a user interacts with your Skill through Alexa, the voice assistant turns user input (usually speech) into structured meaning (usually intents and slots). It then sends a request with this data to you Jovo app. Learn more about the request structure in the official Alexa docs.
The Jovo app then uses this request information to return an appropriate response that tells Alexa what to say (or display) to the user. For example, the code snippet below asks the user if they like pizza:
LAUNCH() { return this.$send(YesNoOutput, { message: 'Do you like pizza?' }); }
If you want to learn more about how to return the right response, take a look at these concepts:
The output is then translated into a response that is returned to Alexa. Learn more about the response structure in the official Alexa docs.
Platform-Specific Features
The Alexa platform integration for Jovo supports a lot of platform-specific features. You can access the Alexa object like this:
this.$alexa;
You can also use this object to see if the request is coming from Alexa (or a different platform):
if (this.$alexa) { // ... }
The following Alexa properties offer additional features:
Request
You can access the request using the following methods:
// Generic request type this.$request; // Alexa request type this.$alexa.$request;
There are also some helper methods to help you retrieve information from Alexa requests.
For example, you can retrieve the unit
property from the Alexa System object like this:
this.$alexa.$request.getUnit(); /* Result: { unitId: string; persistentUnitId: string; } */
User
There are various Alexa specific features added to the user class that can be accessed like this:
this.$alexa.$user;
The following features are offered by the Alexa user property:
User Profile
You can call the Alexa Customer Profile API by using the following methods:
await this.$alexa.$user.getEmail(); // Result: string await this.$alexa.$user.getName(); // Result: string await this.$alexa.$user.getGivenName(); // Result: string await this.$alexa.$user.getMobileNumber(); // Result: { countryCode: string; mobileNumber: string; }
Below is an example getEmail
handler:
import { AskForPermissionConsentCardOutput } from '@jovotech/platform-alexa'; // ... async getEmail() { try { const email = await this.$alexa.$user.getEmail(); return this.$send({ message: `Your email address is ${email}` }); } catch(error) { if (error.code === 'NO_USER_PERMISSION') { return this.$send(AskForPermissionConsentCardOutput, { message: 'Please grant access to your email address.', permissions: 'alexa::profile:email:read', listen: false, }); } else { // ... } } },
If the getEmail
call returns an error with the code NO_USER_PERMISSION
, an AskForPermissionsConsent
card (learn more in the official Alexa docs) returned to ask the user for permission. For this, the AskForPermissionConsentCardOutput
convenience output class is used.
Under the hood, it looks like this:
{ listen: this.options.listen, message: this.options.message, platforms: { alexa: { nativeResponse: { response: { card: { type: 'AskForPermissionsConsent', permissions: this.options.permissions, }, }, }, }, }, }
For a Skill to be able to request information from the Customer Profile API, the permissions need to be added to the Skill manifest, either in the Alexa Developer Console or the skill.json
file. The latter can be done by using the files
property of the Alexa project config:
new AlexaCli({ files: { 'skill-package/skill.json': { manifest: { permissions: [ 'alexa::profile:email:read', // ... ], }, }, }, // ... });
Person Profile
You can call the Alexa Person Profile API by using the following methods:
await this.$alexa.$user.getSpeakerName(); // Result: string await this.$alexa.$user.getSpeakerGivenName(); // Result: string await this.$alexa.$user.getSpeakerMobileNumber(); // Result: { countryCode: string; mobileNumber: string; }
Below is an example getName
handler:
import { AskForPermissionConsentCardOutput } from '@jovotech/platform-alexa'; // ... async getName() { try { const name = await this.$alexa.$user.getSpeakerName(); return this.$send({ message: `Your name is ${name}` }); } catch(error) { if (error.code === 'NO_USER_PERMISSION') { return this.$send(AskForPermissionConsentCardOutput, { message: 'Please grant access to your name.', permissions: 'alexa::profile:name:read', listen: false, }); } else { // ... } } },
For a Skill to be able to request information from the Person Profile API, the permissions need to be added to the Skill manifest, either in the Alexa Developer Console or the skill.json
file. The latter can be done by using the files
property of the Alexa project config:
new AlexaCli({ files: { 'skill-package/skill.json': { manifest: { permissions: [ 'alexa::person_id:read', 'alexa::profile:name:read', // ... ], }, }, }, // ... });
Account Linking
Account linking enables you to connect your Alexa Skill users to other systems.
Learn more in the Account Linking documentation for Alexa.
Output
There are various Alexa specific elements that can be added to the output.
Learn more in the Jovo Output documentation for Alexa.
Device
You can check if the device supports APL by using the following method that checks for platform-specific device capabilities:
import { AlexaCapability } from '@jovotech/platform-alexa'; // ... if (this.$device.supports(AlexaCapability.Apl)) { /* ... */ } // or if (this.$device.supports('ALEXA:APL')) { /* ... */ }
The following Alexa specific capabilities are available:
AlexaCapability.Apl
or'ALEXA:APL'
for theAlexa.Presentation.APL
interfaceAlexaCapability.Html
or'ALEXA:HTML'
for theAlexa.Presentation.HTML
interface
There are also various Alexa specific features added to the device class that can be accessed like this:
this.$alexa.$device;
You can access the following properties and methods of the Alexa device class:
this.$alexa.$device.id
: Get the device ID from the Alexa request- Device location and address
- System settings
Device Location and Address
It is possible to retrieve your Alexa Skill user's address information, if they grant the permission for this. Learn more in the official Alexa docs.
You need to first get the permission, which you can do by sending a card to the user's Alexa app. You can use the AskForPermissionOutput
for this:
import { AskForPermissionOutput } from '@jovotech/platform-alexa'; // ... someHandler() { // ... try { const location = await this.$alexa.$device.getLocation(); // ... } catch(error) { if (error.code === 'NO_USER_PERMISSION') { return this.$send(AskForPermissionOutput, { message: 'Please grant the permission to access your device address.', permissionScope: 'read::alexa:device:all:address', }); } else { // ... } } }
Under the hood, the AskForPermissionOutput
looks like this:
{ message: this.options.message, platforms: { alexa: { nativeResponse: { response: { shouldEndSession: true, directives: [ { type: 'Connections.SendRequest', name: 'AskFor', payload: { '@type': 'AskForPermissionsConsentRequest', '@version': '1', 'permissionScope': this.options.permissionScope, }, token: this.options.token || '', }, ], }, }, }, }, }
You can use the getLocation()
method to retrieve the device location:
import { DeviceLocation } from '@jovotech/platform-alexa'; // ... async someHandler() { const location: DeviceLocation = await this.$alexa.$device.getLocation(); /* Result: { city: string; countryCode: string; postalCode: string; } */ }
The getAddress()
method can be used to retrieve the address associated with the device:
import { DeviceAddressLocation } from '@jovotech/platform-alexa'; // ... async someHandler() { const address: DeviceAddressLocation = await this.$alexa.$device.getAddress(); /* Result: { addressLine1: string; addressLine2: string; addressLine3: string; districtOrCounty: string; stateOrRegion: string; city: string; } */ }
System Settings
It is possible to retrieve some of your Alexa Skill user's settings without them granting you any special permissions. Learn more in the official Alexa docs.
You can use the getTimeZone()
method to retrieve the timezone setting:
async someHandler() { const timezone: string = await this.$alexa.$device.getTimeZone(); /* Result: "Africa/Abidjan" */ }
Entities (Slots)
Alexa slots are called entities in Jovo. You can learn more in the Jovo Model and the $entities
documentation.
You can access the Alexa-specific $entities
property like this, which allows you to get typed access to the native
API result for each slot:
this.$alexa.$entities; // Example: Get native API result object for slot "name" this.$alexa.$entities.name.native;
Learn more about the structure of the API result in the official Alexa documentation on entity resolution.
ISP
Jovo offers an integration with in-skill purchasing (ISP) which allows you to make money by selling digital goods and services through your Alexa Skill.
Learn more in the Jovo ISP documentation for Alexa.
Alexa Conversations
You can build Alexa Skills with Jovo that make use of the Alexa Conversations dialogue management engine.
Learn more in the Jovo Alexa Conversations documentation.
Skill Connections
You can use Jovo with Alexa Skill Connections by sending a Connections.StartConnection
directive as shown in the official Alexa docs.
For example, if you want to connect to a custom task in a specific skill, you can use the StartConnectionOutput
class provided by the Jovo Alexa integration.
import { StartConnectionOutput, OnCompletion } from '@jovotech/platform-alexa'; // ... someHandler() { // ... return this.$send(StartConnectionOutput, { taskName: { amazonPredefinedTask: false, name: 'CountDown', }, // Input for the task input: { upperLimit: 10, lowerLimit: 1, }, // Decides whether session is picked up after task onCompletion: OnCompletion.SendErrorsOnly, token: '<your-token>', // defaults to 1 taskVersion: 1, // This is mandatory in case taskName.amazonPredefinedTask is false providerSkillId: '<skill-id>', }); }
The Output Options can be changed to create a managed skill connection, by setting taskName.amazonPredefinedTask
to true and omitting providerSkillId
.
In case the session is supposed to be resumed after the task is handled, onCompletion
can be changed to OnCompletion.ResumeSession
.
For some specific connections, the Jovo Alexa integration offers convenience output classes. Below is an overview of all classes:
ConnectionAskForPermissionConsentOutput
ConnectionLinkAppOutput
ConnectionPrintImageOutput
ConnectionPrintPdfOutput
ConnectionPrintWebPageOutput
ConnectionScheduleFoodEstablishmentReservationOutput
ConnectionScheduleTaxiReservationOutput
ConnectionTestStatusCodeOutput
ConnectionVerifyPersonOutput
ConnectionAddToShoppingCartOutput
ConnectionBuyShoppingProductsOutput
You can find the output options in each class implementation. For example, you use the ConnectionAskForPermissionConsentOutput
like this:
import { ConnectionAskForPermissionConsentOutput } from '@jovotech/platform-alexa'; // ... someHandler() { // ... return this.$send(ConnectionAskForPermissionConsentOutput, { // Options message: 'Please grant access to your Alexa profile name', shouldEndSession: true, token: '<your-token>', permissionScopes: ['alexa::profile:given_name:read'] }) }
This would result in the following output template:
{ message: 'Please grant access to your Alexa profile name', platforms: { alexa: { nativeResponse: { response: { shouldEndSession: true, directives: [ { type: 'Connections.StartConnection', uri: 'connection://AMAZON.AskForPermissionsConsent/2', input: { '@type': 'AskForPermissionsConsentRequest', '@version': '2', 'permissionScopes': ['alexa::profile:given_name:read'], }, token: '<your-token>', onCompletion: 'RESUME_SESSION', // default }, ], }, }, }, }, }
Custom Tasks
You can handle custom tasks with the following features:
this.$alexa.task
property that offers helper methods and properties to access task related valuesAlexaHandles.onTask()
for the@Handle
decorator to accept task requests
Here is an example:
import { AlexaHandles } from '@jovotech/platform-alexa'; // ... @Handle(AlexaHandles.onTask('Reminder', 1)) reminderTask(): Promise<void> { const task = this.$alexa?.task.getTask(); const taskName = this.$alexa?.task.name; const taskVersion = this.$alexa?.task.version; const input = this.$alexa?.task.input; console.log(task, taskName, taskVersion, input); // ... }
To learn more about all features of the task
property, take a look at the AlexaTask.ts
file.
Under the hood, onTask()
as part of AlexaHandles
looks like this with the parameters taskName: string
and optional taskVersion?: number | string
:
{ types: [InputType.Launch], global: true, if: (jovo: Jovo) => { const task = jovo.$alexa?.task.getTask(); if (!task) return false; if (!jovo.$alexa?.task.hasTaskName(taskName)) return false; if (taskVersion && !jovo.$alexa.task.isVersion(taskVersion)) return false; return true; }, }
Name-free Interaction
You can handle name-free interactions by responding to CanFulfillIntentRequest
requests. To do this, you can use the following two helpers:
AlexaHandles.onCanFulfillIntentRequest()
: A method for the@Handle
decorator that can be added to a handler to accept requests of the typeCanFulfillIntentRequest
- By returning
CanFulfillIntentOutput
, a convenience output class, you can send a response to Alexa that includes theCanFulfillIntent
directive
import { Handle } from '@jovotech/framework'; import { CanFulfillIntentOutput, AlexaHandles } from '@jovotech/platform-alexa'; // ... @Handle(AlexaHandles.onCanFulfillIntentRequest()) someHandler() { // ... return this.$send(CanFulfillIntentOutput, { canFulfill: 'YES', }); }
Under the hood, onCanFulfillIntentRequest()
as part of AlexaHandles
looks like this:
{ global: true, types: ['CanFulfillIntentRequest'], platforms: ['alexa'], }
CanFulfillIntentOutput
looks like this:
{ listen: false, platforms: { alexa: { nativeResponse: { response: { canFulfillIntent: { canFulfill: this.options.canFulfill, slots: this.options.slots ?? {}, }, }, }, }, }, }