COVID-19 Telegram-bot // Respondemos perguntas frequentes automaticamente

No contexto do hype universal no Coronavírus, decidi fazer pelo menos algo útil (mas não menos hype). Neste artigo, falarei sobre como criar e implantar o Telegram Bot usando os métodos da PNL baseada em regras em 2,5 horas (o quanto me levou) para responder a perguntas frequentes usando o caso COVID-19 como exemplo.

No decorrer do trabalho, usaremos a boa e antiga API Python, Telegram, algumas bibliotecas padrão de PNL, além do Docker.



Minuto de Cuidados com OVNI


A pandemia de COVID-19, uma infecção respiratória aguda potencialmente grave causada pelo coronavírus SARS-CoV-2 (2019-nCoV), foi anunciada oficialmente no mundo. Há muitas informações sobre Habré sobre esse tópico - lembre-se sempre de que pode ser confiável / útil e vice-versa.

Pedimos que você seja crítico com qualquer informação publicada.



Lave as mãos, cuide de seus entes queridos, fique em casa sempre que possível e trabalhe remotamente.

Leia publicações sobre: coronavírus | trabalho remoto

Prefácio breve


Este artigo descreve o processo de criação de um simples Telegram Bot, respondendo a perguntas frequentes no COVID-19. A tecnologia de desenvolvimento é extremamente simples e versátil e pode ser usada para qualquer outro caso. Enfatizo mais uma vez que não pretendo ser o estado da arte, mas apenas ofereço uma solução simples e eficaz que pode ser reutilizada.

Como acredito que o leitor deste artigo já tenha alguma experiência com o Python, assumiremos que você já possui o Python 3.X instalado e as ferramentas de desenvolvimento necessárias (PyCharm, VS Code), você pode criar um Bot no Telegram via BotFather e portanto, vou pular essas coisas.

1. Configure API


A primeira coisa que você precisa instalar é a biblioteca de wrapper para a API do Telegram " python-telegram-bot ". O comando padrão para isso é:

pip install python-telegram-bot --upgrade

Em seguida, criaremos a estrutura do nosso pequeno programa, definindo "manipuladores" para os seguintes eventos de bot:

  • start - comando de inicialização do Bot;
  • help - help command (help);
  • mensagem - processamento de mensagens de texto;
  • erro - um erro.

A assinatura dos manipuladores ficará assim:

def start(update, context):
    #   
    pass


def help(update, context):
    #  
    pass


def message(update, context):
    #  
    pass


def error(update, context):
    # 
    pass

Em seguida, por analogia com o exemplo da documentação da biblioteca, definimos a função principal na qual atribuímos todos esses manipuladores e iniciamos o 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()

Chamo sua atenção para o fato de que existem 2 mecanismos para iniciar um bot:

  • Pesquisa Padrão - pesquisa periódica do Bot usando ferramentas padrão da API Telegram para novos eventos ( updater.start_polling());
  • Webhook - iniciamos nosso servidor com um endpoint, para o qual os eventos do bot chegam, ele requer HTTPS.

Como você já percebeu, por simplicidade, usamos o polling padrão.

2. Nós preenchemos manipuladores padrão com lógica


Vamos começar com um simples, preencher o início e ajudar os manipuladores com respostas padrão, temos algo parecido com isto:

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)

Agora, quando o usuário enviar os comandos / start ou / help, ele receberá a resposta prescrita por nós. Chamo a atenção para o fato de o texto estar formatado em Markdown

parse_mode=telegram.ParseMode.MARKDOWN

Em seguida, adicione o log de erros ao manipulador de erros:

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

Agora, vamos verificar se o nosso Bot funciona. Copie o código inteiro escrito em um único arquivo, por exemplo app.py . Adicione as importações necessárias .

Execute o arquivo e vá para Telegram ( não esqueça de inserir seu token no código ). Escrevemos os comandos / start e / help e nos alegramos:



3. Processamos a mensagem e geramos uma resposta


A primeira coisa que precisamos responder à pergunta é a Base de Conhecimento. A coisa mais simples que você pode fazer é criar um arquivo json simples na forma de valores de valor-chave, em que chave é o texto da pergunta proposta e valor é a resposta para a pergunta. Exemplo da Base de Conhecimento:

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

O algoritmo para responder à pergunta será o seguinte:

  1. Recebemos o texto da pergunta do usuário;
  2. Lematize todas as palavras no texto do usuário;
  3. Não comparamos claramente o texto resultante com todas as perguntas lematizadas da base de conhecimento ( distância de Levenshtein );
  4. Selecionamos a pergunta mais "semelhante" da base de conhecimento;
  5. Enviamos a resposta para a pergunta selecionada ao usuário.

Para implementar nossos planos, precisamos de bibliotecas: fuzzywuzzy (para comparações fuzzy) e pymorphy2 (para lematização).

Crie um novo arquivo e implemente o algoritmo que soa:

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 escrever um manipulador de mensagens, escreveremos uma função que salva o histórico de correspondência em um arquivo 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)

Agora, usamos o método que escrevemos na mensagem do manipulador de mensagens de texto da mensagem:

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, agora vá ao Telegram e aprecie a escrita:



4. Configure o Docker e implante o aplicativo


Como o clássico disse: "Se você executar, é bonito executar". Para que tenhamos tudo como pessoas, configuraremos a conteinerização usando o Docker Compose.

Para isso, precisamos:

  1. Criar arquivo de encaixe - define a imagem do contêiner e o ponto de entrada;
  2. Create docker-compose.yml - inicia muitos contêineres usando um único Dockerfile (no nosso caso, não é necessário, mas, caso você tenha muitos serviços, será útil).
  3. Crie boot.sh (o script é responsável diretamente pelo lançamento).

Portanto, o conteúdo do 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"]

O conteúdo do 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

O conteúdo do 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

Então, estamos prontos, para iniciar tudo isso, você precisa executar os seguintes comandos na pasta do projeto:

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

É isso aí, nosso bot está pronto.

Em vez de uma conclusão


Como esperado, todo o código está disponível no repositório .

Essa abordagem, mostrada por mim, pode ser aplicada em qualquer caso para responder a perguntas frequentes, basta personalizar a base de conhecimento! Com relação à base de conhecimento, ela também pode ser aprimorada alterando a estrutura de Chave e Valor para matrizes, para que cada par seja uma matriz de possíveis perguntas sobre um tópico e uma série de possíveis respostas para elas (para uma mudança, as respostas podem ser escolhidas aleatoriamente). Naturalmente, a abordagem baseada em regras não é muito flexível para o dimensionamento, mas tenho certeza de que essa abordagem suportará uma base de conhecimento com cerca de 500 perguntas.

Quem leu até o fim, convido você a experimentar o meu Bot aqui .

All Articles