COVID-19 Telegram-bot // Respondemos preguntas frecuentes automáticamente

En el contexto del bombo universal sobre el coronavirus, decidí hacer al menos algo útil (pero no menos bombo). En este artículo, hablaré sobre cómo crear e implementar el Telegram Bot usando métodos de PNL basados ​​en reglas en 2.5 horas (eso es lo que me llevó) para responder preguntas frecuentes usando el caso COVID-19 como ejemplo.

En el curso del trabajo, usaremos el viejo Python, la API de Telegram, un par de bibliotecas NLP estándar, así como Docker.



Minuto de atención ovni


La pandemia COVID-19, una infección respiratoria aguda potencialmente grave causada por el coronavirus SARS-CoV-2 (2019-nCoV), se ha anunciado oficialmente en el mundo. Hay mucha información sobre Habré sobre este tema; recuerde siempre que puede ser confiable / útil, y viceversa.

Le instamos a que sea crítico con cualquier información publicada.



Lávese las manos, cuide a sus seres queridos, quédese en casa siempre que sea posible y trabaje de forma remota.

Leer publicaciones sobre: coronavirus | trabajo remoto

Breve Prefacio


Este artículo describe el proceso de creación de un Bot de Telegram simple que responde preguntas frecuentes sobre COVID-19. La tecnología de desarrollo es extremadamente simple y versátil, y se puede utilizar para cualquier otro caso. Una vez más, enfatizo que no pretendo ser el Estado del Arte, sino que solo ofrezco una solución simple y efectiva que pueda ser reutilizada.

Como creo que el lector de este artículo ya tiene cierta experiencia con Python, asumiremos que ya tiene instalado Python 3.X y las herramientas de desarrollo necesarias (PyCharm, VS Code), puede crear un Bot en Telegram a través de BotFather, y por lo tanto, me saltearé estas cosas.

1. Configurar API


Lo primero que debe instalar es la biblioteca de contenedor para la API de Telegram " python-telegram-bot ". El comando estándar para esto es:

pip install python-telegram-bot --upgrade

A continuación, crearemos el marco de nuestro pequeño programa definiendo "controladores" para los siguientes eventos de Bot:

  • start: comando de lanzamiento de Bot;
  • ayuda - comando de ayuda (ayuda);
  • mensaje - procesamiento de mensajes de texto;
  • error: un error.

La firma de los manejadores se verá así:

def start(update, context):
    #   
    pass


def help(update, context):
    #  
    pass


def message(update, context):
    #  
    pass


def error(update, context):
    # 
    pass

A continuación, por analogía con el ejemplo de la documentación de la biblioteca, definimos la función principal en la que asignamos todos estos controladores e iniciamos el bot:

def get_answer():
    """Start the bot."""
    # Create the Updater and pass it your bot's token.
    # Make sure to set use_context=True to use the new context based callbacks
    # Post version 12 this will no longer be necessary
    updater = Updater("Token", use_context=True)

    # Get the dispatcher to register handlers
    dp = updater.dispatcher

    # on different commands - answer in Telegram
    dp.add_handler(CommandHandler("start", start))
    dp.add_handler(CommandHandler("help", help))

    # on noncommand i.e message - echo the message on Telegram
    dp.add_handler(MessageHandler(Filters.text, message))

    # log all errors
    dp.add_error_handler(error)

    # Start the Bot
    updater.start_polling()

    # Run the bot until you press Ctrl-C or the process receives SIGINT,
    # SIGTERM or SIGABRT. This should be used most of the time, since
    # start_polling() is non-blocking and will stop the bot gracefully.
    updater.idle()


if __name__ == "__main__":
    get_answer()

Le llamo la atención sobre el hecho de que hay 2 mecanismos para lanzar un bot:

  • Sondeo estándar: sondeo periódico de Bot utilizando herramientas API estándar de Telegram para nuevos eventos ( updater.start_polling());
  • Webhook: iniciamos nuestro servidor con un punto final, al que llegan los eventos del bot, requiere HTTPS.

Como ya notó, para simplificar, usamos el sondeo estándar.

2. Completamos los manejadores estándar con lógica


Comencemos con una simple, complete el inicio y los controladores de ayuda con respuestas estándar, obtenemos algo como esto:

def start(update, context):
    """Send a message when the command /start is issued."""
    update.message.reply_text("""
!
        COVID-19.
:
- *  ?*
- *  ?*
- *   ?*
 ..
 !
    """, parse_mode=telegram.ParseMode.MARKDOWN)


def help(update, context):
    """Send a message when the command /help is issued."""
    update.message.reply_text("""
     (  COVID-19).
:
- *  ?*
- *  ?*
- *   ?*
 ..
 !
    """, parse_mode=telegram.ParseMode.MARKDOWN)

Ahora, cuando el usuario envía los comandos / start o / help, recibirá la respuesta prescrita por nosotros. Le llamo la atención sobre el hecho de que el texto está formateado en Markdown

parse_mode=telegram.ParseMode.MARKDOWN

A continuación, agregue el registro de errores al controlador de errores:

def error(update, context):
    """Log Errors caused by Updates."""
    logger.warning('Update "%s" caused error "%s"', update, context.error)

Ahora, verifiquemos si nuestro Bot funciona. Copiar todo el código escrito en un solo archivo, por ejemplo app.py . Añadir las importaciones necesarias .

Ejecute el archivo y vaya a Telegram ( no olvide insertar su Token en el código ). Escribimos los comandos / start y / help y nos regocijamos:



3. Procesamos el mensaje y generamos una respuesta.


