COVID-19 Telegram-bot // Nous répondons automatiquement aux questions fréquemment posées

Dans le contexte du battage médiatique universel sur le coronavirus, j'ai décidé de faire au moins quelque chose d'utile (mais pas moins de battage médiatique). Dans cet article, je vais vous expliquer comment créer et déployer le Telegram Bot en utilisant des méthodes NLP basées sur des rÚgles en 2,5 heures (c'est le temps qu'il m'a fallu) pour répondre aux questions de la FAQ en utilisant le cas COVID-19 comme exemple.

Au cours du travail, nous utiliserons le bon vieux Python, l'API Telegram, quelques bibliothĂšques NLP standard, ainsi que Docker.



UFO Care Minute


La pandĂ©mie COVID-19, une infection respiratoire aiguĂ« potentiellement grave causĂ©e par le coronavirus SARS-CoV-2 (2019-nCoV), a Ă©tĂ© officiellement annoncĂ©e dans le monde. Il y a beaucoup d'informations sur HabrĂ© sur ce sujet - rappelez-vous toujours qu'il peut ĂȘtre Ă  la fois fiable / utile, et vice versa.

Nous vous invitons à critiquer toute information publiée.


Sources officielles

, .

Lavez-vous les mains, prenez soin de vos proches, restez Ă  la maison dans la mesure du possible et travaillez Ă  distance.

Lire les publications sur: coronavirus | travail Ă  distance

BrÚve préface


Cet article dĂ©crit le processus de crĂ©ation d'un Bot Telegram simple rĂ©pondant aux questions de la FAQ sur COVID-19. La technologie de dĂ©veloppement est extrĂȘmement simple et polyvalente et peut ĂȘtre utilisĂ©e pour tout autre cas. Je souligne encore une fois que je ne prĂ©tends pas ĂȘtre Ă  la pointe de la technologie, mais seulement proposer une solution simple et efficace qui peut ĂȘtre rĂ©utilisĂ©e.

Étant donnĂ© que je crois que le lecteur de cet article a dĂ©jĂ  une certaine expĂ©rience avec Python, nous supposerons que vous avez dĂ©jĂ  installĂ© Python 3.X et les outils de dĂ©veloppement nĂ©cessaires (PyCharm, VS Code), vous pouvez crĂ©er un bot dans Telegram via BotFather, et par consĂ©quent, je vais sauter ces choses.

1. Configurer l'API


La premiĂšre chose que vous devez installer est la bibliothĂšque d'encapsuleur pour l'API Telegram " python-telegram-bot ". La commande standard pour cela est:

pip install python-telegram-bot --upgrade

Ensuite, nous allons construire le cadre de notre petit programme en définissant des "gestionnaires" pour les événements Bot suivants:

  • start - Commande de lancement de Bot;
  • help - commande d'aide (aide);
  • message - traitement des messages texte;
  • erreur - une erreur.

La signature des gestionnaires ressemblera Ă  ceci:

def start(update, context):
    #   
    pass


def help(update, context):
    #  
    pass


def message(update, context):
    #  
    pass


def error(update, context):
    # 
    pass

Ensuite, par analogie avec l'exemple de la documentation de la bibliothÚque, nous définissons la fonction principale dans laquelle nous affectons tous ces gestionnaires et démarrons le 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()

J'attire votre attention sur le fait qu'il existe 2 mécanismes pour lancer un bot:

  • Interrogation standard - interrogation pĂ©riodique de Bot Ă  l'aide des outils standard de l'API Telegram pour les nouveaux Ă©vĂ©nements ( updater.start_polling());
  • Webhook - nous dĂ©marrons notre serveur avec un point de terminaison, auquel les Ă©vĂ©nements du bot arrivent, il nĂ©cessite HTTPS.

Comme vous l'avez déjà remarqué, pour plus de simplicité, nous utilisons l'interrogation standard.

2. Nous remplissons les gestionnaires standard de logique


Commençons par une simple, remplissons le début et aidons les gestionnaires avec des réponses standard, nous obtenons quelque chose comme ceci:

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)

