Create Re-usable Alexa Skills with Custom Tasks (Beta) and Direct Skill Connections (Preview)

German Viscuso Aug 17, 2020
Share:
Tips & Tools Alexa Live News
Blog_Header_Post_Img

Editor's Note

By the end of the last year we announced Custom tasks and Direct Skill Connections. Custom tasks are a way to turn any part of your skill into a re-usable customer-facing operation and allow other sources to connect to it.
Direct Connections is an expansion to Skill connections which allows the developer to connect a requester skill to a specific custom task in a provider skill.
You must not share any protected health information (PHI) or personally identifiable information (PII) when you create a connection between skills. This restriction applies to any information, including information you send in the schema. Child-directed skills and HIPAA skills aren't eligible to use connections between skills.
Direct skill connections are currently in developer preview. To use them, you must register your skill for the preview.
With the recent announcement of Quick Links for Alexa (beta) the custom tasks that you implement in your provider skills can also be launched by customers with just one click from mobile apps, websites, or online ads.

 

In the typical all-in-one Alexa skill, developers build every functionality within an individual skill. If the skill is complex enough this means you'll have to tackle your skill implementation as separate modules that address specific tasks.
Suppose you're working on a skill that, based on your birthday, gives you a set of interesting functionalities (like the skill we showcase in our online Alexa skill building video course "From Zero to Hero") and now you want to add support to tell the customer the horoscope. Typically, this will involve expanding the code to retrieve the horoscope based on the captured birthday by determining the proper zodiac sign, then pass it to an API or database to get the text, properly handle errors and finally format the output to say it back to the user. It doesn't sound like a big deal but now imagine that you need this horoscope functionality in more than one skill and that someone (another developer or even you) already created a simple, robust skill that solves this. Wouldn't it be nice to just be able to "call" this horoscope functionality (from our skill) as a service?

Skill connections lets you request provider skills to perform simple tasks that expand you own skill’s abilities. In a nutshell, a skill connection lets you invoke a specific task provided by another skill from within your own skill so you can offload common tasks to outside providers while retaining control over the customer experience. A task then could be summarized as a piece of user experience that is implemented in an Alexa skill, such as an action that a skill performs.

Skills that offload tasks through skill connections are called Requesters. Skill connections can pass useful context along with your request to another skill. For example, if your skill captures the user's birthday and, based on it, then calculates the zodiac sign, it can then pass that sign through a connection to a horoscope skill to get the horoscope description. From the customer's perspective there's no need to invoke another skill; the horoscope skill takes over the conversation and then comes back to the requester skill to resume the experience (which translates into less friction for the customer which is always welcome). You can make your skill a Requester by implementing the required handlers and re-publishing it (expanding a skill to be a requester is straight forward and simple, more on this below). As a side note, you can also request the launch of a custom task via Quick Links for Alexa (beta) (more on this below).

Skills that perform tasks for other skills are called Providers. You can make your skill a provider by updating its manifest (skill.json) to register the skill as a provider by adding a reference to supported tasks. Then you'll continue by creating the custom task definition files (using the OpenAPI 3.0 specification), implementing the necessary handlers in the back-end, and then resubmitting for approval through the skill certification process. For a horoscope skill to act as a horoscope provider we would have to add a task to it that accepts the birthday or the zodiac sign as parameter, then it should read the horoscope to the customer and finally come back gracefully to the requester skill.

Let's take a close look at the whole process using the Simple Horoscope demo skill in our Alexa Cookbook as an example.

Implement your Custom Task

First of all, in order to deploy skills with tasks, you'll need to use the ASK CLI v2 (only the latest CLI version can deploy them) so if you're on an old CLI version (v1) please see this guide to install or update the ASK CLI v2. If you're going to add Custom Tasks to an existing skill created using ASK CLI v1, you'll need to upgrade your skill package to CLI v2.

In order to provide a uniform, standard way to create tasks in skills we decided to use a standard for defining RESTful web services: the OpenAPI 3.0 specification (originally known as the Swagger specification). So the process begins with the creation of an API specification following this format (inside a skill-package/tasks directory) that defines the structure of each task as a service (you'll need one specification file per supported task). In this specification file you define the path for the API call, the type of HTTP method (for custom tasks that's a post), the input parameters that each call expects and the possible responses that the API call can return (among others). You might want to take a look at a guide that walks you through these initial steps which is available in our documentation.

Now let's take a look at the task definition in the Simple Horoscope demo skill (SignHoroscope.1.json):