Lo primero que necesitamos para responder la pregunta es la Base de conocimiento. Lo más simple que puede hacer es crear un archivo json simple en forma de valores Key-Value, donde Key es el texto de la pregunta propuesta y Value es la respuesta a la pregunta. Ejemplo de base de conocimiento:

{
  "      ?": "  —  .     -     ,     ,     ,        .    ,      ,   .        ,     .",
  "   ?": "  :\n     \n    \n    \n     \n\n         ,    .",
  "  ?": " :\n- (    , , )\n- (  )",
  }

El algoritmo para responder la pregunta será el siguiente:

  1. Recibimos el texto de la pregunta del usuario;
  2. Lematizar todas las palabras en el texto del usuario;
  3. No comparamos claramente el texto resultante con todas las preguntas lematizadas de la base de conocimiento ( distancia de Levenshtein );
  4. Seleccionamos la pregunta más "similar" de la base de conocimiento;
  5. Enviamos la respuesta a la pregunta seleccionada al usuario.

Para implementar nuestros planes, necesitamos bibliotecas: fuzzywuzzy (para comparaciones difusas) y pymorphy2 (para lematización).

Cree un nuevo archivo e implemente el algoritmo sonado:

import json
from fuzzywuzzy import fuzz
import pymorphy2

#   
morph = pymorphy2.MorphAnalyzer()
#  
with open("faq.json") as json_file:
    faq = json.load(json_file)


def classify_question(text):
    #  
    text = ' '.join(morph.parse(word)[0].normal_form for word in text.split())
    questions = list(faq.keys())
    scores = list()
    #      
    for question in questions:
        #    
        norm_question = ' '.join(morph.parse(word)[0].normal_form for word in question.split())
        #       
        scores.append(fuzz.token_sort_ratio(norm_question.lower(), text.lower()))
    # 
    answer = faq[questions[scores.index(max(scores))]]

    return answer

Antes de escribir un controlador de mensajes, escribiremos una función que guarde el historial de correspondencia en un archivo tsv:

def dump_data(user, question, answer):
    username = user.username
    full_name = user.full_name
    id = user.id

    str = """{username}\t{full_name}\t{id}\t{question}\t{answer}\n""".format(username=username,
                                                                 full_name=full_name,
                                                                 id=id,
                                                                 question=question,
                                                                 answer=answer)

    with open("/data/dump.tsv", "a") as myfile:
        myfile.write(str)

Ahora, utilizamos el método que escribimos en el mensaje del mensaje de texto del controlador de mensajes:

def message(update, context):
    """Answer the user message."""
    # 
    answer = classify_question(update.message.text)
    #  
    dump_data(update.message.from_user, update.message.text, answer)
    # 
    update.message.reply_text(answer)

Voila, ahora ve a Telegram y disfruta de la escritura:



4. Configure Docker e implemente la aplicación.


Como decía el clásico: "Si ejecutas, entonces es hermoso ejecutarlo". Para que tengamos todo como personas, configuraremos la contenedorización con Docker Compose.

Para esto necesitamos:

  1. Crear archivo Docker: define la imagen del contenedor y el punto de entrada;
  2. Crear docker-compose.yml: inicia muchos contenedores con un solo Dockerfile (en nuestro caso no es necesario, pero en caso de que tenga muchos servicios, será útil).
  3. Cree boot.sh (el script es responsable directamente del lanzamiento).

Entonces, el contenido del Dockerfile:

#
FROM python:3.6.6-slim

#  
WORKDIR /home/alex/covid-bot

#  requirements.txt
COPY requirements.txt ./

# Install required libs
RUN pip install --upgrade pip -r requirements.txt; exit 0

#      
COPY data data

#   
COPY app.py faq.json reply_generator.py boot.sh  ./

#   
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# 
RUN chmod +x boot.sh

#  
ENTRYPOINT ["./boot.sh"]

El contenido de docker-compose.yml:

# docker-compose
version: '2'
#  
services:
  bot:
    restart: unless-stopped
    image: covid19_rus_bot:latest
    container_name: covid19_rus_bot
    #    boot.sh
    environment:
      - SERVICE_TYPE=covid19_rus_bot
    # volume      
    volumes: 
        - ./data:/data

El contenido de boot.sh:

#!/bin/bash
if [ -n $SERVICE_TYPE ]
then
  if [ $SERVICE_TYPE == "covid19_rus_bot" ]
  then
    exec python app.py
    exit
  fi
else
  echo -e "SERVICE_TYPE not set\n"
fi

Entonces, estamos listos, para comenzar todo esto necesita ejecutar los siguientes comandos en la carpeta del proyecto:

sudo docker build -t covid19_rus_bot:latest .
sudo docker-compose up

Eso es todo, nuestro bot está listo.

En lugar de una conclusión


Como se esperaba, todo el código está disponible en el repositorio .

Este enfoque, mostrado por mí, se puede aplicar en cualquier caso para responder preguntas frecuentes, ¡solo personalice la base de conocimiento! Con respecto a la base de conocimientos, también se puede mejorar cambiando la estructura de Clave y Valor a matrices, por lo que cada par será una serie de preguntas potenciales sobre un tema y una serie de respuestas potenciales (para una variedad de respuestas, puede elegir al azar). Naturalmente, el enfoque basado en reglas no es demasiado flexible para escalar, pero estoy seguro de que este enfoque resistirá una base de conocimiento con aproximadamente 500 preguntas.

Los que han leído hasta el final los invito a probar mi Bot aquí .

All Articles