Cómo crear un bot para Messenger desde cero

En la actualidad existen muchas herramientas para crear chatbots, sin la necesidad de programarlos.

Algunas herramientas son buenas y otras no lo son tanto. Esto se determina principalmente por la facilidad de uso, y los distintos escenarios que cubren.

¿Pero qué pasa si queremos desarrollar nuestro propio chatbot desde cero, sin depender de herramientas de terceros?

¿Es acaso eso posible? ¿Realmente podemos construir un chatbot que nos resulte útil?

La respuesta es sí. Es posible, y hoy veremos cómo hacerlo.

En resumen:

  • Vamos a crear un bot para Facebook Messenger
  • Este bot lo vamos a desarrollar usando Python
  • Y lo vamos a tener alojado de forma gratuita gracias al servicio Google App Engine

¿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 son String, y finalmente la fecha del evento es de tipo DateTime.

  • 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 el user 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!

El mundo de los chatbots no tiene límites

Recuerda que puedes desarrollar todo tipo de bots tomando como base lo aprendido en este artículo/curso.

Si te has perdido en algún punto, te sugiero (y te agradecería mucho) que te inscribas al curso "Desarrolla tu primer Chatbot para Messenger usando Python".

En el curso vemos todo lo expuesto en este artículo, pero paso a paso, y con un mayor nivel de detalle. Además, si te inscribes tendrás acceso a una sección de preguntas y respuestas donde resolveré todas tus dudas.

python google cloud

Cursos recomendados

Curso de Laravel 5.5

Aprende Laravel

Aprende Laravel 5.5 desde cero y desarrolla aplicaciones web reales, en tiempo récord, de la mano de Laravel.

Ver más
Curso práctico de Javascript

Aprende Javascript

Domina JS con este curso práctico y completo! Fundamentos, ejemplos reales, ES6+, POO, Ajax, Webpack, NPM y más.

Ver más
Curso de Laravel, Vue.js y Pusher

Aprende Vue.js

Desarrollemos un Messenger! Aprende sobre Channels, Queues, Vuex, JWT, Sesiones, BootstrapVue y mucho más.

Ver más
Logo de Programación y más

¿Tienes alguna duda?

Si algo no te quedó claro o tienes alguna sugerencia, escribe un comentario aquí debajo.

Además recuerda compartir el post si te resultó de ayuda. ¡Gracias!

Antes que te vayas

Inscríbete en nuestro curso gratuito de Laravel