Copied to clipboard
{
    "openapi": "3.0.0",
    "info": {
        "title": "Task to tell the horoscope by sign",
        "version": "1",
        "x-amzn-alexa-access-scope": "vendor-private",
        "x-amzn-display-details": {
            "en-US": {
                "title": "A task to get the horoscope"
            },
            "es-US": {
                "title": "Una tarea para obtener el horóscopo"
            }
        }
    },
    "tags": [
        {
            "name": "horoscope by sign"
        }
    ],
    "paths": {
        "/SignHoroscope": {
            "summary": "Horoscope by Sign",
            "description": "To give you your horoscope",
            "post": {
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Input"
                            },
                            "examples": {
                                "horoscopeOfLibra": {
                                    "summary": "An example input to get the horoscope of libra",
                                    "description": "The example inputs would be used for validation of the task",
                                    "value": {
                                        "sign": "libra"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "When the horoscope is delivered successfully",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SuccessfulResponse"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "When parameters are missing - e.g. sign is missing"
                    },
                    "404": {
                        "description": "When the given parameter fails a validation - e.g. non existent zodiac sign"
                    },
                    "500": {
                        "description": "When there's an error retrieving the horoscope"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "Input": {
                "type": "object",
                "properties": {
                    "sign": {
                        "type": "string",
                        "x-amzn-display-details": {
                            "en-US": {
                                "name": "Zodiac sign"
                            },
                            "es-US": {
                                "name": "Signo del zodíaco"
                            }
                        }
                    }
                }
            },
            "SuccessfulResponse": {
                "type": "object",
                "properties": {
                    "payload": {
                        "type": "string"
                    }
                }
            }
        }
    }
}

Note: this file uses a JSON format but OpenAPI 3.0 specifications can also be created in YAML

First, we need to pay attention to the file name SignHoroscope.1.json. It's required that you use the same name as the one provided in the paths section in the file contents above. The .1. in the middle specifies the version of the task and is a way to avoid breaking requester skills when we update a task (we'll keep old task versions so the requester skills can decide which version to use). This number must match the version tag in the file contents above.

Second, take a look at the x-amzn-alexa-access-scope section that is of key importance as it will define the visibility of your tasks (visible to all public or visible to your skills only). With vendor-private (which is the default) we make sure the tasks are only visible to skills with the same Vendor ID. We cover this in more detail below but if you want more information you can check our documentation.

Third, note that we define the path /SignHoroscope as a post method and that we specify what the request body should look like. The requestBody section also provides an examples section which is specially important as these are used in the certification process to test the tasks (for more info on the format of the examples check our documentation).

Then, and also as part of the paths definition, we provide all possible response codes for the custom task. Note that, in all cases, we're not providing implementation code but just structure. It will be up to the back-end to provide logic to process the incoming task request and always return response codes that match the specification (more on this below).

Finally, in the components/schema section by the end of the file, we provide the detailed definition for input and output parameters (what is passed to the task as input for processing and what is returned programmatically to the requester). Note that it's not mandatory to pass parameters in the task's response (since the Provider skill takes over you're all set there but in some cases you might want to return parameters back to the Requester skill).

By now we have defined a Task specification but we still need to provide the implementation in our back-end code. As an example, I will show you what that looks like in a Node.js based Lambda.

The first thing you have to take into account is that incoming task requests will have a request type of LaunchRequest with the addition of a task name parameter. That means that you can't have your standard LaunchRequest handler being processed first in the skill builder request handler queue. The request handlers for tasks should go first:

Copied to clipboard
exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        SignHoroscopeTaskHandler, // <- make sure tasks handler are added before the standard LaunchRequest handler
        LaunchRequestHandler,
        HoroscopeIntentHandler,
        SignHoroscopeIntentHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        FallbackIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler)
    .addRequestInterceptors(
        LocalisationRequestInterceptor)
    .addErrorHandlers(ErrorHandler)
    .lambda();

And this is the implementation of the actual task handler (SignHoroscopeTaskHandler):

Copied to clipboard
// This task handler must be added below to the skill builder *before* LaunchRequestHandler
const SignHoroscopeTaskHandler = {
    canHandle(handlerInput) {
        const {requestEnvelope} = handlerInput;
        return Alexa.getRequestType(requestEnvelope) === 'LaunchRequest'
            && requestEnvelope.request.task
            && requestEnvelope.request.task.name === SKILL_ID + '.SignHoroscope';
    },
    async handle(handlerInput) {
        const {requestEnvelope, responseBuilder} = handlerInput;
        let {task} = requestEnvelope.request;

        if(!(task.input && task.input['sign'])) {
            return responseBuilder
                .addDirective({
                    type: 'Tasks.CompleteTask',
                    status: {
                        code: 400,
                        message: 'Missing zodiac sign parameter'
                    }
                })
                .getResponse();
        }
        
        let sign = task.input['sign'];
        // we have to validate the sign here as we don't have model validations in tasks
        if(!ZODIAC_SIGNS.some(item => item === sign)) {
            return responseBuilder
                .addDirective({
                    type: 'Tasks.CompleteTask',
                    status: {
                        code: 404,
                        message: 'Invalid zodiac sign parameter'
                    }
                })
                .getResponse();
        }
        
        let horoscope = {};
        // here we take a fake horoscope from a localisation file but ideally you'd fetch it dinamically from a database or service
        horoscope.description = handlerInput.t('HOROSCOPE_' + sign);

        return responseBuilder
                .speak(horoscope.description)
                .addDirective({
                    type: 'Tasks.CompleteTask',
                    status: {
                        code: 200,
                        message: 'OK'
                    },
                    result: {
                        payload: horoscope.description
                    }
                })
                .getResponse();
    }
};

Note: When responding to a request with a Tasks.CompleteTask or Connections.StartConnection directive, your response cannot include audio or video player directives.

As you can see, in the canHandle() function, we make sure the request if of type LaunchRequest but we also check that the task name matches our task (note that a fully qualified task name must also include the skill ID which we previously defined as a constant in the lambda so you should replace this with your skill ID).

Then you'll see that we check if we received the parameter (in this case the zodiac sign) that will allow us to find the proper horoscope description. If we didn't there's not much that we can do so we send a Tasks.CompleteTask directive with status code 400 which in HTTP protocol lingo means Bad request (note that the task specification that we wrote above must include the 400 reference as a valid response code and the same goes for all response codes that we send via this type of directives). If the parameter is present we can retrieve it with task.input['PARAMETER_NAME'] making sure PARAMETER_NAME is a name defined in the input parameters of the components/schema section in the task definition file (as you can see in SignHoroscope.1.json above).

Additionally, we have to take care of parameter validation in the code. This is very important as tasks do not use any front-end side validation (such as voice interaction model Entity Resolution and Validations), so it's up to the developer to do validations in the back-end. In the code above we check the incoming zodiac sign parameter against a list of valid signs (ZODIAC_SIGNS) and send a Tasks.CompleteTask directive with status code 404 if we can't find it (this basically means 'Not found' in the HTTP protocol).

Finally, we get a horoscope description for the passed sign (we're just using a placeholder text here, ideally you'd fetch a daily horoscope description from a database or API) and then create a response. There are two important things to note in the response: 1) we use a speak() function so Alexa can tell the horoscope to the customer (remember that the Provider skill takes over the interaction so it definitely needs to speak!) and 2) we send a Tasks.CompleteTask directive with status code 200('OK') to signal that we got a normal conclusion to the operation. Passing a parameter such as payload in the result field of the directive is optional (but if you do that please make sure you define it in components/schema inside SignHoroscope.1.json as part of a successful response as yoi can see above).