Désormais, lorsque l'utilisateur envoie les commandes / start ou / help, il recevra la réponse que nous lui avons prescrite. J'attire votre attention sur le fait que le texte est formaté en Markdown

parse_mode=telegram.ParseMode.MARKDOWN

Ensuite, ajoutez la journalisation des erreurs au gestionnaire d'erreurs:

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

Maintenant, vérifions si notre Bot fonctionne. Copiez tout le code écrit dans un seul fichier, par exemple app.py . Ajoutez les importations nécessaires .

Exécutez le fichier et accédez à Telegram ( n'oubliez pas d'insérer votre Token dans le code ). Nous écrivons les commandes / start et / help et nous réjouissons:



3. Nous traitons le message et générons une réponse


La premiĂšre chose dont nous avons besoin pour rĂ©pondre Ă  la question est la base de connaissances. La chose la plus simple que vous pouvez faire est de crĂ©er un fichier json simple sous la forme de valeurs de valeur-clĂ©, oĂč ClĂ© est le texte de la question proposĂ©e et Valeur est la rĂ©ponse Ă  la question. Exemple de base de connaissances:

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

L'algorithme pour répondre à la question sera le suivant:

  1. Nous obtenons le texte de la question de l'utilisateur;
  2. Lemmatiser tous les mots du texte de l'utilisateur;
  3. Nous ne comparons pas clairement le texte résultant avec toutes les questions lemmatisées de la base de connaissances ( distance de Levenshtein );
  4. Nous sélectionnons la question la plus «similaire» dans la base de connaissances;
  5. Nous envoyons la réponse à la question sélectionnée à l'utilisateur.

Pour mettre en Ɠuvre nos plans, nous avons besoin de bibliothùques: fuzzywuzzy (pour les comparaisons floues) et pymorphy2 (pour la lemmatisation).

Créez un nouveau fichier et implémentez l'algorithme sondé:

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

Avant d'Ă©crire un gestionnaire de messages, nous allons Ă©crire une fonction qui enregistre l'historique de la correspondance dans un fichier 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)

Maintenant, nous utilisons la méthode que nous avons écrite dans le message du gestionnaire de message texte:

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, allez maintenant Ă  Telegram et profitez de l'Ă©criture:



4. Configurer Docker et déployer l'application


Comme le disait le classique: "Si vous exécutez, alors c'est beau à exécuter.", Pour que nous ayons tout en tant que personnes, nous allons configurer la conteneurisation à l'aide de Docker Compose.

Pour cela, nous avons besoin de:

  1. Créer Dockerfile - définit l'image du conteneur et le point d'entrée;
  2. Create docker-compose.yml - lance de nombreux conteneurs à l'aide d'un seul Dockerfile (dans notre cas, ce n'est pas nécessaire, mais si vous avez de nombreux services, cela sera utile.)
  3. Créez boot.sh (le script est directement responsable du lancement).

Ainsi, le contenu du 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"]

Le contenu 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

Le contenu 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

Donc, nous sommes prĂȘts, pour dĂ©marrer tout cela, vous devez exĂ©cuter les commandes suivantes dans le dossier du projet:

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

VoilĂ , notre bot est prĂȘt.

Au lieu d'une conclusion


Comme prévu, tout le code est disponible dans le référentiel .

Cette approche, montrĂ©e par moi, peut ĂȘtre appliquĂ©e dans tous les cas pour rĂ©pondre aux questions de la FAQ, il suffit de personnaliser la base de connaissances! En ce qui concerne la base de connaissances, elle peut Ă©galement ĂȘtre amĂ©liorĂ©e en changeant la structure de la clĂ© et de la valeur en tableaux, de sorte que chaque paire sera un tableau de questions potentielles sur un sujet et un tableau de rĂ©ponses potentielles (pour un changement, les rĂ©ponses peuvent ĂȘtre choisies au hasard). Naturellement, l'approche basĂ©e sur les rĂšgles n'est pas trop flexible pour la mise Ă  l'Ă©chelle, mais je suis sĂ»r que cette approche rĂ©sistera Ă  une base de connaissances avec environ 500 questions.

Ceux qui ont lu jusqu'Ă  la fin, je vous invite Ă  essayer mon Bot ici .

All Articles