lunes, 27 de mayo de 2019

mensaje enriquecidos con dialogflow


Mensajes enriquecidos (Rich Messages) con DialogFlow

Ya hemos visto una introducción a dialogflow y si no lo has hecho te sugiero revisarlo, ya que emplearemos el mismo ejemplo, pero ahora vamos a ver como enriquecer las respuestas que brinda nuestro bot con algo más visual.

Para entrar en contexto acerca de lo que hablamos, recordemos cual fue el resultado obtenido en la introducción a dialogflow.





Tipos de mensajes enriquecidos


Dialogflow cuenta con varios tipos de mensajes enriquecidos, pero no todos son compatibles para lo que podemos llamas respuestas simples, existen mensajes que solo funcionan acciones de google (ACTIONS ON GOOGLE) que permiten una mayor interacción con el usuario, las acciones de google son aquellas que con son empleadas con el asistente de google, aquel que podemos invocar la frase "Ok google" en nuestro teléfono android.


Vamos a ver la lista de mensajes enriquecidos de dialogflow y cuales funcionan solo con las acciones de google.


Tipo de mensaje ACTIONS ON GOOGLE
text No
image No
quickReplies No
card No
payload Si
simpleResponses No
basicCard Si
suggestions Si
linkOutSuggestion Si
listSelect Si
carouselSelect Si


Para más información acerca de los tipos de mensaje enriquecidos de dialogflow; consultar mensajes dialogflow


Para poder ejemplificar los mensajes enriquecidos en dialogflow pensaba mostrar información general de la ciudad que deseáramos, alguna imagen y quizás algunos links a la información, sin embargo me resulto difícil encontrar una API gratis que me pudiera proporcionar dicha información (Un resumen y alguna imagen), y no por que exista una API que brinde dicha información, si no porque su información era limitada y no se encontraban todas las ciudades, soló las más populares o de algunas zonas del planeta, lo que las volvía poco practicas para nuestro objetivo.

pero quiero mencionar que dentro de mi búsqueda encontré la API de Google Places la cual se e hizo muy interesante, si bien dicha API cuenta con una versión de prueba mediante un crédito de $300, pero requiere habilitar la facturación mediante una tarjeta de crédito, si bien ,no se realiza ningún cargo a menos de que se agote el crédito, esto puede resultar un inconveniente para algunos, y por ello mismo la he descartado.



Entonces la dinámica para nuestro agente sera enumerar los diferente tipos de mensajes, y pedir al bot que no muestre un ejemplo del mensaje en base a su numeración






Creando un Intento (Intent)

Vamos a utilizar el mismo agente creado en nuestro ejemplo anterior y vamos a crear un intento y lo llamaremos mensajee_intent y agregaremos las siguientes frases de entrenamiento (Training phrases)

  • Ejemplo numero
  • Dame el ejemplo numero
  • Muéstrame el ejemplo numero




Guardamos cambios, agregaremos un parámetro llamado opcion  de tipo @sys.number que nos indique el número de ejemplo que queremos ver y sera obligatorio (Riquiered)





Posteriormente seleccionamos la palabra numero y le asignamos el parámetro opcion en cada una de las frases de entrenamiento, para que de esta manera podamos acceder a el desde nuestro servicio en node








Como estamos usando el ejemplo anterior donde ya esta configurado el webhoook por lo que omitiré dicha parte, y solo les recordare que de deben habilitar el webhook en intento (Intent),  y pasaremos directamente al código que debe correr en node.






Con el fin de facilitar las cosas vamos a declarar un variables por cada tipo de mensaje con su estructura básica, empezando por la respuesta principal que envía nuestra aplicación a nuestro agente en dialogflow


function respuestaMensaje(){
this.fulfillmentText = '';
this.fulfillmentMessages = [];
}



y una vez agregados los tipos de mensajes obtendríamos el siguiente código


var express = require('express');
var bodyParser = require('body-parser');
var request = require('request');
var app = express();
//El cuerpo de solicitudes recibidas sera fomateado con formato JSON
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
function respuestaMensaje(){
this.fulfillmentText = '';
this.fulfillmentMessages = [];
}
function mensajeText(){
this.text = {
text: []
};
}
function Image(imageUri, accessibilityText){
this.image = {
imageUri : imageUri,
accessibilityText : accessibilityText
}
}
function mensajeQuickReplies(){
this.quickReplies = {
title : '',
quickReplies : []
}
}
function mensajeCard(){
this.card = {
title : '',
subtitle : '',
imageUri : '',
buttons : []
}
}
function Buttons(text, posback){
this.text = text;
this.postback = posback;
}
function Button(title, uri){
this.title = title;
this.openUriAction = {
uri: uri
}
}
function mensajeSimpleResponse(){
this.simpleResponses = {
simpleResponses : []
}
}
function SimpleResponses(textToSpeech, ssml, displayText){
this.textToSpeech = textToSpeech;
this.ssml = ssml;
this.displayText = displayText;
}
function mensajeBasicCard(){
this.platform = 'ACTIONS_ON_GOOGLE';
this.basicCard = {
title : '',
subtitle : '',
formattedText : '',
image : {},
buttons : []
};
}
function mensajeSuggestions(){
this.platform = 'ACTIONS_ON_GOOGLE';
this.suggestions = {
suggestions : []
};
}
function suggestion(title){
this.title = title;
}
function mensajelinkOutSuggestion(destinationName, uri){
this.platform = 'ACTIONS_ON_GOOGLE';
this.linkOutSuggestion = {
destinationName : destinationName,
uri : uri
};
}
function mensajelistSelect(title){
this.platform = 'ACTIONS_ON_GOOGLE';
this.listSelect = {
title : title,
items : []
};
}
function item(info, title, description, image){
this.info = info;
this.title = title;
this.description = description;
this.image = image;
}
function SelectItemInfo(key, synonyms){
this.key = key,
this.synonyms = synonyms
}
function mensajecarouselSelect(){
this.platform = 'ACTIONS_ON_GOOGLE';
this.carouselSelect = {
items : []
};
}


