Desarrollando RIC Escape: una aventura conversacional para #GoogleAssistant

Desde hace tiempo tenía interés en ver qué se podía hacer con Google Assistant y qué tipo de apps se podían desarrollar. Durante estos últimos días he experimentado con ello con un objetivo concreto para ver el alcance de esta tecnología. ¿Sería posible hacer una aventura conversacional? ¿Cómo sería la experiencia de usuario?

Si llegaste aquí buscando la solución de RIC, la tienes en el siguiente post: https://medium.com/@itortv/soluci%C3%B3n-walkthrough-para-ric-escape-remoto-interfaz-de-conciencia-e0061234ef3c

Aquí os cuento cómo está a día de hoy el estado del arte de las apps de Google Assistant, y de cómo fui capaz de sacar un MVP en 3 intensos días, mezclando tecnología de Actions On Google, Dialogflow App, Firebase Functions y además haciendo enfoque en el diseño del código a través de TDD (con Mocha).

Puedes encontrar todo el código aquí: https://github.com/jmarti-theinit/ric-escape

RIC Escape

Es una aventura conversacional al viejo estilo. Te mueves entre habitaciones, y en cada una puedes “mirar”, “usar”, “ir”, “coger” e “inventario”. Con un añadido tipo escape room, y es que debes conseguir escapar en menos de 35 minutos.

Me llamaba la atención la idea de conseguir 35 minutos puros de flow, obligando a que la partida fuera ininterrumpida, y todo ello solo hablando con una máquina. No se puede salvar la partida: o la resuelves o mueres. Como un escape room (Eso sí, la puedes repetir, claro).

¿La historia? Te despiertas en una nave espacial, sin memoria alguna. Realmente no estás despierto, porque no te puedes mover. Pero estás “conectado” a un robot “RIC” al que le das comandos para que se mueva e interactúe con la nave espacial por ti. Y en 35 minutos la nave especial impactará contra una estrella, si no haces nada para evitarlo. La nave espacial lleva a otros 2.000 pasajeros, que se encuentran dormidos. Tú eres el único “que ha despertado”, y eres la única esperanza para salvar los 2.000 pasajeros de la nave.

¿Cómo habéis llegado a esa situación? ¿Qué puedes hacer para resolverlo?

Tienes 35 minutos.

Pequeño spoiler: realmente existe una historia detrás con su plot twist y todo, de la cual estoy muy orgulloso.

Estado del arte de las apps en Google Assistant

Con Google Assistant, en teoría puedes hacer cosas tan geniales como “Búscame un Uber para la dirección X”, o “Enciende la luz del salón”. ¿Mola, verdad? Pues espérate, que en español todavía no. Esa fue mi mayor decepción. Tras hacer todo el trabajo en esos 3 días, y conseguir la publicación de la app en otros 3, descubrí que la gente no podía hacer uso de la app porque las apps de Google Assistant en Español no están todavía disponibles.

Es decir, Google Assistant sí está disponible. Lo que no está disponible es la opción de usar apps de otros desarrolladores sobre Google Assistant en español.

¿Significa que no puedo probar las apps que hago? Sí, los miembros del proyecto sí pueden usarlo. No puede usarlo “gente corriente”. Así que lo que hago ahora es agregar betatesters a la aplicación, dándoles permiso de lectura. Tengo una lista de casi 20. Si quieres ser un betatester de esta aplicación, ¡escríbeme y házmelo saber de inmediato!

¿Cómo se usa una app de Google Assistant?

Hay dos formas de invocar una app: de forma explícita o de forma implícita.

  • De forma explícita: “Hablar con el asistente de Uber”.
  • De forma implícita: “Buscar un taxi para ir al sitio X”.

En el caso que yo desarrollé, RIC Escape, sólo he activado la apertura de la app de forma explícita. No he experimentado todavía cómo se trabajaría de forma implícita, y tampoco tengo tan claro qué funcionalidad le asignaría.

En mi caso, por tanto, la app se abre con el comando “Hablar con Remoto Interfaz de Conciencia” (las siglas de RIC).

