Saltar al contenido

Cómo hacer un chatbot con Botman

Índice

Deseando crear un chatbot Web busqué y entre las alternativas apareció BotMan que es un framework en PHP para la creación de chatbots. Como anillo al dedo.

Función del chatbot

El chatbot debía hacer lo siguiente:

  • Mostrar el listado de productos disponibles.
  • Reportar el último precio de un producto solicitado.
  • Listar los reportes disponibles.
  • Enviar el informe requerido en PDF.

Instalación

La instalación es sólo de hacer dentro de tu proyecto usando Composer:

composer require botman/botman

También instalé Doctrine para manejar caché, igual mediante Composer. Además hay que instalar los controladores para usar el chatbot en la Web y en Telegram (en mi caso), hay más controladores para otros servicios:

composer require botman/driver-web
composer require botman/driver-telegram

Para probar el funcionamiento en la Web hay que crear un widget e incluirlo en la página que se va a utilizar, por ejemplo creando un archivo llamado chat_frame.html:

<!doctype html>
<html>
<head>
    <title>BotMan Widget</title>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/botman-web-widget@0/build/assets/css/chat.min.css">
</head>
<body>
<script id="botmanWidget" src='https://cdn.jsdelivr.net/npm/botman-web-widget@0/build/js/chat.js'></script>
</body>
</html>

Y en la página que va a servir (index.html de tu sitio, por ejemplo) para tener la ventana del chat hay que incluir lo siguiente:

<script>
var botmanWidget = {
    frameEndpoint: '/superbot/chat_frame.html',
    chatServer: '/superbot/bot/',
    dateTimeFormat: 'd/m/yy HH:MM',
    title: 'SuperBot',
    introMessage: 'Hola, soy SuperBot',
    placeholderText: 'Escribe un mensaje...'
};
</script>
<script src='https://cdn.jsdelivr.net/npm/botman-web-widget@0/build/js/widget.js'></script>

Hay más opciones de configuración para el widget. Entonces superbot sería el directorio donde vive nuestro proyecto, así quedaría una posible estructura de archivos:

- wwwroot/
    - index.html
    - ...
    - superbot/
        - chat_frame.html
        - bot/
            - cache/
            - vendor/
            - index.php
            - composer.lock
            - ....

Podés sacar a vendor/ a otro directorio o bloquearlo en el servidor Web, es tu decisión.

Configuración inicial

Primero voy a mostrar el index.php de mi bot:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?php

require_once "vendor/autoload.php";

use BotMan\BotMan\BotMan;
use BotMan\BotMan\BotManFactory;
use BotMan\BotMan\Drivers\DriverManager;
use BotMan\BotMan\Cache\DoctrineCache;

require_once "precios.php";
require_once "informes.php";
require_once "otros.php";
require_once "productos.php";

$config = [
    'web' => [
    	'matchingData' => [
            'driver' => 'web',
        ],
    ],
    'telegram' => [
        'token' => '456576242:5A46SD5465X9CV46A5S4DF',
    ],
    'botman' => [
        'conversation_cache_time' => 10
    ]
];

DriverManager::loadDriver(\BotMan\Drivers\Web\WebDriver::class);
DriverManager::loadDriver(\BotMan\Drivers\Telegram\TelegramDriver::class);
$doctrineCacheDriver = new \Doctrine\Common\Cache\PhpFileCache('cache');

$botman = BotManFactory::create($config, new DoctrineCache($doctrineCacheDriver));

//El saludo diario
$botman->hears('.*hola.*', function (BotMan $bot) {
    $bot->reply('Hola qué tal, se sobre los últimos <strong>precios</strong> e <strong>informes</strong> de mercado.', [
        'parse_mode' => 'HTML'
    ]);
});

//Conversación simple de precios
$botman->hears('.*precio[s]?', function (BotMan $bot) {
    $bot->startConversation(new ConversacionPrecios);
});

//Conversación avanzada de precios
$botman->hears('.*precio[s]? de {producto} {variedad} de {calidad}', function (BotMan $bot, $producto, $variedad, $calidad) {
    $bot->startConversation(new ConversacionPrecios($producto, $variedad, $calidad));
});

//Conversación simple de informes
$botman->hears('.*informes', function (BotMan $bot) {
    $bot->startConversation(new ConversacionInformes);
});

//Conversación avanzada de informes
$botman->hears('.*informe {tipo}', function (BotMan $bot, $tipo) {
    $bot->startConversation(new ConversacionInformes($tipo));
});

//Conversación simple de productos
$botman->hears('.*producto[s]?', function (BotMan $bot) {
    $bot->startConversation(new ConversacionProductos);
});

//Nombres simples de productos
$botman->hears('{termino}', function (BotMan $bot, $termino) {
    $bot->startConversation(new ConversacionOtros($termino));
});

// Empieza a escuchar
$botman->listen();

Las primeras líneas tienen que ver con la carga de archivos instalados por Composer, luego las librerías cargadas de BotMan. Cuando llegamos a los require esos son míos, son los que contienen el código de cada uno de esos elementos.

Luego en la variable $config se almacena la configuración del bot, ahí tengo; la carga del controlador Web, el de Telegram y una configuración del tiempo de una conversación en caché. En las siguientes líneas cargamos los controladores e instanciamos el bot en $botman.

Después llegamos a como escuchar y responder algo con el método más básico:

$botman->hears('.*hola.*', function (BotMan $bot) {
    $bot->reply('Hola qué tal, se sobre los últimos <strong>precios</strong> e <strong>informes</strong> de mercado.', [
        'parse_mode' => 'HTML'
    ]);
});