El siguiente paso es identificar el ejemplo solicitado en nuestro servicio en node y construir el ejemplo solicitado y responder a nuestro agente. para ello nuestro programa empleara un switch que evalué las opciones y responda según sea el caso, veamos el siguiente código terminado para ver el funcionamiento.


var express = require('express');
var bodyParser = require('body-parser');
var request = require('request');
var app = express();
//El cuerpo de solicitudes recibidas sera fomateado con formato JSON
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
function respuestaMensaje(){
this.fulfillmentText = '';
this.fulfillmentMessages = [];
}
function mensajeText(){
this.text = {
text: []
};
}
function Image(imageUri, accessibilityText){
this.image = {
imageUri : imageUri,
accessibilityText : accessibilityText
}
}
function ImageCarousel(imageUri, accessibilityText){
this.imageUri = imageUri;
this.accessibilityText = accessibilityText;
}
function mensajeQuickReplies(){
this.quickReplies = {
title : '',
quickReplies : []
}
}
function mensajeCard(){
this.card = {
title : '',
subtitle : '',
imageUri : '',
buttons : []
}
}
function Buttons(text, posback){
this.text = text;
this.postback = posback;
}
function Button(title, uri){
this.title = title;
this.openUriAction = {
uri: uri
}
}
function mensajeSimpleResponse(){
this.simpleResponses = {
simpleResponses : []
}
}
function SimpleResponses(textToSpeech, ssml, displayText){
this.textToSpeech = textToSpeech;
this.ssml = ssml;
this.displayText = displayText;
}
function mensajeBasicCard(){
this.platform = 'ACTIONS_ON_GOOGLE';
this.basicCard = {
title : '',
subtitle : '',
formattedText : '',
image : {},
buttons : []
};
}
function mensajeSuggestions(){
this.platform = 'ACTIONS_ON_GOOGLE';
this.suggestions = {
suggestions : []
};
}
function suggestion(title){
this.title = title;
}
function mensajelinkOutSuggestion(destinationName, uri){
this.platform = 'ACTIONS_ON_GOOGLE';
this.linkOutSuggestion = {
destinationName : destinationName,
uri : uri
};
}
function mensajelistSelect(title){
this.platform = 'ACTIONS_ON_GOOGLE';
this.listSelect = {
title : title,
items : []
};
}
function item(info, title, description, image){
this.info = info;
this.title = title;
this.description = description;
this.image = image;
}
function SelectItemInfo(key, synonyms){
this.key = key,
this.synonyms = synonyms
}
function mensajecarouselSelect(){
this.platform = 'ACTIONS_ON_GOOGLE';
this.carouselSelect = {
items : []
};
}
//función donde se procesan las solitudes de clima
app.post('/mensajes', function (req, res) {
var opcion = req.body.queryResult.parameters.opcion;
console.log('Opción número: ' + opcion);
var respuesta = new respuestaMensaje();
switch(opcion){
case 1: //Text
var texto = new mensajeText();
texto.text.text.push('Opción uno, mensaje enriquecido Text 1 de 2');
texto.text.text.push('Opción uno, mensaje enriquecido Text 2 de 2');
respuesta.fulfillmentMessages.push(texto);
break;
case 2://Image
var imagen = new Image('https://assistant.google.com/static/images/molecule/Molecule-Formation-stop.png', 'Imagen de prueba');
respuesta.fulfillmentMessages.push(imagen);
break;
case 3://quickReplies
var respuestas = new mensajeQuickReplies();
respuestas.quickReplies.title = 'Opción 3 QuickReplies, selecciona una opción';
respuestas.quickReplies.quickReplies.push('Unos');
respuestas.quickReplies.quickReplies.push('Dos');
respuesta.fulfillmentMessages.push(respuestas);
break;
case 4://card
var tarjeta = new mensajeCard();
tarjeta.card.title = 'Opción 4, Card';
tarjeta.card.subtitle = 'Subtitulo, opción 4';
tarjeta.card.imageUri = 'https://assistant.google.com/static/images/molecule/Molecule-Formation-stop.png';
tarjeta.card.buttons.push(new Buttons('Boton uno', ''));
tarjeta.card.buttons.push(new Buttons('Boton dos', ''));
respuesta.fulfillmentMessages.push(tarjeta);
break;
case 5://simpleResponses
var respuestaSimple = new mensajeSimpleResponse();
respuestaSimple.simpleResponses.simpleResponses.push(new SimpleResponses('Opción 5, simpleResponses', 'Opción 5, simpleResponses', 'display text'));
respuesta.fulfillmentMessages.push(respuestaSimple);
break;
case 6://basicCard
var tarjetaBasica = new mensajeBasicCard();
tarjetaBasica.basicCard.title = 'Opcion 6, basic card';
tarjetaBasica.basicCard.subtitle = 'subtitulo';
tarjetaBasica.basicCard.formattedText = 'Texto con formato';
tarjetaBasica.basicCard.image = new Image('https://assistant.google.com/static/images/molecule/Molecule-Formation-stop.png', '').image;
tarjetaBasica.basicCard.buttons.push(new Button('Boton uno', 'https://www.google.com/'));
tarjetaBasica.basicCard.buttons.push(new Button('Boton dos', 'https://es.wikipedia.org'));
respuesta.fulfillmentMessages.push(tarjetaBasica);
break;
case 7://suggestions
var sugerencias = new mensajeSuggestions();
sugerencias.suggestions.suggestions.push(new suggestion('Opción 7, sugerencia uno'));
sugerencias.suggestions.suggestions.push(new suggestion('Opción 7, sugerencia dos'));
respuesta.fulfillmentMessages.push(sugerencias);
break;
case 8://linkOutSuggestion
var linkSugerencias = new mensajelinkOutSuggestion('Google', 'www.google.com');
respuesta.fulfillmentMessages.push(linkSugerencias);
break;
case 9://listSelect
var lista = new mensajelistSelect('Ejemplo 9, listselect');
lista.listSelect.items.push(new item(new SelectItemInfo('1', ['Uno', 'One']), 'Ejemplo 9',
'listSelect', new Image('https://assistant.google.com/static/images/molecule/Molecule-Formation-stop.png', 'Google'))
);
respuesta.fulfillmentMessages.push(lista);
break;
case 10://carouselSelect
var carousel = new mensajecarouselSelect();
carousel.carouselSelect.items.push(new item(new SelectItemInfo('1', ['Uno', 'One']), 'Ejemplo 9',
'Carousel 1', new ImageCarousel('https://assistant.google.com/static/images/molecule/Molecule-Formation-stop.png', 'Google'))
);
carousel.carouselSelect.items.push(new item(new SelectItemInfo('2', ['Dos', 'Two']), 'Ejemplo 9',
'Carousel 2', new ImageCarousel('https://assistant.google.com/static/images/molecule/Molecule-Formation-stop.png', 'Wikipedia'))
);
respuesta.fulfillmentMessages.push(carousel);
break;
default:
respuesta.fulfillmentText = 'Opción ivalida, ingresa una del 1 al 10';
break;
}
//Variable tipo JSON para guardar la respusta a enviar al agente
res.json(respuesta);
});
//Bucle indefinido escuhando el puerto 3000, a la espera de peticiones
app.listen(3000, function () {
console.log('App escuchando puerto 3000');
});


