Dallo scorso 30 ottobre anche in Italia è disponibile la gamma di Smart Speaker Echo di Amazon. Gli Echo (ma anche altri dispositivi compatibili) sono alimentati da Alexa , l’assistente vocale di Amazon. Oltre alle possibilità offerte dal sistema principale come impostare timer, fare ricerche su internet e cose di questo genere, è possibile aumentare le possibilità offerte da questa piattaforma tramite applicazioni di terze parti. Queste applicazioni nell’ecosistema Alexa sono chiamate Skills. Noi di Coding Jam abbiamo creato la nostra skill ufficiale, che per ora legge solo l’elenco degli ultimi articoli. Questa semplice skill sarà quella che analizzeremo in questo primo articolo sul mondo Alexa.

Per iniziare

Per poter sviluppare una Skill Alexa serve un account Amazon collegato alla Alexa Developer Console. Questa console web ci permette di creare una skill e modificarne sia le informazioni “anagrafiche” come nome, categoria che informazioni legate alla distribuzione come paesi in cui è disponibile e così via. Da qui è inoltre possibile sottoporre la skill ad Amazon per la pubblicazione. Funziona in maniera del tutto simile ad uno store di applicazioni mobile. Sempre dalla console è possibile modificare l’Interaction Model, un file JSON che indica come l’utente interagirerà con la nostra Skill. In pratica è lo scheletro della nostra conversazione.

Da un punto di vista operativo una skill Alexa è una Lambda di AWS. Quindi altro prerequisito per poter sviluppare delle Skill Alexa è quello di avere un account per Amazon Web Services.

Alexa Development Console

Tool da utilizzare

Per quanto riguarda le operazioni di creazione della Skill e di deploy ho deciso di utilizzare ask-cli, tool da riga di comando in NodeJS per lo sviluppo di Skill Alexa.

Una volta installato il tool con il comando

npm i ask-cli -g

Bisogna configurare la vostra macchina tramite il comando

ask init

E seguire le istruzioni che verranno fuori sulla console. A questo punto potete creare una nuova skill tramite il comando

ask new [nome_progetto]

Una volta creata l’applicazione base, potete entrare nella cartella appena creata e deployare la vostra applicazione tramite il comando

ask deploy

Il tool si occupa di pubblicare sia i dati relativi alla console Alexa che la Lambda e siete pronti a testare il tutto sul vostro device fisico. Una cosa importante da tenere a mente è che non è possibile testare in locale la Skill Alexa: dovete sempre passare per del codice in cloud. Esistono vari modi di testarla: dal dispositivo fisico, all’applicazione mobile passando per dei simulatori web. Il consiglio che vi do, dato il costo basso degli speaker è quello di utilizzare device fisico per capire bene i tempi di latenza reali.

Struttura di una Skill Alexa

La struttura di una Skill Alexa creata con ask-cli è la seguente.

Struttura directory di una skill Alexa

Si possono facilmente individuare i tre elementi di cui abbiamo parlato ad inizio post:

  • Il Manifest della skill nel file skill.json
  • Gli Interaction Model all’interno della cartella models
  • La Lambda che contiene il codice vero e proprio all’interno della cartella lambda/custom

Analizziamo nel dettaglio i tre elementi di una prima versione embrionale della skill di CodingJam che ci permette di leggere la lista degli ultimi articoli. Iniziamo con capire che informazioni sono presenti all’interno del Manifest. Quello che vedete è il file skill.json della nostra skill.

{
  "manifest": {
    "publishingInformation": {
      "locales": {
        "it-IT": {
          "summary": "La Skill ufficiale del blog CodingJam",
          "examplePhrases": [
            "Alexa, apri coding jam",
            "Alexa, avvia coding jam e leggi gli ultimi post",
            "Alexa, lancia coding jam"
          ],
          "keywords": [
            "Software",
            "sviluppo",
            "programmazione"
          ],
          "name": "CodingJam",
          "smallIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/8d98/c68e022243a44c00b317ce37e3238f23/APP_ICON?versionId=HJIUCawr2kPw4U59KnIG5EETtA3ZE6o.&AWSAccessKeyId=AKIAJFEYRBGIHK2BBYKA&Expires=1547203332&Signature=cUDRNzrTBn4MM%2FZTmdGnZuTLabs%3D",
          "description": "CodingJam è uno dei più famosi blog italiani sullo sviluppo software. Con questa skill potrai conoscere i titoli degli ultimi aggiornamenti e dare la possibilità di leggere un estratto dell'articolo più recente.",
          "largeIconUri": "https://s3.amazonaws.com/CAPS-SSE/echo_developer/f47d/ab9490f2d9434346ab2021c98a7c3781/APP_ICON_LARGE?versionId=hrAUEwr7URyHkZMYs.1PWssFoathGvd_&AWSAccessKeyId=AKIAJFEYRBGIHK2BBYKA&Expires=1547203332&Signature=%2B4x1koKaEqOqd7Kky%2BabjMlS%2F5A%3D"
        }
      },
      "isAvailableWorldwide": false,
      "testingInstructions": "Just follow the instructions in the launch request",
      "category": "NEWS",
      "distributionCountries": [
        "IT"
      ]
    },
    "apis": {
      "custom": {
        "endpoint": {
          "sourceDir": "./lambda/custom"
        }
      }
    },
    "manifestVersion": "1.0",
    "privacyAndCompliance": {
      "allowsPurchases": false,
      "isExportCompliant": true,
      "containsAds": false,
      "isChildDirected": false,
      "usesPersonalInfo": false
    }
  }
}

