Desarrollo de un agente conversacional con la herramienta Dialogflow

19 de julio de 2019

Introducción

Dialogflow es una herramienta para la construcción de interfaces de conversación mediante voz y texto que proporciona una nueva forma de interacción con productos y servicios. Esta herramienta, accesible en www.dialogflow.com, permite mediante una interfaz web la definición de todos los aspectos a controlar de una conversación: a qué se quiere responder, cómo se quiere responder, en qué idiomas, flujos de conversación, etc. La definición de este modelo de conversación es fácilmente exportable e integrable con herramientas ya existentes en el mercado como pueden ser el asistente de Google, Amazon Alexa, Telegram o, simplemente, su inserción en una página web.

En este artículo se presenta el agente AIMI, que proporciona información sobre mimacom y, más concretamente, sobre los proyectos en los que se está trabajando en una de sus oficinas: descripción de los proyectos, información sobre las personas que trabajan en ellos y tecnologías que se utilizan.

Se presenta en primer lugar el sistema a alto nivel que se ha construido, con una descripción de sus componentes, entrando a continuación al detalle de los aspectos más relevantes de Dialogflow y su interacción con el resto del sistema, objetivo principal de este artículo.

Arquitectura del sistema

El sistema que se ha desarrollado se compone principalmente de 3 piezas que se comunican entre sí mediante APIs REST. Estos componentes son:

Conversación mediante Dialogflow

Para entender qué problema soluciona Dialogflow es necesario explicar algunos conceptos básicos del modelo de diálogo que se define, para lo que lo más sencillo es introducir estos conceptos mediante ejemplos.

Lo más básico que se podría hacer con un agente desarrollado con Dialogflow sería, por ejemplo, saludarlo. Para ello, cualquier persona podría utilizar frases como:

Con estas frases se entrenará al agente para que reconozca un saludo. Este concepto (saludo) corresponde al de intent, que no es otra cosa que la intención, idea o propósito de lo que el usuario acaba de pronunciar (o introducir mediante un texto, según la interfaz que se esté utilizando). Así, un agente constará de una serie de intents definidos según el ámbito de la conversación, más o menos amplia según el escenario de que se trate. Por ejemplo, podríamos tener definido el intent "oficinas" en nuestro agente, que reconozca las siguientes frases:

Cuando el usuario se dirija a AIMI con una de estas frases o similar, el intent "oficinas" será reconocido y se responderá con alguno de los outputs definidos:

Por tanto, mediante técnicas de machine learning, Dialogflow entrenará a su agente y será capaz de identificar frases de sus futuros usuarios según la similitud que guarden con éstas. No obstante, rara vez un diálogo tiene frases tan cerradas como las presentadas hasta ahora, sino que alguna o varias partes de estas frases serán variables, introduciendo lo que se denominan slots. Por ejemplo, el usuario podrá indicar su nombre de las siguientes formas:

Ante la imposibilidad de generar frases de entrenamiento para todos los nombres existentes, Dialogflow permite definir frases de entrenamiento con slots de la siguiente forma:

Estos slots (en este caso @sys.given-name es un tipo de slot predefinido por Dialogflow) se asignarían a parámetros de cada intent (por ejemplo el parámetro $given-name), que se utilizará para dar respuesta al usuario definiendo outputs como los siguientes:

Dialogflow proporciona distintos tipos de slots que se pueden utilizar en las frases de entrenamiento. Como se acaba de mostrar, @sys.given-name correspondería a un nombre propio de los disponibles en Dialogflow, existiendo otros tipos de slots para fechas, ciudades, etc. Sin embargo, es muy probable que estos tipos de slots no sean suficiente para nuestro diálogo. Nuestro agente AIMI permite que el usuario le pregunte por distintos aspectos de proyectos desarrollados por mimacom; proyectos que no son asociados a ningún tipo de slot definido de Dialogflow. Para que AIMI reconozca correctamente estos slots deberemos crear una entidad, en este caso llamada projectName en la que introduciremos como opciones todos los nombres de proyectos sobre los que queremos dar información. Posteriormente, ya estaríamos en condiciones de crear un intent "Página web" que identifique que el usuario está pidiendo a AIMI la página web de un proyecto mediante las siguientes frases de entrenamiento:

Fulfillment

En el último ejemplo de la sección anterior se ha creado un intent que permite al usuario preguntar cuál es la página web de un proyecto. Para responder con preguntas que quedan fuera del sistema de información que forma parte de un agente conversacional, el agente debe utilizar servicios externos que le proporcionen dicha información. Esto es lo que se conoce como fulfillment: la acción de realizar una llamada a un servicio externo para darle respuesta al usuario.

Existen dos tipos de fulfillments:

slot filling

Tiene como objetivo generar una respuesta al usuario cuando en su intent faltan datos. Por ejemplo, si en el caso anterior el usuario preguntase "¿Cuál es la página web?", Dialogflow detectaría que le falta el parámetro @projectName, por lo que llamaría al servicio para generar una respuesta del tipo "¿A qué proyecto te refieres?". En el caso de faltar un único slot, Dialogflow podría generar esta pregunta sin problemas, siendo esta funcionalidad más útil cuando un intent necesita varios slots para ser respondido, de forma que el servicio externo pueda ir generando preguntas en las que se enumeren todos los slots que faltan.

obtención de información

Es el tipo de fulfillment por defecto y en el que nos centraremos, y tiene como objetivo dar una respuesta a la pregunta del usuario una vez que éste ha proporcionado un intent válido. Según la arquitectura presentada en la primera sección, agente AIMI enviaría una petición a nuestro servicio, que se apoyaría en su base de conocimiento para darle respuesta al usuario. En la siguiente sección se presenta con más detalle técnico este proceso.