Qué nombre tan largo, ¿verdad? Sí, ese fue otro de los problemas: cómo encontrar un nombre único, pero transcribible. Mi primera opción era “RIC Escape”, pero al decirlo al móvil, me lo transcribía como “Rick Escape”, y por tanto, la aplicación no se abría y mi app fue rechazada.

Además, todas las apps que se abran de forma explícita se deben abrir de forma “Hablar con…”, “Conversar con…”, “Contactar con…”. Es decir, el nombre de la app se espera que tenga un nombre tipo asistente… “Hablar con Asistente de Uber”, o “Hablar con Centralita Uber”…

Componentes en RIC Escape para Google Assistant

En RIC Escape se mezclan las siguientes tecnologías:

  • Actions On Google: el motor para definir, crear y publicar apps en Google Assistant. “Build apps for the Google Assistant even if you don’t know how to code”. La equivalente a la consola de Play Store de Android para desarrolladores. A través de su consola puedes crear las diferentes apps, definir su ficha, ver analíticas de uso y descubrimiento en “el market”, y un simulador de Google Assistant para probar la app. Para editar o incluir código de la app, AoG te propone usar “Dialogflow”.
  • Dialogflow: Dialogflow es un editor visual de conversaciones, y quien tiene la inteligencia de dirigir el flujo de una conversación. Las conversaciones, al ser parseadas, crean intents, que invocan a tu aplicación. Los intents derivan de acciones o eventos. Es decir, si tú dices “usar objeto1 con objeto2”, el motor de Dialogflow crea un intent con acción “usar”, y con argumentos “objeto1” y “objeto2”. Todo ello definible visualmente. Además, existen “entidades”, que son como el “tipo del argumento”. Puedes definir argumentos de tipo numérico, de tipo ciudad, de tipo teléfono, etc. Del modo que ayuda al agente entrado de las conversaciones a saber qué intent es el que se está invocando.
    También tiene el concepto de “Contextos”, que te permite saber si un intent ha sido llamado después de otro o no.
    Por ejemplo, permite cosas como que digas “Usar”, el asistente te responda “¿Qué quieres usar con qué?” y tú indiques “objeto1 con objeto2”. En la segunda invocación podrás detectar que el contexto es el mismo, y por tanto, la acción es “Usar” (esto último no está implementado pero es la lógica evolución de la app a medio plazo, para crear conversaciones más naturales con RIC).
Definiendo el parseo de las acciones en Dialogflow
  • DialogflowApp webhook: desde la consola de Dialogflow se puede indicar que, o bien el intent responde un texto determinado, o bien responde el resultado de invocar a una webhook, es decir, una lógica colgada en una web. Dialogflow te facilita que hagas esto colgando una app en Firebase Functions, desarrollando éstas en nodejs, ya que te facilita además, una librería DialogFlowApp para nodejs. Puedes utilizar webhooks expuestos en otros servidores, e incluso en otros lenguages; pero en este último caso, deberías entonces estudiar y analizar el protocolo de intercambio (en JSON) para el webhook. En mi caso, al utilizar su librería nodejs, fue totalmente transparente para mi.
    A este endpoint le llega algo tan sencillo como una request con parámetros “acción” y “argumentos”, que, a través de código js, provocan respuestas de tipo texto (en mi caso). Existen otro tipo de respuestas (RichResponses) que permiten añadir tarjetas visuales, imágenes y enlaces.
    Todo el código generado en nodejs con la librería DialogflowApp, fue desarrollado mediante TDD (con Mocha). Para ello, tuve que generar un mock del DialogflowApp, ya que no encontraba ninguno. Mi mock en concreto solo expone las funcionalidades de la interfaz que yo utilizo; por ejemplo, no maneja bien las RichResponse, pero no es alcance de mi funcionalidad. Tienes todo este código en: https://github.com/jmarti-theinit/ric-escape

