¿Por qué desarrollar nuestro propio chatbot en vez de usar una herramienta?
Existen aplicaciones, que cuentan con una interfaz gráfica de usuario, y que nos permiten crear nuestros propios bots. ¿Por qué hemos de desarrollar uno desde cero?
Tenemos 3 razones principales:
- Es gratis. La capa gratuita de App Engine es muy generosa, por lo que es muy poco probable que excedamos el límite. Esto ocurriría sólo si tenemos varios miles de usuarios comunicándose con nuestro bot continuamente. De ser el caso, nuestro éxito superaría con creces nuestra inversión en hosting.
- Para aprender. ¿Realmente no sientes curiosidad por ver cómo se desarrolla un chatbot desde cero?
- Ir más lejos. Las herramientas para crear chatbots pueden ser muy interesantes, pero realmente estamos limitados a lo que ofrecen. Desarrollar nuestro propio chatbot, nos permitirá ser originales, y desarrollar lo que deseemos. Inclusive, hasta podríamos desarrollar nuestra propia plataforma para creación de chatbots.
Escogiendo un canal para nuestro chatbot
Podemos construir un bot para distintos canales. Entre los canales más populares tenemos Facebook Messenger, Slack, Twitter y Telegram.
En este artículo vamos a hablar de forma específica acerca del desarrollo de chatbots para Facebook Messenger.
¿Por qué? Principalmente porque Messenger es la plataforma más popular para chatbots. Casi todas las herramientas para construir chatbots se centran en Messenger, y algunas incluso sólo soportan Messenger. Y existe una buena razón para ello: presenta 2.167 millones de usuarios activos al mes (dato correspondiente a inicios del 2018).
Otra razón por la que se tiende a priorizar Messenger: botones de respuesta rápida.
Hay botones que nuestro chatbot puede ofrecer a los usuarios como un atajo y ahorrar que ellos lo tengan que escribir. Esto no sólo hace que el bot sea más atractivo (¿a quién le gusta tener que escribir desde un móvil?), también hace que nuestro trabajo como desarrolladores de chatbots sea mucho más sencillo.
Si ofrecemos botones a los usuarios, entonces ellos los usarán. Esto significa que no debemos preocuparnos por "parsear" consultas arbitrarias, que un usuario podría escribir (en lenguaje natural y probablemente fuera de contexto).
Guiar a nuestros usuarios es bueno para ellos, pero lo es también para nosotros.
El mágico árbol de nodos
Vamos a diseñar nuestro bot basados en un árbol de nodos.
Los posibles estados del bot se determinan en función a este árbol.
Los nodos representan:
- Los mensajes que envía el bot
- Las posibles respuestas que puede dar el usuario
El siguiente árbol representa entonces cada flujo de conversación posible.
Dedica unos segundos para analizarlo.
say: "Hola! Por favor selecciona una opción para poder ayudarte."
answers:
Cursos disponibles:
say: Tenemos varios cursos! Todos ellos son muy interesantes y totalmente prácticos. Por favor selecciona la opción que te resulte más interesante.
answers:
Dominar Javascript:
say: https://www.udemy.com/javascript-curso-practico-y-completo/?couponCode=INVITACION
Aprender Laravel:
say: https://www.udemy.com/curso-laravel-5-5-desde-cero-desarrolla-publica-una-app-pedidos/?couponCode=INVITACION https://www.udemy.com/laravel-y-oauth-2-facebook-twitter-google/?couponCode=INVITACION
Integrar Laravel+Vue:
say: https://www.udemy.com/realtime-messenger-usando-laravel-vue-bootstrap-pusher/?couponCode=PROMOCION
Tengo una duda:
say: ¿Es una duda sobre un video que viste en el canal de Youtube? ¿O necesitas ayuda personalizada?
answers:
Es sobre un video:
say: Gracias por visitar el canal. Por favor escribe tu duda en un comentario, y te responderé por allí tan pronto como pueda.
Busco asesoría:
say: Gracias por tu interés. Por favor visita este enlace, donde verás cómo puedo ayudarte https://programacionymas.com/asesoria
Solicitar desarrollo:
say: ¿Tienes definido formalmente el proyecto y estás dispuesto a invertir en él?
answers:
Aún no está definido:
say: Por favor cuéntame más y entonces te diré cómo puedo ayudarte.
No tengo presupuesto:
say: Entiendo. En tal caso te recomiendo inscribirte a mis cursos sobre programación. Puedes aprender mucho con los tutoriales, y además siempre atiendo todas las dudas.
Sí y sí:
say: Genial. Por favor envíame un documento con los requerimientos y te contactaré tan pronto como tenga una propuesta.
Como ves, el árbol de conversación es bastante sencillo. Está escrito en formato YAML (Yet Another Markup Language), lo que facilita su lectura.
El nodo raíz especifica el primer mensaje que el bot envía al usuario. En este caso el mensaje inicial es "Hola. ¿En qué te puedo ayudar?", y según la respuesta del usuario, la conversación con el bot fluye.
¿Qué necesitamos para empezar a desarrollar nuestro bot?
Para desarrollar nuestro bot, tenemos primero que configurar un par de cosas en Facebook.
Las instrucciones oficiales se pueden encontrar aquí, pero en resumen, vamos a necesitar:
- Una página en Facebook — cada bot necesita una página diferente.
- Una cuenta de desarrollador, que nos permitirá registrar nuestra app en Facebook.
- Una app en Facebook para obtener acceso a un
secret access token
(lo necesitaremos posteriormente).
Los bots para Facebook funcionan a través de webhooks, que son URLs que nosotros definimos y que Facebook Messenger usará para interactuar con nuestro bot.
Para publicar nuestro webhook usaremos Google App Engine. La ventaja de esto es que resulta gratuito para bajos volúmenes de tráfico, y escala automáticamente si necesitamos más. Así mismo usaremos Python como lenguaje de programación, pero en realidad se puede lograr también con cualquier otro lenguaje.
Vamos a necesitar descargar el Python SDK y crear un proyecto de Google Cloud si aún no tenemos uno.
Creando nuestro Webhook
La primera misión de nuestro webhook es permitirle a Facebook verificar que realmente se trata de un webhook auténtico. Para ello simplemente tenemos que gestionar una petición GET, que contiene un token de verificación (es una cadena secreta y aleatoria, que debemos definir en Facebook).
La verificación es posible usando el siguiente código:
class MainPage(webapp2.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
mode = self.request.get("hub.mode")
if mode == "subscribe":
challenge = self.request.get("hub.challenge")
verify_token = self.request.get("hub.verify_token")
if verify_token == VERIFY_TOKEN:
self.response.write(challenge)
else:
self.response.write("Ok")
Es así como definimos una clase para gestionar peticiones (usando el framework
webapp2
). Aunque no se muestra en el fragmento anterior, esta clase presenta también un constructor que se encarga de inicializar nuestra clase bot.
El código completo está disponible en Github, pero te recomiendo seguir el curso para desarrollar este chatbot desde cero y paso a paso.
Gestionando mensajes de usuarios
Necesitamos interpretar los mensajes que escriben los usuarios. Para ello primero necesitamos capturar estos mensajes.
Estos son enviados por Facebook a nuestro webhook, a través de peticiones POST.
def post(self):
logging.info("Data obtenida desde Messenger: %s", self.request.body)
data = json.loads(self.request.body)
if data["object"] == "page":
for entry in data["entry"]:
for messaging_event in entry["messaging"]:
sender_id = messaging_event["sender"]["id"]
recipient_id = messaging_event["recipient"]["id"]
if messaging_event.get("message"):
# Eventos de tipo message
if messaging_event.get("postback"):
# Eventos de tipo postback
Aquí "parseamos" la información que recibimos en formato JSON desde Facebook, para que posteriormense se pueda analizar y procesar.
Básicamente Facebook Messenger nos permite suscribirnos a eventos de 2 tipos: eventos
message
(cuando el usuario escribe) y eventos postback
(que son enviados cuando un usuario hace clic en un botón de respuesta).Entonces iteramos sobre los messaging events en general, y dependiendo el tipo decidimos cómo actuar.
Luego de recibir la información que nos envía Facebook e identificar los mensajes, invocamos al método
handle
para que nuestra clase Bot se encargue de su procesamiento. El fragmento anterior sólo muestra la estructura general.
Enviando mensajes a los usuarios
Cuando instanciamos nuestra clase Bot, enviamos la función
send_message
al constructor.Esta función permite a nuestro bot devolver mensajes de respuesta a los usuarios. Y su definición es la siguiente:
def send_message(recipient_id, message_text, possible_answers):
headers = {
"Content-Type": "application/json"
}
message = get_postback_buttons_message(message_text, possible_answers)
if message is None:
message = {"text": message_text}
raw_data = {
"recipient": {
"id": recipient_id
},
"message": message
}
data = json.dumps(raw_data)
logging.info("Enviando mensaje a %r: %s", recipient_id, message_text)
r = urlfetch.fetch("https://graph.facebook.com/v2.6/me/messages?access_token=%s" % ACCESS_TOKEN,
method=urlfetch.POST, headers=headers, payload=data)
if r.status_code != 200:
logging.error("Error %r enviando mensaje: %s", r.status_code, r.content)
La variable
recipient_id
que esta función recibe se corresponde con el identificador del usuario al que vamos a responder. Junto a esta variable tenemos como parámetros: el texto a enviar, y algunos botones de respuesta rápida (que el usuario podrá presionar). Primero nos aseguramos que las cabeceras de nuestra petición especifiquen el formato a usar (JSON), y entonces agregamos nuestros botones postback como parte del mensaje.
Estos botones no estarán presentes siempres, sólo en algunos casos, dependiendo de lo que se defina en nuestro árbol. De todas formas, para los casos en que están presentes, la función
get_postback_buttons_message
es la encargada de dar a estos botones el formato adecuado (según lo exige Facebook).Finalmente hacemos nuestra petición al Facebook Graph API, enviando el
access token
que Facebook nos dio cuando registramos nuestra app.Ejecutando nuestro bot
El código final de nuestro archivo principal, contiene lo siguiente, que es necesario para construir la clase principal y ejecutar el webhook que va a representar a nuestro bot:
app = webapp2.WSGIApplication([
('/', MainPage),
], debug=True)
Cerebro de nuestro bot
Y ahora llegamos a un punto interesante.
¿Cómo sabe el bot qué decir? El cerebro de nuestro bot se corresponde con el archivo
bot.py
.class Bot(object):
def __init__(self, send_callback, users_dao, tree):
self.send_callback = send_callback
self.users_dao = users_dao
self.tree = tree
def handle(self, user_id, user_message, is_admin=False):
# lógica del bot
La clase es inicializada con 3 parámetros:
- una función callback (que ya se ha definido antes) para devolver mensajes a los usuarios,
- un objeto que proporciona el acceso a datos (para guardar el historial de las conversaciones), y
- el árbol que contiene los posibles flujos de conversación (se obtiene a partir del YAML mostrado anteriormente).
Como es de notarse, la clase
handle
es la que contiene la lógica principal del bot.El código anterior sólo muestra un fragmento del método. Pero en resumen aquí:
- Primero registramos el mensaje recibido del usuario, y obtenemos el historial de mensajes intercambiados con dicho usuario, usando nuestra instancia
DAO
(data access object
). - Esto nos permitirá reproducir las acciones del usuario, para descubrir dónde es que nos encontramos según el árbol.
- Se definen un mensaje y unos botones por defecto, que serán devueltos en caso que el usuario diga algo que el bot no entiende.
- Y así mismo se define una variable para determinar si el usuario desea reiniciar la conversación con el bot.
El historial de mensajes es muy importante. Estos mensajes guardan los textos enviados tanto por el usuario como por el bot.
Finalmente, tras recorrer todo el historial, escribimos nuestra respuesta (en un log y en una base de datos propia), y enviamos el mensaje de respuesta al usuario.
La última pieza del rompecabezas
Lo último pero no menos importante es la definición de un
data access object
y un modelo que represente a los eventos notificados por Messenger.Estas clases en conjunto nos permiten gestionar toda la información relacionada con los mensajes intercambiados (considerando 3 actores: usuarios, bot y administrador).
class UserEvent(ndb.Model):
user_id = ndb.StringProperty()
author = ndb.StringProperty()
message = ndb.StringProperty()
date = ndb.DateTimeProperty(auto_now_add=True)
class UserEventsDao(object):
def add_user_event(self, user_id, author, message):
event = UserEvent()
event.user_id = user_id
event.author = author
event.message = message
event.put()
logging.info("Evento registrado: %r", event)
def get_user_events(self, user_id):
events = UserEvent.query(UserEvent.user_id == user_id).order(UserEvent.date)
return [(event.message, event.author) for event in events]
def remove_user_events(self, user_id):
events = UserEvent.query(UserEvent.user_id == user_id)
quantity = events.count()
for event in events:
event.key.delete()
logging.info("Se eliminaron %r eventos", quantity)
def admin_messages_exist(self, user_id):
events = UserEvent.query(UserEvent.user_id == user_id, UserEvent.author == 'admin')
return events.count() > 0
Nuestro DAO hace uso de Google Datastore. Y la API de Python hace que el uso de Datastore sea muy fácil.
En el fragmento anterior:
- Primero creamos una clase modelo
UserEvent
, que especifica los campos y sus tipos. En nuestro caso, el user ID, el autor del mensaje, y el mensaje mismo sonString
, y finalmente la fecha del evento es de tipoDateTime
.
- Para crear y almacenar un nuevo evento de usuario, simplemente instanciamos esta clase, fijamos las propiedades, y llamamos al método
put()
desde el objeto.
- Para obtener los eventos de un usuario, llamamos a la función
query()
y usamos como filtro eluser ID
. Aquí ordenamos los eventos por fecha, y devolvemos una lista de tuplas.
Despliegue (deployment del bot)
Hemos dado un vistazo al código que compone nuestro bot! Ahora corresponde realizar el proceso de despliegue, para finalmente conectarlo con Messenger.
Para desplegar nuestra aplicación sobre App Engine, usamos el comando
gcloud
que viene con el App Engine SDK:gcloud app deploy
Una vez realizado el proceso de deployment, la URL de nuestro webhook será:
http://[PROJECT_ID].appspot.com/
Entonces actualizamos nuestra app Facebook con esta URL de webhook y estaremos listos!
0 Comentarios