Let me give you an important advice on performance here. Time outs are shorter than 8 seconds when a skill is running a task so please keep tasks as atomic as possible and avoid long running processes, multiple API calls or even a single slow API call. At the time of this writing the typical task timeout is 3 seconds so be careful!

Are we done? Not yet. We have to expose any tasks that we created in the skill manifest (skill.json) so they can be deployed with the skill. That's very straight forward and done in the skill-package/skill.json file in the apis section like this:

Copied to clipboard
"apis": {
      "custom": {
        "tasks": [
          {
            "name": "SignHoroscope",
            "version": "1"
          }
        ]
      }
    }

Note: while this feature is in Preview mode you'll have to fill in a form with your skill ID to be able to deploy tasks via the ASK CLI. For more info about deployment steps please check out the README file of our Simple Horoscope demo skill.

Launch your Custom Task

Launch a Task using Skill Connections

Once your skill is published the task will be available so it can be called from other skills. You can test your task from the command line but, typically, you'd use a directive to pass control from the requester task to the provider task:

Copied to clipboard
return handlerInput.responseBuilder
    .speak(speechText)
    .addDirective({
        'type': 'Connections.StartConnection', 
        'uri': 'connection://[YOUR_SKILL_ID].SignHoroscope/1?provider=[YOUR_SKILL_ID],
        'input': {
            'sign': 'virgo'
        },
        'token': '[YOUR_CORRELATION_TOKEN]'
    })

