24/01/2021

Skill de Alexa II: Conectar nuestra skill con un servicio externo

En el post anterior de esta serie veíamos cómo crear una skill de Alexa sencilla, consistente en invocar nuestra skill y pedirle una cita célebre y que Alexa nos diera una respuesta que en esa primera versión de la skill era estática, una cita que nosotros mismos habíamos incluido en la lógica y que por tanto siempre era la misma. Ese post sirvió para mostrar los pasos para crear la skill desde cero así como qué componentes y elementos debemos manejar dentro del sistema de desarrollo de skills para Alexa por lo que es recomendable echar un vistazo a ese primer post antes de continuar con éste si es que no se tienen los conocimientos básicos del desarrollo de skills.

Así pues en este post, como siguiente paso, vamos a hacer que nuestra skill, en lugar de devolver una respuesta estática, se conecte a un servicio externo de citas célebres mediante una llamada HTTP para obtener esa cita famosa que nos pide el usuario en nuestro ejemplo. De esta forma la respuesta de Alexa será siempre distinta.

Conectando nuestra skill con un servicio externo mediante una llamada HTTP GET

Para conectar nuestra skill con APIs o servicios externos mediante llamadas HTTP necesitamos definir esa comunicación en la parte backend de nuestra skill, es decir, una vez dentro del Developer Console tenemos que acudir a la pestaña Code en la que están definidas nuestras funciones lambda que determinan la lógica y capacidades de nuestra skill. Para nuestro ejemplo vamos a recurrir a una API externa facilitada a través del servicio quotable que nos proporcionará diversos servicios para obtener citas célebres, entre ellos uno de citas célebres aleatorias que se adapta exactamente a nuestras necesidades.

Como ya adelantamos en el planteamiento de la skill vamos a usar, tanto para el asistente de voz como para el resto de servicios externos, el inglés como idioma de desarrollo para facilitar la integración y conseguir un resultado coherente.

1. Conociendo el servicio externo

En nuestro caso, como ya hemos dicho, la API de quotable nos proporciona un endpoint https://api.quotable.io/random mediante el cual podemos obtener citas famosas o célebres de forma aleatoria. Según vemos en la documentación de la API los detalles del servicio son:

  • Service: Get random quote Returns a single random quote from the database

  • Path: GET /random

  • Query parameters:

    • maxLength: Int The maximum Length in characters ( can be combined with minLength )
    • minLength: Int The minimum Length in characters ( can be combined with maxLength )
    • tags: String Filter random quote by tag(s). Takes a list of one or more tag names, separated by a comma (meaning AND) or a pipe (meaning OR). A comma separated list will match quotes that have all of the given tags. While a pipe (|) separated list will match quotes that have either of the provided tags.
  • Response:

      {
      _id: string
      // The quotation text
      content: string
      // The full name of the author
      author: string
      // The length of quote (number of characters)
      length: number
      // An array of tag names for this quote
      tags: [string]
      }
    

En nuestro ejemplo nos vamos a centrar en el servicio sencillo, sin usar query parameters, por tanto haremos una llamada /random que nos va a devolver entre otras cosas un author y un content que es la información que nos puede interesar devolver al usuario.

2. Llamando al servicio externo desde nuestra skill

Si recordamos los pasos de creación de nuestra skill elegimos un backend tipo Node.js, por lo que necesitaremos escribir la llamada HTTP en Javascript para que nuestro backend realice la llamada y procese la respuesta. Para ello vamos a crear un nuevo archivo dentro del proyecto de código de la skill, en nuestro caso lo llamamos getQuote.js pero se le puede dar otro nombre, lo único importante es que tenga extensión .js.

En ese archivo añadimos el código para realizar la llamada al servicio externo:

var https = require('https');

module.exports = function getQuote() {
  return new Promise(((resolve, reject) => {

    var options = {
        host: 'api.quotable.io',
        port: 443,
        path: '/random',
        method: 'GET'
    };

    const request = https.request(options, (response) => {

      response.setEncoding('utf8');
      let returnData = '';

      response.on('data', (chunk) => {
        returnData += chunk;
      });

      response.on('end', () => {
        resolve(JSON.parse(returnData));
      });

      response.on('error', (error) => {
        reject(error);
      });
    });

    request.end();

  }));

}