Para probar el código recuerden correr el comando node app.js y tener en ejecución al aplicación de ngrok con el comando ngrok http 3000 recuerda que de ser necesario debes actualizar la url pública en el webhook del agente para que funcione correctamente.
si tienes duda en este ejemplo puedes ver la compilación  y ejecución del código del ejemplo anterior


Si hemos hecho todo bien hasta este momento, deberíamos obtener resultados como los siguientes.


Ejemplo 1 (Text)





Ejemplo 3 (QuickReplies)






Ejemplo 4(Card)




En el caso de los mensajes enriquecidos que deban ser establecidos como ACTIONS_ON_GOOGLE debemos cambiar como nuestro agente nos muestra la respuesta default como en la siguiente imagen





De tal forma que obtendríamos un ejemplo como el siguiente





Como pueden ver los mensajes enriquecido en dialogflow abren una nueva posibilidad para el desarrollo de nuestros bot(s) ya que nos permiten una mayor interacción con el usuario, ya que podemos mostrarle opciones y esperar una respuesta generando una verdadera interacción. sin duda la profundización de este tema para otro post, donde podamos pedir que elija entre varias opciones y en base a su respuesta poder continuar con el proceso para lo que este diseña el bot.


También quiero terminar recordandoles que la información de diagnostico (DIAGNOSTIC INFO) es muy útil para encontrar problemas cuando empleamos webhook en nuestro agente.


Por ejemplo la siguiente pantalla nos muestra que la ejecución del webhook fue correcta




En el caso de que hubiese un problema con nuestro servicio en node, la comunicación entre nuestro agente y el servicio que estamos corriendo o algún otro error, nos mostraría un error en esta parte.


Ademas de ello podemos evaluar el mensaje que recibe nuestro agente en la pestaña de FULFILLMENT RESPONSE donde podremos ver el mensaje y si esta bien estructurado y la información que contiene





En este ejemplo solo he empleado dos tipos de respuestas, Default y ACTIONS_ON_GOOGLE para indicar la plataforma de destino donde se empleara la respuesta, la cual puede ser una de las siguiente:



  • PLATFORM_UNSPECIFIED
  • FACEBOOK
  • SLACK
  • TELEGRAM
  • KIK
  • SKYPE
  • LINE
  • VIBER
  • ACTIONS_ON_GOOGLE
  • GOOGLE_HANGOUTS