The response above will trigger a message by Alexa that states that the control is passing to another skill. Then, the provider skill takes over the interaction with the user via a custom task. Afterwards, when that task is done a status code is returned and, at the developer's discretion, some form of payload.

Processing the response in the Requester skill when the task is finished is done in a SessionResumedRequest handler, e.g.:

Copied to clipboard
const SessionResumedRequestHandler = {
    canHandle(handlerInput) {
        const {request} = handlerInput.requestEnvelope;
        return request.type === 'SessionResumedRequest';
    },
    handle(handlerInput) {
        const {status, result} = handlerInput.requestEnvelope.request.cause;
        const {code, message} = status;
        let speechText = `Got back! Status code is ${code}, message is ${message}`;
        if (code === '200') {
          speechText += ` and payload is. ${result.payload}`;
        }

        return handlerInput.responseBuilder
            .speak(speechText)
            .getResponse();
    },
};

Note: in the code snippet above the properties on result such as payload are defined by you, the developer, in the task specification file while the properties in status such as code and message are standard to all task responses.

Our technical documentation covers the Requester steps in great detail and provides additional recommendations.

At the moment of this writing there are two extra ways in which you can call a custom task: from Quick Links and from Timers.

Launch a Task using Quick Links

You can also request the launch of a custom task via Quick Links for Alexa (beta) by using a link with this URL pattern: https://alexa-skills.amazon.com/apis/custom/skills/<skill-id>/tasks/<custom-task-name>/versions/<task-version>?<task-input-key1>=<task-input-value1>&<task-input-key2>=<task-input-value2>. As an example, here's a Quick Link pointing to the live version of the Simple Horoscope skill requesting the horoscope for Virgo (at the time of this writing Quick Links is available in US based locales only so the link will only work with US based devices).

Launch a Task from Timers

Additionally you can trigger a custom task from a Timer by creating one that has a LAUNCH_TASK triggering behavior as shown here:

 

Copied to clipboard
function getCustomTaskLaunchTimer(handlerInput, title, duration) {
    return {
        duration: duration,
        label: title,
        creationBehavior: {
            displayExperience: {
                visibility: 'VISIBLE'
            }
        },
        triggeringBehavior: {
            operation: {
                type : 'LAUNCH_TASK',
                textToConfirm: [{
                    locale: 'en-US',
                    text: 'Timer elapsed. Would you like to launch {continueWithSkillName}?'
                }],
                task : {
                    name : '[SKILL_ID].[TASK_NAME]',
                    version : '[TASK_VERSION]',
                    input : {
                        //[TASK_INPUT_PARAMETERS]
                    }
                }
            },
            notificationConfig: {
                playAudible: true
            }
        }
    };
}

Note: you'll have to replace the elements between [ ] above and call the Timers API passing a call to the function above to create the timer. A full Timers API demo skill is available in the Alexa Cookbook.

When the Timer is elapsed your custom task will be launched.

It's easy to connect to your custom task based skills because we have multiple ways to do it and because you know they exist and you're clear about the parameters. But how can developers find out about publicly available tasks?

Discover Custom Tasks

Once your skill is certified and published your tasks will or will not be accessible to other developers depending on the visibility you assign in x-amzn-alexa-access-scope in the task definition json file. Note that in the definition above we give it the value vendor-private so only your Requester skills (with same Vendor ID) will be able to see the task. You need to change it to public if you want to make it searchable by others or usable via Quick Links for Alexa (beta).

In order to publish your custom task based skill then you'd start by submitting it for certification and publication as described in Skill Certification and Publication. And after it's certified and published to the live stage, your task will become part of the custom tasks catalog and will be available to use through Skill Connections.

When you're all set with the above, developers will be able to discover your task via the ASK CLI search-task and get-task commands only when the skill is live and you have set x-amzn-alexa-access-scope to public. If you interested about the command parameters, so you can do your own research on available custom tasks, please check this documentation page.

Wrap Up

I hope you liked this walk-through and that you start adding custom tasks to your skill right away. As developers contribute more and more custom tasks you'll be able to save time by leveraging a selection of task-enabled skills and getting more traffic from requester skills and Quick Links pointing to yours.
In order to get started please check out the custom Tasks demo skill in our Alexa Cookbook feature demos called Simple Horoscope and start building with custom tasks!
Don't hesitate to contact me via Twitter (@germanviscuso) if you have any questions.

Resources

Related Articles

Reach More Customers with Quick Links for Alexa (Beta) and New In-Skill Purchasing Options

Subscribe