AIMI ML: API y desarrollo del servicio

Veamos con un ejemplo práctico los detalles técnicos de Dialogflow para la gestión de un fulfillment. En los fragmentos de código que se muestran se omite la implementación de algunos métodos cuya finalidad queda lo suficientemente clara en el contexto en que se encuentran.

Al introducir una frase asociada a un intent habilitado para ser respondido mediante fulfillment, el agente AIMI enviará una petición POST al webhook que se haya definido para el agente, el cual extraerá la información necesaria para consultar a AIMI Store y devolver una respuesta

POST request

    {
      "responseId": "351a1171-8f38-4d2d-b929-28ed4663b6e5-64cfa233",
      "queryResult": {
        "queryText": "cuál es la web de ABCD?",
        "parameters": {
          "projectName": "ABCD"
        },
        "allRequiredParamsPresent": true,
        "fulfillmentMessages": [
          {
            "text": {
              "text": [
                ""
              ]
            }
          }
        ],
        "intent": {
          "name": "projects/agent-5d485/agent/intents/2ea6291d-f37a-446c-9300-4ab3cdd10f86",
          "displayName": "Homepage de proyecto"
        },
        "intentDetectionConfidence": 1,
        "languageCode": "es"
      },
      "originalDetectIntentRequest": {
        "payload": {}
      },
      "session": "projects/agenttest-5d485/agent/sessions/b2a4eeca-951b-f967-743b-e4ec5efb3cbc"
    }

Las partes más destacables de este intent son:

Webhook

A continuación se muestra el webhook que dará respuesta a la petición anterior, desarrollado para NodeJS/Express, y que es bastante sencillo:

    router.post('/', function (req, res, next) {
      if (isDialogflow(req)) {
        handleDialogFlowRequest(req, res);
      } else if (isAlexa(req)) {
        handleAlexaRequest(req, res);
      } else {
        res.sendStatus(400);
      }
    });

Como se ve, existen métodos que identifican a qué tipo de agente pertenece la petición req. Esto es debido a que un mismo webhook puede atender las peticiones de diferentes agentes (Dialogflow, Alexa, etc.). El método handleDialogflowRequest será el encargado de crear un WebhookClient (del API de Dialogflow) a partir de la request y response de esta petición. Se define un Map con los distintos intents a los que se quiere responder (correspondientes al queryResult.intent.displayName mostrado anteriormente) y el método handler encargado de gestionar dicha petición.

    function handleDialogflowRequest(req, res) {
      const agent = new WebhookClient({request: req, response: res});
    
      let intentMap = new Map();
      intentMap.set("Numero de proyectos", intentInfo["Numero de proyectos"].handler);
      intentMap.set("Nombre de proyectos", intentInfo["Nombre de proyectos"].handler);
      …
      intentMap.set("Homepage de proyecto",intentInfo["Homepage de proyecto"].handler);
      …
      agent.handleRequest(intentMap);
    };

Handler

El handler de cada intent retornará una Promise (podría haber código asíncrono que acceda a una base de datos o incluso a un servicio externo, como es nuestro caso, en el que atacamos a nuestra base de conocimiento que está expuesta en la constante aimiStorePath). El servicio dialogflowService construye la petición HTTP a partir de los parámetros del WebhookClient que se ha instanciado para atender esta request. Una vez obtenida la respuesta de este servicio, la envía al método buildResponse

    handler: function(agent) {
		return new Promise(function (resolve, reject) {
			var path = dialogflowService.buildPath(aimiStorePath, agent.parameters);
			http_utils.callAimiStore(path).then((data) => {
				buildResponse(agent, data);
				resolve('done');
			}).catch(function(reason) {
				agent.add(aimiStoreErrorResponse);
				resolve('done');
			});
		});
    }

Este método buildResponse simplemente construirá la respuesta a partir de los datos obtenidos del servicio consultado, y añadirá la respuesta al WebhookClient mediante el método add.

    buildResponse: function(agent, data) {
        if (data && data.length > 0) {
            agent.add(`La página web del proyecto es ${data}`);
        } else {
            agent.add('Lo siento, no tengo la URL de la web de este proyecto');
        }
    }

Respuesta

Este método genera una respuesta en formato JSON que será enviada al agente con el que el usuario está conversando. Este JSON sigue el formato definido por el API de Dialogflow.

    {
      "fulfillmentText": "La página web del proyecto es http://www.abcd.com/",
      "outputContexts": [
        {
          "name": "projects/agent-5d485/agent/sessions/b2a4eeca-951b-f967-743b-e4ec5efb3cbc/contexts/info-project",
          "lifespanCount": 15,
          "parameters": {
            "info-project:projectName": "ABCD"
          }
        }
      ]
    }

El agente mostrará o leerá (según su tipo de output) al usuario el contenido del atributo fulfillmentText

Resumen

En este artículo se ha presentado la arquitectura del sistema AIMI, que da respuesta a un usuario sobre los proyectos y tecnologías utilizados por mimacom. Se han explicado los conceptos fundamentales de un agente conversacional construido con Dialogflow, así como los aspectos técnicos de la implementación de un webhook que da respuesta a consultas complejas hechas sobre el sistema de información de la empresa.

Imagen de Brother UK bajo licencia CC By 2.0 - https://www.flickr.com/photos/brother-uk/39623894105

Sobre el autor: Pablo López

Desarrollador fullstack en mimacom, trabajando con Angular, React e Ionic en el front y Sprint Boot y Micronaut en el backend.

Comments
Únete a nosotros