De tal manera que podemos enviar un mensaje enriquecido dependiendo de la plataforma donde este implementado nuestro bot.




Espero les sea de utilidad este post.

viernes, 17 de mayo de 2019

bot para el clima con dialogflow y node

Vamos a ver una introducción a dialogflow creando un bot para consultar el clima.


Quizás algunos ya conocen o han oído hablar de dialogflow, un servicio basado en la de nube desarrollado por Google que nos permite crear bot's de una manera rápida y sin necesidad de tener conocimientos avanzados en múltiples áreas.

La utilidad de un bot son varias, ya que podemos delegar las tareas repetitivas, predictivas y de poco valor para un proceso, por ejemplo, tomar un pedido, brindar información de horarios, agendar una cita, consulta de saldo, consultar el clima, etc.

Es aquí donde entra DialogFlow que nos permite llevar dicha tarea a cabo con relativa facilidad e implementarla en múltiples medios, pero no entrare en mucha teoría, y seré practico.

Bot para el clima

Vamos a DialoFlow console previo registro, así que si no lo has hecho, te invito a realizarlo.

Lo primero que vamos a realizar es crear un nuevo agente, en donde un agente es modulo de procesamiento de lenguaje natural (NLU), o dicho de otra forma, es la forma de agrupar un proyecto

En el menú del lado izquierdo desplegamos el menú y seleccionamos crear nuevo agente (Create new agent).

Posteriormente le asignamos un nombre y seleccionamos el idioma español, ya que es con que trabajara nuestro bot.




















Intents


Una vez que hemos creado nuestro bot, vamos a ver los intentos (Intents), donde representa un dialogo en un conversación dentro de un ámbito especifico. normalmente un agente tiene varios Intentos que equivalen a probables respuestas para el usuario. y en base al dialogo del usuario, el agente intentara asignar el Intento adecuado.


Al crear nuestro agente, de manera automática nos creara dos Intentos, uno de bienvenida (Deafult Welcome Intent), y otro incomprensión (Default Fallback Intent), por ahora los dejaremos ya que nos serán de utilidad.

Veamos que tenemos hasta ahora, y como podemos ir probando el funcionamiento de nuestro bot.


En la parte superior derecha se encuentra una entrada "Try it now" en donde podemos escribir algo, y esperar la respuesta del agente basado en su configuración, y debajo de este nos mostrar información acerca de como interpreto la entrada del usuario.

Por ahora no podemos esperar mucho, más que algún saludo corto,  el cual se encuentra predenterinado en el Intent Default Welcome Intent.












Ahora vamos a crear nuestro primer Intent, colocando en la sección de Intents, y seleccionemos Create Intent




Asignamos un nombre, y dejamos lo demás tal como esta, y guardamos cambios para poder continuar





El siguiente paso es entrenar nuestro agente con el Intent que acabamos de crear para que pueda entender como intentara el usuario expresar su petición, por lo cual debemos pensar de cuantas formas posibles el usuario nos pedirá información del clima, teniendo en cuenta que el objetivo para nuestro caso es proporcionar solo la temperatura y el condición del clima en general.

Algunas frases que puede emplear el usuario pueden ser las siguientes


  • ¿Cómo esta el clima?
  • ¿Cuál es el clima?

Como pueden darse cuenta, falta un elemento importante, que es lugar de donde queremos conocer el clima, por lo que necesitamos conocer el lugar, y en este caso lo manejaremos por ciudad. podemos agregar la siguientes frase de entrenamiento.


  • ¿cuál es el clima en ciudad?
  • ¿Que ta tal el clima en ciudad?
  • Cómo va en el clima en ciudad?







Como ya se abran dado cuenta surge una variable, que es ciudad, la cual puede variar de solicitud en solicitud por cada usuario, y no solo eso, si no que esta debe ser real, y estar escrita correctamente.


Para manejar dicha situación nos apoyaremos de los parámetros que emplea nuestro Intent, y para ello vamos a la sección Action and Parameters donde vamos a crear un parámetro llamado Ciudad.







Llamaremos a este parámetro Ciudad, y en la sección de ENTITY seleccionaremos @sys.geo-city recuerda escribir el signo arroba para que despliegue la lista de opciones, también marcaremos la casilla REQUIRED, para indicar que el usuario debe indicar este parámetro para pode continuar.

Al indicar que este parámetro es requerido, en la sección de PROMPTS nos pedirá que definamos una frase que le indique al usuario que falta este parámetro.





Tendremos que hacer click en el mensaje Define prompts, donde escribiremos las frase(s) que le indicaran al usuario la necesidad de dicho parámetro.




Una vez hecho esto, nos aseguramos de guardar los cambios, recuerda que cada que se guarda un cambio, se mostrar un mensaje del lado inferior derecho indicando que se ha inicio el entrenamiento del agente con los nuevo cambios, esto no tardaría más de un par de segundos.

Con esto que acabamos de agregar, cada vez que el usuario pregunte por el clima, sin especificar la ciudad de interés, el agente nos preguntara cual la ciudad donde queremos conocer el clima; como lo muestra la siguiente imagen.