Aquí el chatbot está escuchando (hears) por la palabra hola, si la detecta entonces responde (reply) con el mensaje ‘Hola qué tal…’. Esa parte de lo que BotMan escucha lo puede interpretar como una cadena o como una expresión regular. La respuesta con el método reply se puede formatear con etiquetas HTML utilizando el atributo parse_mode y ya se pueden usar etiquetas como strong.

Con esto ya se tiene un chatbot básico. Podríamos seguir creando sólo para escuchar y responder de ésta forma pero limita bastante el uso que se le puede dar, para eso hay que usar las conversaciones.

Conversaciones

Las Conversaciones en BotMan sirven principalmente para guardar el estado de la conversación entre usuario y chatbot, para que en el futuro cercano el bot recuerde de lo que se ha estado hablando, así puede responder de la mejor manera. En mi caso no me interesa conocer datos del usuario o que el bot los recuerde, sólo que el usuario obtenga lo que desea del bot.

Una conversación puede comenzar de la siguiente forma:

$botman->hears('.*informe {tipo}', function (BotMan $bot, $tipo) {
    $bot->startConversation(new ConversacionInformes($tipo));
});

Escuchamos por informe seguido del marcador {tipo} y si la detecta entonces inicia una conversación de informes donde:

  • Recibe el tipo del informe ($tipo).
  • Busca el documento disponible para lo solicitado (semanal, mensual, etc.).
  • Envía el documento como enlace (URL) y como adjunto.

Como adjunto es que lo envía directamente en la plataforma si es que lo soporta, en lo que estoy usando sólo Telegram soporta eso. Entonces dentro de informes.php:

$datos = $this->obtenerURL('semanal');

if ($datos !== null){
    $adjunto = new File($datos['url'], [
    'custom_payload' => true,
    ]);

    $respuesta = OutgoingMessage::create('Este es el archivo semanal que tengo: <a href="'.$datos["url"].'" target="_blank">'.$datos["url"].'</a>')->withAttachment($adjunto);
}

Donde obtenerURL es un método que busca la URL (el PDF está en línea) que le pertenece a ese tipo de informe y devuelve dicha dirección. En $adjunto creamos el adjunto que se le va a pasar a OutgoingMessage::create, esto crea la respuesta que se le va a dar al usuario con todo y el archivo.

También se pueden hacer preguntas al usuario usando botones, para más detalle ver los posts Hora del código del #2 al #5. El archivo informes.php contiene toda la conversación en cuanto a informes, este archivo es en general:

<?php

use BotMan\BotMan\Messages\Conversations\Conversation;
use BotMan\BotMan\Messages\Incoming\Answer;
use BotMan\BotMan\Messages\Attachments\File;
use BotMan\BotMan\Messages\Outgoing\OutgoingMessage;
use BotMan\BotMan\Messages\Outgoing\Question;
use BotMan\BotMan\Messages\Outgoing\Actions\Button;

class ConversacionInformes extends Conversation
{

    /*El resto del código*/

    public function run()
    {
        /*El primer método o lógica a llamar*/
    }
}

Cada conversación en BotMan es una clase que hace uso de las diferentes librerías que el framework provee. Su método por defecto es run que hace que la conversación inicie. Se va a usar __construct sólo si hay algún valor que pasarle, en el caso de informes se le pasa uno pero es opcional.

La funcionalidad para mostrar precios sigue el mismo sentido que para lo de informes, es sólo que la lógica interna de cómo buscar los registros en la base de datos es diferente. Por ejemplo, al buscar el informe semanal sólo se devuelve un único registro, en precios hay que comprobar nombres de productos completos, nombres populares cortos, generar botones de forma programática para el listado de productos, etc. Así mismo tu chatbot será diferente.

Integración con Telegram

Primero hay que instalar la aplicación en el teléfono, luego hay que hablar con el BotFather desde dentro de Telegram y seguir sus instrucciones para crear el bot. Más información.

Luego de crear el bot con el BotFather, hay que copiar el token que da. Este token hay que colocarlo en la parte de Telegram dentro de $config. Este token se puede volver a cambiar en el futuro. El BotFather permite bastante configuración del bot, es interesante.

Ahora ya tenemos creado el bot en Telegram y el bot en nuestro servidor público, pero son dos cosas separadas. Para hacer que se comuniquen hay que registrar el webhook con el bot de Telegram. En Firefox yo utilizo un complemento llamado RESTED para poder hacer este tipo de consultas.

Con el token hay que componer la dirección: https://api.telegram.org/botTOKEN/setWebhook ésta hay que mandarla mediante el método POST y debe llevar un parámetro llamado url que va a contener la dirección donde está publicado el bot, no el chat frame sino que donde está el controlador del bot en si.

Por ejemplo tomando la estructura de archivos de más arriba esto estaría en wwwroot/superbot/bot y asumiendo dominio.com como nuestro sitio, entonces el webhook sería https://dominio.com/superbot/bot. Le podés agregar index.php dependiendo de como este configurado el servidor Web.

Usando RESTED para establecer el webhook de BotMan con Telegram

Usando RESTED para establecer el webhook de BotMan con Telegram

Conclusión

Se que los chatbots son una maravilla de los 70’s y los 80’s, también se que un chatbot es en si una especie de anti patrón de diseño para usar en una página Web. Pero esto de las aplicaciones como WhatsApp, Telegram, Signal, etc. son inventos que se prestan muy bien para los chatbots, muchos nos hemos acostumbrado a escribir más que a llamar para poder consultar o registrar un dato o un archivo mediante texto, es más simple y rápido.

Hacer un chatbot con BotMan es ridículamente sencillo, se pueden hacer preguntas y respuestas simples o usar conversaciones que permitan guardar el estado de dicha comunicación con el usuario. La integración con servicios de terceros para implementar el bot en esas plataformas no requiere mayor configuración.

Gracias a Gloria Borrayo y Guillermo Schwindt por la revisión de este artículo.