A parte il contenuto anagrafico, come il mercato in cui pubblicarla e la categoria della skill, l’informazione più importante è il path della Lambda che sarà il “cervello” della nostra skill. Il tool ask-cli si occuperà poi in autonomia durante il deploy di creare/aggiornare la Lambda e collegarla alla Skill.

Interaction Model

Iniziamo a entrare nel vivo della nostra skill, analizzando con l’Interaction Model. Il concetto alla base dell’Interaction Model di Alexa (ma anche di altri framework conversazionali) è l’Intent. Un intent rappresenta un’azione attivata dall’utente tramite un comando vocale. Ogni Intent ha almeno due elementi fondamentali. Il primo è il type che rappresenta quindi il tipo di operazione che l’utente vuole fare. La seconda sono i samples, ovvero una serie di frasi tipo che l’utente potrebbe utilizzare per attivare quel comando. Gli Intent possono essere di due tipi: predefiniti e custom. Quelli predefiniti sono legati ad operazioni basilari come “Si”, “No”, “Annulla”, etc., mentre quelli custom sono legati al nostro dominio applicativo. L’insieme degli Intent (e di altri elementi che esulano dallo scopo di questo primo post introduttivo) forma il nostro Interaction Model. Prima di analizzare l’Interaction Model della nostra skill ecco i due unici (per questa prima versione) scenari di utilizzo.

Caso 1

Utente: "Alexa, Apri CodingJam"
Alexa: "Benvenuto nella Skill Alexa di CodingJam. Puoi chiedere di leggerti i titoli degli ultimi articoli del nostro blog."
Utente: "Leggi gli ultimi post"
Alexa: "Ecco i titoli degli ultimi articoli di codingjam. [Elenco ultimi 4 Titoli]"

Caso 2

Utente: "Alexa, Apri CodingJam"
Alexa: "Benvenuto nella Skill Alexa di CodingJam. Puoi chiedere di leggerti i titoli degli ultimi articoli del nostro blog."
Utente: "Annulla"
Alexa: "Ok, alla prossima"

Il nostro Interaction Model è quindi formato da due Intent, il primo indica la volontà di leggere gli ultimi post mentre il secondo di annullare l’operazione e di uscire dalla Skill.

{
  "interactionModel": {
      "languageModel": {
          "invocationName": "coding jam",
          "intents": [
              {
                  "name": "ListIntent",
                  "slots": [],
                  "samples": [
                      "leggi gli ultimi titoli",
                      "leggi i titoli",
                      "leggi i titoli degli ultimi articoli",
                      "leggi i titoli degli ultimi post"
                  ]
              },
              {
                  "name": "AMAZON.CancelIntent",
                  "samples": []
              }
          ],
          "types": []
      }
  }
}

ListIntent è un intent custom in quanto legato al nostro dominio (la richiesta degli ultimi titoli), mentre il CancelIntent è un Intent predefinito, riconoscibile in quanto inizia con Amazon.*. In questo secondo caso infatti non sono richiesti dei samples in quanto Alexa li conosce già.

Lambda

Infine arriviamo alla parte di codice. Il nostro scopo ora è quello di creare una Lambda (io ho scelto di scriverla in JavaScript) che risponda alle richieste degli utenti, definiti dagli Intent all’interno del nostro Interaction Model. Per aiutarci utilizziamo l’SDK ufficiale di Amazon per NodeJS. Il codice della nostra Lambda è il seguente.

const Alexa = require('ask-sdk-core')

const LaunchRequestHandler = require('./handlers/launch')
const ListIntentHandler = require('./handlers/list')
const CancelIntentHandler = require('./handlers/cancel')

module.exports.handler = Alexa
  .SkillBuilders
  .custom()
  .addRequestHandlers(LaunchRequestHandler)
  .addRequestHandlers(ListIntentHandler)
  .addRequestHandlers(CancelIntentHandler)
  .lambda()