Una vez llegado a este punto, es necesario indicar dentro de las frase de entrenamiento, cual es texto que corresponde al parámetro deseado, para ello vamos a seleccionar la frase de interés dentro de nuestras frase de entrenamiento y aparecerá un menú emergente donde podemos especificar el tipo de dato y/o parámetro que representa.





En nuestro caso asignaremos en cada frase de entrenamiento del intento la palabra ciudad, el parámetro Ciudad de tipo @sys.geo-city






Hay un punto importante, es que cuando creamos nuestro parámetro y los asignamos a la(s) frases, podemos ver que se asina un nombre de variable, en nuestro caso $Ciudad es cual es muy útil, ya que podremos acceder a dicho valor a través de su nombre, ya sea dentro del mismo Intento e incluso dentro de otros.







Antes de poder probar el Intento de nuestro Agente, vamos a guardar los cambios, y agregar una respuesta a solicitud del usuario, hasta aquí sera una respuesta fija, para demostrar el resultado de lo que hemos construido hasta el momento.


Para ello, dentro del mismo intento, vamos a la sección de Responses y seleccionamos ADD RESPONSE




La frase que emplearemos es "La temperatura en $Ciudad es de 28°C"

Como se pueden dar cuenta aparece la variable $Ciudad, la cual sera sustituida por el parámetro que representa dicha variable, y mejor aún, ya que le hemos indicado al agente que es un tipo de dato City, el agente nos dará el nombre correcto.

Ya que es posible que el usuario escriba el nombre con o sin tilde, o una abreviación,el agente podrá reconocerlo y proporcionarnos el nombre correcto.







Guardamos cambios y vamos a comprobar el trabajo realizado, para ello vamos del lado izquierdo en parte de Try now, en mi caso escribiré Cual es el clima de Irapuato, uso el ejemplo de mi ciudad, pero puedes escribir cualquiera.






Y obtendremos la respuesta que le hemos indicado





Podemos ver que adicional a la respuesta a la solicitud del usuario, nos muestra en Intento que realizo la tarea, así como el parámetro extraído de la petición.

funcionara de igual manera si solicitamos el clima sin especificar la ciudad, el agente nos preguntara por la ciudad, y unas vez que sea indicada, nos brindara la respuesta que ya hemos configurado.










Consulta del clima real de las ciudades


Hasta ahora solo habíamos hechos peticiones al agente de cualquier ciudad valida, y obteníamos siempre una respuesta fija, previamente establecida por nosotros. Pero esto no es lo que buscamos de nuestro bot. por lo que que es momento de que se vuelva dinámico, y nos devuelva el clima real.


Para poder lograr nuestro objetivo es necesario emplear el servicio de un tercero, en este caso un servicio para consultar el clima mediante alguna API, para ello hay múltiples opciones que ofrecen dicho servicio. Donde he evaluado el servicio de openweathermap donde tuve problemas con los resultados, debido a que estos se tornaban imprecisos, quizás debido a que hay muchas ciudades comparten el mismo nombre.


Por ello decidí emplear el servicio de accuweather, donde puedo limitar los resultado a un país de manera fácil, así evitar inconvenientes en los resultados.

Accuweather al igual que muchos servicios ofrecen un versión gratis, pero limitada, es nuestro caso, esta restringido a 50 solicitudes por día, pero es más que suficiente para nuestro ejemplo.


Para poder continuar es necesario registrarnos e iniciar sesión en la plataforma, no daré ejemplo de ello, ya que es igual que el registro de cualquier otro servicio.


Ya estando registrado, debemos ir a la sección MY APPS, y crear una nueva APP que es la forma de Accuweather de gestionar los accesos a su API. Nos pedirá información del uso que le daremos, en ese punto pueden llenarla libremente.





Una vez hecho esto, consultamos al APP que acabamos de crear, donde nos mostrara el API Key necesario para poder consumir su API.







Webhook y Node.js



A este punto nuestro proyecto se vuelve más interesante, ya que es donde comienza a volver dinámico, debido a que podrá consultar el clima real de las ciudades gracias al servicio de clima en el que nos registramos.

Y para poder llevarlo acaba haremos uso de Webhooks y un servio web basado en Node.js, que bien puede ser cualquier otro entorno de desarrollo, tal como Phyton, pero que para nuestro ejemplo he decidido usar el primero debido a su relativa facilidad.


Un Webhook es manera comunicar y/o transmitir información mediante una solicitud POST. para ello Dialogflow requiere de una url publica(Internet) que maneje del puerto 443 (HTTPS). pero como no todos disponemos un servidor con una dirección pública, vamos a requerir de otro servicio que nos permita crear un túnel desde una dirección pública hacia nuestra computadora(localhost) donde tendremos nuestro servicio web recibiendo las peticiones de Dialogflow.

Para lograr crear este túnel utilizaremos el servicio de ngrok el cual nos permite crear dicho túnel de manera muy simple y sin complicaciones, y solo basta con registrarnos y descargar el ejecutable que nos proporciona.
Para entender mejor todo lo que acabamos de mencionar, vamos a ver el siguiente diagrama.