Como podemos observar estamos importando el módulo de Node.js llamado https para realizar llamadas http mediante la línea var https = require('https'). Luego creamos una función, que exportamos para que esté accesible desde otros puntos del backend, que se encarga de configurar la llamada al servicio externo anteriormente descrito, configurando el host api.quotable.io y el path /random, especificando que nuestra llamada va a ser de tipo GET. Una vez incluido el código en el nuevo fichero que hemos creado es momento de hacer uso de él desde nuestro index.js, en concreto dentro del Intent que se encarga de devolver la llamada.

Para ello volvemos al fichero index.js y modificamos el código para realizar la llamada que obtiene la cita desde el servicio externo:

  1. Añadimos la referencia al nuevo fichero que contiene la lógica que realiza la llamada y la asignamos a una variable que hemos llamado getQuote de cara a mantener el mismo criterio de nomenclatura:
const getQuote = require('./getQuote.js');
  1. Modificamos el bloque de código del Intent QuoteIntentHandler para sustituir la variable estática que devolvíamos hasta ahora con la llamada externa:
const QuoteIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'QuoteIntent';
    },
    async handle(handlerInput) {

        const response = await getQuote();

        return handlerInput.responseBuilder
            .speak(response.author + " Said: " + response.content)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

Como podemos ver simplemente usamos la función getQuote y para la respuesta de nuestra skill, mediante la función speak, construimos el mensaje extrayendo del objeto response que hemos recibido los atributos author, que contiene el nombre del autor, y content que contiene la cita en sí, tal como vimos en la documentación del servicio /random.

Finalmente hacemos Deploy de nuestra nueva funcionalidad y probamos el resultado en la pestaña Test:

Como vemos la respuesta de Alexa, además de ser dinámica basándose en el servicio de citas aleatorias, sigue la estructura de respuesta que hemos definido, mostrando author y content.

Enviando una petición a un servicio externo mediante HTTP POST

Hasta ahora hemos visto cómo hacer peticiones para obtener datos de un servicio externo mediante llamadas GET, que simplemente obtienen datos. Si lo que necesitamos es realizar una llamada POST (o PUT para más detalles sobre los distintos tipos de verbos HTTP consulta esto o esto), que son llamadas que se caracterízan no solo por recibir una respuesta sino por enviar datos en el body de la llamada, lo único que tendremos que hacer será, aparte de cambiar la configuración de la llamada, añadir el contenido de ese body.

A continuación muestro un ejemplo simple de cómo podríamos hacerlo. Imaginemos que vamos a llamar a un servicio al que le enviamos un nombre y un dni:

var https = require('https');

module.exports = function httpPost(name, dni) {
  return new Promise(((resolve, reject) => {

        var bodyContent = JSON.stringify({
            'name': name,
            'dni': dni
        });

    var options = {
        host: '[SOME-HOST]',
        port: 443,
        path: '/[SOME-PATH]',
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-Length': bodyContent.length
        }
    };

    const request = https.request(options, (response) => {

      response.setEncoding('utf8');
      let returnData = '';

      response.on('data', (chunk) => {
        returnData += chunk;
      });

      response.on('end', () => {
        resolve(JSON.parse(returnData));
      });

      response.on('error', (error) => {
        reject(error);
      });
    });

    request.write(bodyContent);
    request.end();

  }));

}

Tal como muestra el ejemplo definimos una variable bodyContent a la que asociamos un JSON con los datos de nombre y dni que recibe la función, en [HOST] pondríamos el host que contiene el servicio y en [PATH] el recurso de la API del servicio que va a recibir nuestra petición, igual que hemos hecho para el ejemplo con la llamada GET. Finalmente la línea request.write(bodyContent) se encargará de añadir nuestro body a la llamada.

En este caso, como hemos creado una función parametrizada la llamada que haremos desde el index.js deberá aportar esos parámetros:

    const response = await httpPost('John Doe', '29292929X');

Conclusión

En este post hemos visto cómo conectar nuestra skill de Alexa con un servicio externo tanto con llamadas GET como POST para aumentar la funcionalidad de nuestra skill y enriquecer las respuestas que Alexa da al usuario. En el siguiente post de la serie veremos cómo ampliar la funcionalidad de nuestra skill permitiendo que ésta sea capaz de memorizar datos que le aportamos por voz mediante el uso de Slots y cómo gestionarlos en el backend de nuestra skill.

Recursos utilizados