Come potete notare dallo snippet qui sopra, l’astrazione che l’SDK di Alexa fa per farci organizzare in maniera ottimale il codice è la divisione in Handlers. Abbiamo infatti un Handler per ognuno dei due Intent, più un Handler aggiuntivo per gestire la richiesta iniziale. Rispondere cioè a “Alexa apri CodingJam”. Iniziamo proprio da quest’ultimo Handler per capire qual è la struttura base di un Handler per una Skill Alexa.

const texts = require('../texts')

module.exports = {
  canHandle (handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest'
  },
  handle (handlerInput) {
    return handlerInput.responseBuilder
      .speak(texts.WELCOME)
      .reprompt(texts.REPROMPT)
      .getResponse()
  }
}

Un Handler quindi è formato da due metodi. Il primo chiamato canHandle ed il secondo chiamato handle. Il meccanismo è il seguente: per ogni richiesta dell’utente l’SDK cicla tutti gli Handler e seleziona il primo il cui metodo canHandle restituisce true. Dopodiché prende il risultato del metodo handle e lo invia al dispositivo.

In questo caso specifico questo Handler si attiva alla richiesta di apertura, e lo possiamo capire dal fatto che il tipo di richiesta è LaunchRequest e si limita a rispondere con un messaggio di benvenuto e ad esortarci a dare un comando dopo 8 secondi di silenzio da parte dell’utente tramite il metodo reprompt. Vediamo ora come si crea un Handle che risponde ad un Intent, iniziando dall’Intent AMAZON.CancelIntent.

const texts = require('../texts')

module.exports = {
    canHandle(handlerInput) {
        if(!handlerInput.requestEnvelope.request.type === 'IntentRequest'){
            return false
          }
      
        return handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent'
    },
    handle(handlerInput) {
        return handlerInput.responseBuilder
            .speak(texts.EXIT)
            .withShouldEndSession(true)
            .getResponse()
    }
}

Anche in questo caso il canHandle è molto semplice: controlliamo che la richiesta sia relativa ad un Intent e che l’intent da gestire sia di tipo AMAZON.CancelIntent. A differenza di prima qui non ci aspettiamo un ulteriore interazione con l’utente quindi chiudiamo la sessione con il metodo withShouldEndSession. Infine, ecco il codice che gestisce l’Intent per la lettura dei post.

const codingJam = require('../api/codingJam')
const texts = require('../texts')

const MAX_TITLES = 4

module.exports = {
  canHandle(handlerInput) {
    if(!handlerInput.requestEnvelope.request.type === 'IntentRequest'){
      return false
    }

    return handlerInput.requestEnvelope.request.intent.name === 'ListIntent'
  },
  handle(handlerInput) {
    return codingJam.getTitles().then(titles => {
      const titlesText = titles
        .slice(0, MAX_TITLES)
        .map((title) =&gt; `<p>${title}</p>`)
        .join(' ')

      const message = `
        <p>${texts.LAST_TITLE_INTRODUCTION}</p> ${titlesText}
      `

      return handlerInput.responseBuilder
        .speak(message)
        .withShouldEndSession(true)
        .getResponse()
    })
  }
}

Questo ultimo Handler funziona in maniera simile agli altri ma il messaggio non è un semplice testo, ma è formato da dei tag di un linguaggio di markup dedicato chiamato Speech Synthesis Markup Language (SSML). Tramite SSML possiamo pilotare molte sfaccettature del messaggio in uscita, come ad esempio la voce da utilizzare (in italiano è presente una seconda voce maschile che potete utilizzare nelle vostre skill), ma anche volume, tono, velocità etc etc. In questo caso utilizziamo un semplice tag <p> tra i vari post per indicare un paragrafo e lasciare quindi una pausa tra un titolo ed un altro.

Conclusioni

Con questo primo post ho voluto mostrare solo pochissimi elementi di una Skill Alexa, sicuramente seguiranno altri post che affronteranno altri problemi più specifici come gestione di variabili, internazionalizzazioni e cosi via. Utilizzeremo la nostra skill come base per questa serie di post, quindi se siete curiosi, vi invito a diventare beta tester della nostra skill o a proporci nuove features entrando nel canale #alexa del nostro slack. Come al solito il codice completo del progetto è su GitHub. Alla prossima.

30 Posts

Sono uno sviluppatore front-end, speaker e trainer per Flowing. Oltre a scrivere su vari blog (tra cui codingjam!) sono l'autore de libro "Frameworkless Front-end Development". Nel tempo libero mi piace cucinare cibo etnico e giocare ai videogame.