Como se puede apreciar en la imagen, el usuario realizar la petición a dialogflow, este la procesa y realiza una petición POST  hacía la dirección pública que nos proporciona ngrok, quien a su vez la redirige hacia nuestro equipo localhost a través de un puerto valido que le especifiquemos y para poder atender acabo dicha solicitud consulta un servicio de clima, en nuestro caso accuweather y posterior a ello devuelve una respuesta al agente de dialogflow.


Hasta este momento ya tenemos configurado nuestro agente en dialogflow, ya contamos con nuestra cuenta en ngrok, también contamos con nuestra cuenta en accuweather, y solo nos falta montar nuestro servicio web con Node.js



Para ello nos vamos a apoyar de una editor de código muy bueno para esta tarea, Visual Studio Code el cual puedes descargar su última versión desde su página oficial completamente gratis, y por supuesto también sera necesario descargar e instalar nodejs, les recomiendo la versión LTS.
Es posible que después de instalar este software sea necesario reiniciar el equipo. Tampoco daré un ejemeplo de ello ya que es como muchos instaladores, siguiente, siguiente.




Vamos a comenzar abriendo Visual Studio Code y seleccionar una carpeta que sera donde desarrollaremos nuestro proyecto





Creamos la carpeta "bot_clima" donde sera nuestro directorio de trabajo





Ahora abrimos una terminal




y escribimos la siguiente linea
"npm init -y"

Esto no permitirá configurara nuestro proyecto.



Ahora necesitamos instalar un marco de trabajo para realizar nuestro desarrollo web, y emplearemos Node.js, existen muchos otros tanto para node, como para otros entornos de desarrollo, nosotros emplearemos Express.js para ello vamos a correr la siguiente linear para instalarlo en nuestro proyecto

npm install express --save




Debemos tener algo parecido como la siguiente imagen




vamos a agregar un archivo llamado app.js, basta con hacer clic en nuevo archivo o clic derecho sobre el directorio de trabajo.





vamos a agregar el siguiente código a nuestro archivo app.js


var express = require('express');
var app = express();
app.post('/', function (req, res) {
res.send('Hola mundo');
});
app.listen(3000, function () {
console.log('App escuchando puerto 3000');
});
view raw app.js hosted with ❤ by GitHub

Como puede ver es sumamente sencillo, solo incluimos Express, un método que atienda la peticiones post en la raíz de nuestra aplicación, y finalmente corremos nuestro servicio en el puerto 3000, puede ser cualquier puerto valido disponible en tu equipo.


Compilar código


Antes de continuar es muy importante probar que lo llevamos hasta el momento funcione correctamente, y para ello vamos a escribir en la consola de nuestro proyecto la siguiente linea.

node app.js


con esto estamos ejecutando nuestra aplicación, si no hay ningún problema o error, deberíamos ver algo así.






Ya vimos que nuestro proyecto se encuentra corriendo correctamente, pero también es necesario saber que nos responde nuestra aplicación, para ello hay que consultar en nuestro navegador la dirección localhost:3000, y nos mostrara lo siguiente.



Y nos marca error, esto debido a que nuestra aplicación responde a un método de tipo POST, y cuando realizamos una petición a través del navegador, realiza una petición GET, en nuestro caso lo dejaremos como esta, ya que el webhook del agente de dialogflow lo realiza mediante un método POST.

Si queremos ver como ser muestra en el navegador solo cambia en el código la palabra POST por GET o agrega una copia del método de tipo GET
Si bien es funcional, no resulta muy practico, para ello podemos disponer de una herramienta llamada Postman, la cual nos permite hacer peticiones de diferentes tipos.


Vamos a comprobar el resultado que nos da nuestra aplicación, mediante este software, solo escribimos nuestra dirección y el puerto con el que trabajamos indicando que es una petición POST




Como podemos ver, hemos obtenido el resultado que hemos programado, y podemos visualizarlo gracias este Software.


Lo que vamos hacer a continuación es que nuestra aplicación responda con una sencilla frase, al agente de dialogflow, para vamos a recurrir al software que previamente descargamos ngrok para crear un túnel de Internet hacia nuestro equipo (localhost), por comodidad voy hacerlo desde el mismo Visual Studio Code, pero puedes hacer de manera desde la consola de tu sistema operativo, por ejemplo el CMD de Windows.





Por default nos abre una terminal de power shell, así que vamos a teclear la palabra CMD, para emplear una consola de Windows.





Por comodidad he copiado el archivo "ngrok.exe" a la carpeta del proyecto, pero pueden tenerlo en cual quier ubicación, pero tendrán que escribir la ruta de acceso completa, o bien agregarlo al PATH.

para la primera vez que ejecutamos dicho software, hay que seguir la indicaciones de la documentación de ngrok




Posterior a ello, no sera necesario, y para crear el túnel corremos el siguiente comando


ngrok http 3000




El único inconveniente en manejar este comando desde Visual Studio Code es que no se puede detener la ejecución, lo cual se realiza con el atajo "Ctrl + C", por lo que es necesario cerrar la terminal y volver a ejecutar el comando


Hay que tener en cuenta que nuestro proyecto debe estar corriendo en node, y puedes cambiarte fácilmente de consola, simplemente seleccionándola del lado izquierdo del botón "+" .
para correr nuestra aplicación escribimos el siguiente comando

node app.js





Ahora, vamos a copiar la url completa, la que empieza con "https" hasta la terminación ".io"