A modo de resumen, en cada una de estas tecnologías, se desarrolla lo siguiente y con el siguiente “volumen” del trabajo total.

  • Actions On Google: crear la app y vincularla con tu DialogFlow. 5%.
  • Dialogflow: crear intents y acciones, y llamar al webhook. 10%.
  • Firebase functions en nodejs: desarrollo del código fuente que dada una acción y unos argumentos, devuelve una respuesta. 85%.

Dialogflow

A modo de curiosidad, muestro aquí los diferentes Dialogflow creados. En un futuro éstos podrían expandirse para crear conversaciones más naturales, desarrollando principalmente los concepto de entidades y contextos.

Código de los webhooks / firebase functions

El punto de entrada de estas Firebase Functions es el siguiente: https://github.com/jmarti-theinit/ric-escape/blob/master/index.js

En él, se define qué función se ejecuta por cada uno de los intents:

Esto me ha llevado a estructurar el código en que el procesado de cada uno de estos intents es responsabilidad de su función y fichero correspondiente.

funciones que procesan los intent — https://github.com/jmarti-theinit/ric-escape/tree/master/intents

Y cada uno de estos ficheros donde se procesa un intent, tiene una forma más o menos similar (la más compleja es quizás use, que acepta dos argumentos).

función “walk.js”. — https://github.com/jmarti-theinit/ric-escape/blob/master/intents/walk.js

Es crucial el uso de “app.data”, pues permite mantener el contexto de toda la conversación (de lo que ocurre en esos 35 minutos) sin necesidad de realizar nada adicional. Es decir, si tú en una request determinada, colocas algo en el objeto app.data, este elemento estará disponible al leer “app.data” en requests siguientes. De este modo es muy fácil mantener la persistencia de la sesión / conversación.

He llamado SCURE al motor de procesado de la aventura gráfica. En honor a SCUMM (Script Creation Utility for Maniac Mansion), yo bauticé al mío como SCURE (Script Creation Utility for Ric Escape).

Y es que uno de los efectos que enseguida ocurrió al trabajarlo con TDD (mejor dicho, en este caso creo que lo enfoqué más con BDD), es que enseguida vi la necesidad y oportunidad de desacoplar (1) código relacionado con DialogflowApp (parseo intents y argumentos y doy respuestas), y (2) código relacionado con el parseo de la aventura conversacional (el SCURE en sí). Es decir, SCURE está totalmente desacoplado del concepto de Google Assistant y podría utilizarse el mismo código en consola, por ejemplo.

Es por ello, que tenemos también, por cada acción de estos intents, su correspondiente función en scure.js.

funciones SCURE que ejecutan la aventura conversacional

El “contenido” de la aventura conversacional se encuentra situado en un único fichero JS, a modo de DSL, que contiene los siguientes elementos:

  • Rooms: las habitaciones en las que se puede estar.
  • Map: representa de qué habitación a qué habitación se puede uno dirigir.
  • Items: los diferentes elementos (y si se pueden llevar o no).
  • Usages: qué elementos se pueden usar solos, y qué elementos se pueden usar con otros, y qué acciones ocurren.
  • Sentences: las frases estándar que puede comentar el robot RIC.

Además, he implementado cosas como:

  • Descripciones condicionales: hay determinadas descripciones o respuestas que dependen de si has cogido un objeto con anterioridad o no, o si has realizado determinadas acciones anteriormente (locks).
  • Locks: las acciones pueden ser de tipo “unlockActions”, que desbloquean determinadas descripciones u objetos. Como por ejemplo, al abrir un armario con una llave, se desbloquea el lock “opencloset”, y determinadas descripciones revisan si está unlockeado el concepto “opencloset”.
Cuidado con leerte este fichero que te spoilearás: https://github.com/jmarti-theinit/ric-escape/blob/master/ric-escape-data.js

Gracias al motor SCURE y al formato bastante sencillo del “ric-escape-data.js”, es evidente que la posibilidad de expandir y convertir este desarrollo en un “repositorio de aventuras conversacionales para Google Assistant” está al alcance, pues en el fondo esto todo es una SDK. Yo mismo, si veo que RIC tiene interés, podría crear en menos de un día su segunda parte. Pero más interesante me parece la oportunidad de crear un gran repositorio de aventuras entre todos: si quisieras hacer una, sería genial que te pusieras en contacto conmigo y lo habláramos.

