¡Todo lo nuevo está bien olvidado!
Ahora, muchas personas están escribiendo varios bots que se comunican con el usuario en IM y de alguna manera ayudan al usuario a vivir.

Si observa el código de muchos bots, generalmente se reduce al mismo patrón:
- llega el mensaje
- se pasa al manejador de mensajes de usuario (
callback
)
Esta es generalmente una forma universal de escribir bots. Es adecuado para chats de una persona y para bots conectados a grupos. Con este método, todo está bien, excepto uno: el código de incluso bots simples a menudo es bastante confuso.
Tratemos de desentrañarlo.
Comenzaré con las renuncias:
- Lo que se describe en este artículo es adecuado para bots del tipo
<->
. - El código en este artículo es un código de boceto. Escrito específicamente para este artículo en 15 minutos. Así que no juzgues estrictamente.
- Utilicé un enfoque similar en los negocios: con equilibrio de carga. Pero, por desgracia, mi código de producción tiene muchas dependencias de infraestructura y es muy fácil no publicarlo. Por lo tanto, este boceto se usa en el artículo. Me referiré a los problemas de desarrollo de paradigmas (describiré dónde y cómo nos desarrollamos).
Bueno, ahora vámonos.
Como soporte, considere la biblioteca asincrónica de aiograma, python3.7 + . El enlace tiene un ejemplo de un simple echo bot.
Lo copiaré aquí:
Ver código """ This is a echo bot. It echoes any incoming text messages. """ import logging from aiogram import Bot, Dispatcher, executor, types API_TOKEN = 'BOT TOKEN HERE'
Vemos que la organización del bot es tradicional. Cada vez que un usuario nos escribe algo, se llama a una función de controlador.
¿Qué hay de malo en este paradigma?
Que la función de controlador para implementar diálogos complejos debe restaurar su estado desde algún tipo de almacenamiento en cada una de sus llamadas.
Si observa la mayoría de los bots que admiten algún tipo de negocio (por ejemplo, contratación), le hacen preguntas al usuario 1..N, luego hacen algo en función de los resultados de estas preguntas (por ejemplo, guardan el perfil en la base de datos).
Si fuera posible escribir un bot en un estilo tradicional (en lugar de un estilo de anillo), entonces sería posible almacenar los datos del usuario directamente en la pila.
Intentemos hacerlo.
Esbocé un boceto del módulo, conectando el cual puedes usar con esta biblioteca:
Una pequeña explicación:
La clase ChatDispatcher
se ChatDispatcher
con los siguientes parámetros:
- funciones de fragmentación de mensajes entrantes (por qué se denomina fragmentación, más tarde, cuando tocamos cargas grandes). La función devuelve un número único que indica un diálogo. En el ejemplo, simplemente devuelve la ID de usuario.
- funciones que realizarán el trabajo del servicio de chat.
- Valor de tiempo de espera para inactividad del usuario.
Descripción del puesto:
- En respuesta al primer mensaje del usuario, se crea una tarea asincrónica que servirá para el diálogo. Esta tarea funcionará hasta que se complete el diálogo.
- Para recibir un mensaje de un usuario, lo solicitamos explícitamente. Ejemplo de chat de
echo
:
async def chat(get_message): message = await get_message() await message.answer(message.text)
- Respondemos a los mensajes que nos ofrece la biblioteca (
message.answer
).
Intentemos escribir un bot en este paradigma
Ejemplo de código completo aquí Un ejemplo de bot escrito: simplemente agrega un par de números y produce un resultado.
El resultado se ve así:

Bueno, ahora echemos un vistazo más de cerca al código. Las instancias no deben plantear preguntas.
La integración con nuestro boceto se realiza en el controlador estándar al que llamamos await chat_dispatcher.handle(message)
. Y describimos el chat
en la función de chat
, repetiré su código aquí:
async def chat(get_message): try: message = await get_message() await message.answer(' , ') first = await get_message() if not re.match('^\d+$', str(first.text)): await first.answer(' , : /start') return await first.answer(' ') second = await get_message() if not re.match('^\d+$', str(second.text)): await second.answer(' , : /start') return result = int(first.text) + int(second.text) await second.answer(' %s (/start - )' % result) except ChatDispatcher.Timeout as te: await te.last_message.answer('- , ') await te.last_message.answer(' - /start')
Código de servicio de chat: solo solicita los datos uno por uno al usuario. Las respuestas del usuario simplemente se apilan en la pila (variables first
, second
, message
).
La función get_message
puede get_message
una excepción si el usuario no ingresa nada durante el tiempo de espera especificado (y puede pasar el tiempo de espera en el mismo lugar).
Estado de diálogo: directamente relacionado con el número de línea dentro de esta función. Bajando el código, avanzamos a lo largo del esquema de diálogo . Hacer cambios al hilo de diálogo no es fácil, ¡pero es muy simple!
Por lo tanto, no se necesitan máquinas de estado. En este paradigma, puede escribir diálogos muy complejos y comprender que su código será mucho más simple que el código con callback
.
Desventajas
Donde sin ellos.
- Para cada usuario activo, hay una tarea-corutina. En promedio, una CPU normalmente atiende a unos 1000 usuarios, luego comienzan los retrasos.
- Reiniciar todo el demonio: finaliza todos los cuadros de diálogo (y los reinicia).
- El código [del ejemplo] no está adaptado para la escala de carga y la internacionalización.
Si con el segundo problema está claro qué hacer: interceptar la señal de parada y decir a los usuarios "Tengo una emergencia aquí, fuego, volveré un poco más tarde". Ese último problema puede causar dificultades. Veámoslo:
Escala de carga
Obviamente, los bots cargados deben iniciarse en muchos backends a la vez. En consecuencia, se webHook
modo de operación webHook
.
Si solo equilibra el webHook
entre, por ejemplo, dos backends, entonces obviamente debe asegurarse de alguna manera de que el mismo usuario llegue a la misma rutina que le está hablando.
Hicimos esto de la siguiente manera.
- En el equilibrador, analice el JSON del mensaje entrante (
message
) - Elija una identificación de usuario
- Usando el identificador, calculamos el número de back-end (== fragmento). Por ejemplo, usando el
user_id % Nshards
. - Redirigimos la solicitud al fragmento.
ID de usuario: se convierte en la clave para fragmentar entre las rutinas de los cuadros de diálogo y la base para calcular el número de fragmento del backend en el equilibrador.
El código de dicho equilibrador es simple: está escrito en cualquier idioma en 10 minutos. No lo traeré.
Conclusión
Si escribe bots en este paradigma, simplemente puede rehacer los diálogos de uno a otro. Además, lo importante es que el nuevo programador entienda fácilmente el código de los diálogos que alguien hizo antes que él.
¿Por qué la mayoría de la gente escribe bots en la arquitectura de anillo? No lo sé.
Solían escribir en ese paradigma. Servir salas de chat en este estilo fue adoptado en la era de IRC y bots para ello. Así que no pretendo ser ningún tipo de novedad.
Y mas Si usa este paradigma en un lenguaje con el operador goto
, este será solo un hermoso ejemplo del uso de goto
(los bucles en los cuadros de diálogo se realizan maravillosamente en goto
). Lamentablemente, esto no se trata de Python.