Y vamos a la sección del Fulfillment en dialogflow y habilitar el webhook, y en la parte de la URL, copiamos la dirección que nos da ngrok.





Cabe mencionar,  que la URL que nos proporciona ngrok cambia, cada vez que lo ejecutamos, de tal manera que cada que pierdas conexión, apagues tu equipo, o cierres la terminal, tendrás que actualizar dicho valor en el webhook de tu agente.

Y también debemos habilitar el webhook en el Intent deseado y para ello seleccionamos ENABLE FULFILLMENT que se encuentra dentro del Intento.




Y una vez habilitado seleccionamos Enable webhook call for this intent





Seguramente habrán notado, que agregue a la URL la terminación "/clima" esto con el fin de mostrar que no es necesario tener nuestro servicio en raíz, y por cual también sera necesario modificar el código para agregar es terminación.


Si quisiéramos probar nuestro agente, no obtendríamos ninguna respuesta, puesto que si bien, el agente enviara un petición hacia la URL que hemos especificado, no hemos programado como es que nuestro servicio debe procesar dicha información y, mucho menos como responderla.


Para llevarlo a cabo dicha tarea debemos poder interpretar la petición de nuestro agente y para poder llevar a cabo dicha tarea, vamos hacer un parse a la petición recibida a formato JSON para poder manejarla con facilidad, vamos emplear la API o librería; como prefieran llamarla de body-parser


Primero vamos a instalarlo con el siguiente comando desde de la consola de Visual Studio Code

npm install body-parser





Ahora vamos a agregar dicha librería a nuestro proyecto e indicar que debe hacer un parse a JSON al cuerpo de las peticiones que reciba.



var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.post('/', function (req, res) {
res.send('Hola mundo');
});
app.listen(3000, function () {
console.log('App escuchando puerto 3000');
});
view raw app.js hosted with ❤ by GitHub


Con esto nuestro agente podrá obtener respuesta desde el servicio que se encuentra corriendo en node desde nuestro equipo, pero antes hay indicarle que debe responder, veamos el siguiente código



var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.post('/clima', function (req, res) {
res.json({fulfillmentText: 'Respondientdo desde webhook en node'});
});
app.listen(3000, function () {
console.log('App escuchando puerto 3000');
});
view raw app.js hosted with ❤ by GitHub


Recuerden guardar cambios y volver a correr su código.
Y hagamos una prueba desde nuestro agente, y debemos obtener la siguiente respuesta.





Como puede apreciar en el código hemos respondido con un objeto JSON a la solicitud de nuestro agente.


Y se preguntaran donde esta el parámetro, pues vamos a ver como podemos utilizarlo.



var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.post('/clima', function (req, res) {
var ciudad = req.body.queryResult.parameters.Ciudad;
var respuestaWebhook = {
fulfillmentText: ''
};
respuestaWebhook.fulfillmentText = 'Respondiendo desde webhook con el parametro ' + ciudad;
res.json(respuestaWebhook);
});
app.listen(3000, function () {
console.log('App escuchando puerto 3000');
});
view raw app.js hosted with ❤ by GitHub


Como pueden ver accedimos con la instrucción

req.body.queryResult.parameters.Ciudad


Para conocer más acerca del formato empleado por el agente para enviar y recibir información pueden consultar en la documentación https://dialogflow.com/docs/fulfillment/how-it-works

También para facilitar la cosas más adelante, hemos creado un objeto tipo con formato JSON que utilizaremos más adelante para responde la petición del agente


var respuestaWebhook = {
fulfillmentText: ''
};



Vamos a probar los nuevos cambios realizados, consultando nuevamente el clima de la ciudad, ver como webhook responde con el parámetro recibo.





Hasta aquí todo bien, pero ya que tenemos todo lo necesario, ya podemos consultar el servicio de accuweather donde previamente nos registramos y obtuvimos nuestra app key, pero antes vamos a ver cual sera el diagrama de funcionamiento para entender mejor como sera el funcionamiento.







Como puede ver en el diagrama de flujo anterior, primero es necesario buscar la ciudad por su nombre para poder obtener su id, y ya con dicho id podemos buscar el clima de la ciudad especificada. esto se debe a que la forma en la que trabaja la API de accuweather es base al id de la ciudad, quizás esto se deba a que hay muchas ciudad que comparten nombre, pero esta es solo una conjetura propia.


Hay que tener en cuenta que al buscar por ciudad, y en caso de existir más de una con el mismo nombre, la API de accuweather nos devolverá todas ellas, sin embargo en este ejemplo siempre tomaremos la primera ciudad, para hacer comprensible este ejemplo, así que hay una oportunidad de mejora.


Para llevar acabo dichas consultas, de ciudad y climas necesitamos la URL de la API para llevar a cabo dicha tarea, en el caso de la búsqueda de la ciudad, emplearemos la siguiente


http://dataservice.accuweather.com/locations/v1/cities/mx/search


Donde la palabra mx es remplazada por el código de país a donde se limitara las búsqueda, en mi caso México(MX) para conocer su código de país deben consultar la siguiente dirección


Por ejemplo para consultar el clima de mi ciudad, tendría que realizar la siguiente petición GET