Estoy muy orgulloso también de la forma de los tests, que creo que son bastante legibles:

Test para confirmar que un objeto no puede ser usado si no estoy en la habitación donde está dicho objeto

Inicialmente, añadir todo el mock del test me supusieron 2–4 horas de dolor que constantemente me planteaba si merecía la pena o si iba a poder llevarme a algo. Me pregunto que si hubiera sido código en producción para un cliente, con fecha límite para ayer, quizás hubiera tirado la toalla antes.

Lo curioso es que una vez montado todo eso, el desarrollo se agilizó enormemente. Pues probarlo sino hubiera sido muy doloroso: cada vez que se hace un cambio, se debe deployar a las firebase functions, ir al simulador, ejecutar la aplicación y volver al punto donde estabas antes… Con los tests, el feedback, por supuesto, era inmediato.

Definitivamente, ejecutarlo con TDD es lo que me permitió hacerlo en tiempo récord.

Una de las dudas que me surgió es si era bueno hacer los tests sobre el verdadero “ric-escape-data.js” o debía utilizar un “ric-escape-data-tests.js” para que los tests no dependieran de la aventura final. Creo que fue muy buena opción mantener la primera (a pesar de que todas las señales me indicaban que debía ser la segunda), ya que me permitía entender con mucha más claridad cuál era el siguiente test que necesitaba hacer. Es decir, si en la historia debía añadir “usar objeto 1 con objeto 2”, pero todavía mis tests no permitían usar dos objetos entre sí, esta necesidad salía mucho más natural al hacerlo directamente sobre el fichero real.

Y un efecto “colateral” que ocurrió al hacerlo así, fue el desarrollo de un test de aceptación / funcional, que me da la fiabilidad de que, aun haciendo cualquier cambio, el juego se puede finalizar. Y no solo eso, sino que permite documentar la sucesión de comandos que se deben realizar para finalizar el juego. ¿No es eso un walkthrough? Efectivamente, mi libro de pistas está escrito en código. Eso sí, cuidado si lo lees: OJO SPOILER.

Conclusiones

Gracias a la capacidad de desarrollar apps sobre Google Assistant, tengo una app (por ahora no abierta al público :( y sin fecha de salida) que permite jugar una aventura conversacional durante 35 minutos, en la que considero una historia entretenida e interesante.

Estoy bastante orgulloso del código generado, totalmente desarrollado mediante BDD, y que me ha proporcionado:

  • Una SDK, con el motor SCURE y con su propio DSL (sencillo, liviano y en javascript) para la generación de nuevas aventuras conversacionales. ¿Te interesa?
  • Aprendizaje sobre el funcionamiento del parseo de conversaciones en Google Assistant, contextos y entidades.
  • Varios insights sobre acoplamiento a la hora de desarrollar y separar las funciones.
  • Un efecto colateral curioso: el walkthrough del juego escrito en código.

¿Y sabes cuánto me llevó todo esto? El 95% de todo ello lo hice en aproximadamente 3 días. Gracias a TDD.

Dos últimas notas

Y no quiero dejar de aprovechar la oportunidad para repetir lo siguiente:

  • Ya que la app no está abierta al público, busco betatesters. Si quieres jugar la app, contáctame. A cambio te pediré que al terminar me hagas una pequeña reflexión de qué te ha gustado y qué no.
  • Me encantaría aprovechar el motor para generar una segunda aventura conversacional. De hecho, este juego lo hice pensando en lo que me gustaría que existiera, por lo que si te atreves, también, ponte en contacto conmigo.

Para cualquiera de los dos casos, puedes bien escribirme aquí o por twitter @itortv.

¡Gracias!

Aprendiendo y creciendo a través de equipos que desarrollamos software. Trabajo en @grupo_init

Aprendiendo y creciendo a través de equipos que desarrollamos software. Trabajo en @grupo_init