Una vez que tenemos el código de la ciudad buscada, necesitamos buscar el clima de la ciudad en base al id obtenido, para ello empleamos la siguiente URL, donde 232870 es el id de mi ciudad en plataforma de accuweather


http://dataservice.accuweather.com/currentconditions/v1/232870


Entonces para consultar el clima de mi ciudad por su id queda de la siguiente forma








y casi para terminar, para llevar acabo dichas consultas necesitamos instalar el modulo request para node que es una versión simplificada para realizar solicitudes HTTP. para ello corremos el siguiente comando en la consola de Visual Studio Code

npm install request






 no se olviden de guardar cambios y volver a ejecutar su código con node con simple comando node app.js


y nuestro código final quedaría de la siguiente manera


var express = require('express');
var bodyParser = require('body-parser');
var request = require('request');
var app = express();
//El cuerpo de solicitudes recibidas sera fomateado con formato JSON
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
//función donde se procesan las solitudes de clima
app.post('/clima', function (req, res) {
//Extramos el parametro ciudad, que se encuentra dentro de la petición(webhook) del agente
var ciudad = req.body.queryResult.parameters.Ciudad;
var codigoCiudad = 0;
var urlCodigoCiudad = 'http://dataservice.accuweather.com/locations/v1/cities/mx/search?apikey=uCJJsCIxCD9ruag9ZKTQlYzHnZyRXxgO&q=' + ciudad;
console.log('Consulta de clima para ' + ciudad);
//Variable tipo JSON para guardar la respusta a enviar al agente
var resClima = {
fulfillmentText: ''
};
//Realizamos la consulta para encontrar la ciudad por su nombre
request(urlCodigoCiudad, { json: true }, (err, resp, body) => {
//Si existe un error al procesar la solicitud de busqueda de la ciudad
if(err){
console.log('Error al buscar la ciudad');
console.log(err);
resClima.fulfillmentText = 'No fue posible consultar su ciudad en este momento';
}
else{
//El contenido de la respusta a nuestra solicitud se encuentra en la variable body
//para más información acerca de como trabaja el modulo request
//https://www.npmjs.com/package/request
/*Verificamos que exista una respuesta por parte de la API de accuweather, dicha API
regresa un arreglo de objetos con la información acerca del clima, en el caso de que el
arreglo tenga una logitud de cero, significa que no fue encontrada la ciudad, hay que tomar
en en el caso del español las tildes o algún caracter especial puede generar no sea
encontrada la ciudad buscada*/
if(body.length == 0){
console.log('Ciudad no encontrada');
resClima.fulfillmentText = 'No se ha encontrado su ciudad, asegurese de haberla escrito correctamente';
res.json(resClima);
}
else{
//Extraemos el id de la ciudad
codigoCiudad = body[0].Key;
//y armamos la url para la consulta del clima
var urlClimaCiudad = 'http://dataservice.accuweather.com/currentconditions/v1/' + codigoCiudad + '?apikey=uCJJsCIxCD9ruag9ZKTQlYzHnZyRXxgO&language=es';
//Realizamos la consulta para buscar el clima de la ciudad por su id
request(urlClimaCiudad, {json: true}, (err2, resp2, body2) => {
//en caso de error indicamos un problema
if(err2){
console.log('Problema al obtener el clima');
resClima.fulfillmentText = 'No fue posible consultar el clima de tú ciudad en este momento';
}
//Extraemos la información de la API, y armamos la respuesta a enviar al agente
//más detalles https://developer.accuweather.com/accuweather-current-conditions-api/apis/get/currentconditions/v1/%7BlocationKey%7D
resClima.fulfillmentText = 'La temperatura de ' + ciudad + ' es de ' + body2[0].Temperature.Metric.Value;
resClima.fulfillmentText += ' y ' + body2[0].WeatherText;
res.json(resClima);
});
}
}
});
});
//Bucle indefinido escuhando el puerto 3000, a la espera de peticiones
app.listen(3000, function () {
console.log('App escuchando puerto 3000');
});
view raw app.js hosted with ❤ by GitHub

Hay muchas mejoras que se pueden realizar a este código, pero como es solamente una introducción a dialogflow, no quise extender más el código.
Cabe señalar que una validación importante que no he agregado es cuando se alcanza el limite de peticiones hechas a la plataforma de accuweather, que son 50 en el plan gratis




Ahora probemos con otro ciudad





Como podemos comprobar hemos logrado nuestro objetivo consultar el clima mediante un bot, hay que recordad en el caso de las tildes los más probable es que tengamos problemas, ya que en la plataforma de accuweather no están registrados con ella, así que aquí otra gran oportunidad de mejora para quien quiera profundizar en el tema.


Dialogflow cuenta con una herramienta de diagnostico muy útil cuando estamos desarrollando bot's
y la podemos encontrar en la parte de pruebas




y nos puede mostrar información del webhook, su respuesta y estatus











Si quiere aprender más pueden consultar el post Mensajes enriquecidos en dialogflow
Pues hasta aquí llega este post, espero les haya sido de utilidad, y ayuda en sus proyectos.

Que tipo de carga rápida usa mi celular

Lo bueno y lo malo de la carga rápida Con la introducción de la carga rápida sin duda ha sido una de las grandes mejoras y a la...