Skip to content

2024

ChatBot Memory

Nous présentons ici une approche pour la gestion de la mémoire à court terme dans les chatbots, en utilisant une combinaison de techniques de stockage et de résumé automatique pour optimiser le contexte conversationnel. La méthode introduite repose sur une structure de mémoire dynamique qui limite la taille des données tout en préservant les informations essentielles à travers des résumés intelligents. Cette approche permet non seulement d'améliorer la fluidité des interactions mais aussi d'assurer une continuité contextuelle lors de longues sessions de dialogue. En outre, l'utilisation de techniques asynchrones garantit que les opérations de gestion de la mémoire n'interfèrent pas avec la réactivité du chatbot.

Modélisation Mathématique de la Gestion des Conversations

Dans cette section, nous formalisons mathématiquement la gestion de la mémoire de conversation dans le chatbot. La mémoire est structurée comme une liste de paires représentant les échanges entre l'utilisateur et le bot.

Structure de la Mémoire de Conversation

La mémoire de conversation peut être définie comme une liste ordonnée de paires (u_i, d_i), où u_i représente l'entrée utilisateur et d_i la réponse du bot pour le i-ième échange. Cette liste est notée \mathcal{C} :

\mathcal{C} = [(u_1, d_1), (u_2, d_2), \ldots, (u_n, d_n)]

n est le nombre total d'échanges dans l'historique actuel.

Mise à Jour de la Mémoire

Lorsqu'un nouvel échange se produit, une nouvelle paire (u_{n+1}, d_{n+1}) est ajoutée à la mémoire. Si la taille de \mathcal{C} dépasse une limite maximale prédéfinie M_{\text{max}}, l'échange le plus ancien est retiré :

\mathcal{C} = \begin{cases} \mathcal{C} \cup \{(u_{n+1}, d_{n+1})\}, & \text{si } |\mathcal{C}| < M_{\text{max}} \\ (\mathcal{C} \setminus \{(u_1, d_1)\}) \cup \{(u_{n+1}, d_{n+1})\}, & \text{si } |\mathcal{C}| = M_{\text{max}} \end{cases}

Comptage des Mots

Pour gérer l'espace de mémoire et décider quand la compression est nécessaire, nous calculons le nombre total de mots W(\mathcal{C}) dans la mémoire :

W(\mathcal{C}) = \sum_{(u_i, d_i) \in \mathcal{C}} (|u_i| + |d_i|)

|u_i| et |d_i| sont respectivement le nombre de mots dans u_i et d_i.

Compression de la Mémoire

Lorsque W(\mathcal{C}) dépasse un seuil W_{\text{max}}, la mémoire est compressée pour maintenir la pertinence du contexte. Cette compression est réalisée par un modèle de résumé \mathcal{S}, tel que BART :

\mathcal{C}_{\text{compressed}} = \mathcal{S}(\mathcal{C})

\mathcal{C}_{\text{compressed}} est la version résumée de la mémoire, réduisant le nombre total de mots tout en préservant l'essence des interactions passées.

Intégration dans le Modèle de Langage

Le modèle de langage utilise le contexte compressé pour générer des réponses pertinentes. Le prompt P utilisé par le modèle est construit comme suit :

P = f(\mathcal{C}_{\text{compressed}}, \text{contexte})

\text{contexte} est le contexte supplémentaire récupéré à partir d'un pipeline RAG, et f est une fonction de concaténation qui prépare le texte pour le modèle.

Cette approche assure que le chatbot dispose toujours d'un contexte conversationnel à jour, permettant des interactions plus naturelles et engageantes avec l'utilisateur.

Implémentation du Code pour la Gestion de la Mémoire d'un Chatbot

Dans cette section, nous allons examiner un exemple de code en Python qui illustre la gestion de la mémoire dans un chatbot. Le code utilise PyTorch et les transformers de Hugging Face pour gérer et compresser l'historique des conversations.

Préparation de l'Environnement

Nous commençons par vérifier si un GPU est disponible, ce qui permet d'accélérer le traitement si nécessaire.

import torch
from transformers import pipeline
import logging

if torch.cuda.is_available():
    device: int = 0
else:
    device: int = -1

MAX_MEMORY_SIZE: int = 2000

Définition de la Classe ChatbotMemory

La classe ChatbotMemory gère l'historique des conversations et effectue des opérations de mise à jour et de compression. Donc à chaques fois que update_memory est appelé, le texte en mémoire est compté et traité au besoin.

class ChatbotMemory:
    def __init__(self, conv: list = []):
        self.conversation_history = conv

    def update_memory(self, user_input: str, bot_response: str) -> None:
        self.conversation_history.append(f"'user': {user_input}, 'bot': {bot_response}")

        if memory_counter(self.conversation_history) > 1000:
            self.conversation_history = compressed_memory(self.conversation_history)
            logging.info("Mémoire compressée.")

        if len(self.conversation_history) > MAX_MEMORY_SIZE:
            self.conversation_history.pop(0)
            logging.info("Mémoire réduite.")
        return 0

    def get_memory(self):
        return self.conversation_history

Compression et Comptage de la Mémoire

La fonction _get_compressed_memory utilise le modèle BART pour résumer l'historique des conversations.

def _get_compressed_memory(sentence: str) -> str:
    summarizer = pipeline("summarization", model="facebook/bart-large-cnn", device=device)
    summary = summarizer(sentence, max_length=50, min_length=5, do_sample=False)
    return summary[0]['summary_text']

La fonction compressed_memory applique la fonction _get_compressed_memory à chaque segment de l'historique des conversations. Pour cela nous optimisons la procédure en effectuant un traitement par Batch. Cette méthode est dissocié de la fonction _get_compressed_memory de manière à pouvoir introduire de nouvelles méthodes de compression.

def compressed_memory(conv_hist: list) -> list:
    return [_get_compressed_memory(' '.join(conv_hist[i:i+5])) for i in range(0, len(conv_hist), 5)]

La fonction memory_counter compte le nombre total de mots dans l'historique. (Note qu'il pourrais être interessant de réaliser cette étape avec des tokens plutot que des mots.)

def memory_counter(conv_hist: list) -> int:
    st = ''.join(conv_hist)
    return len(st.split())

Conclusion

Ce code établit un cadre efficace pour la gestion de la mémoire dans un chatbot, en utilisant des techniques de compression pour maintenir un contexte pertinent et en améliorant la performance globale du système. L'utilisation de modèles de résumé comme BART assure que même lorsque la mémoire est compressée, le contexte essentiel est préservé.

Code sur GitHub

Sécurité de vos codes

La sécurité avec Bandit, un outil d'analyse de sécurité de code pour Python.

🔍 Pourquoi Bandit ?

Analyse automatique : Bandit passe en revue votre code pour détecter les usages courants qui peuvent être dangereux pour la sécurité.

Intégration facile : S'intègre dans vos pipelines CI/CD pour des contrôles de sécurité continus.

Mettre en place Bandit est aussi simple que:

​pip install bandit
bandit -r your_code.py​

Ajouter à un pipeline GitHub Action, à la suite des tests unitaires:

​name: Security check​
​run: bandit -r your_code.py

Vous pouvez aussi l'intégrer dans un nouveau workflows en ajoutant un nouveau fichier .yml.

Jetez un œil à l'image jointe pour voir Bandit en action dans un workflow GitHub Actions. Aucune vulnérabilité trouvée, c'est exactement ce que nous voulons voir !

image

Avec Bandit, votre code est surveillé pour éviter les erreurs courantes qui pourraient le compromettre et ce à chaque push ! C'est super pratique et cela doit devenir une habitude.

Automatisation des Tests

Vous le savez (enfin j'espère), les tests sont cruciaux pour tous les projets de développement. Mais les exécuter manuellement à chaque fois ? Pas très 2024 ! 🤖

Voici une astuce pour les développeurs soucieux d'efficacité : l'intégration de Pytest avec GitHub Actions.

Pytest est un framework de test pour vos applications Python. Et quand vous combinez cela avec la puissance des GitHub Actions, vous obtenez une suite de tests automatisés qui s'exécutent à chaque push ou pull request, assurant que vos modifications n'introduisent pas de régressions !

Pour configurer une action de base GitHub et exécuter vos tests Pytest, créez un fichier .github/workflows/python-app.yml​ dans votre repo et ajouter votre configuration comme sur mon exemple en photo.

image

🚀 Et voilà ! Vos tests se lancent automatiquement, vous offrant une tranquillité d'esprit.

Je suis persuadé que certains d'entre vous utilisent d'autres techniques ou des outils plus performants pour les tests, l'essentiel étant de toujours prévenir son code de toutes formes de régression !

La documentation technique et Sphinx

Ici, je ne vous parlerai pas du README (qui est un guide rapide), mais plutôt de la documentation technique.

Une documentation claire et accessible est aussi cruciale que le code lui-même. Elle guide les utilisateurs et les contributeurs, facilitant l'utilisation et la contribution au projet.

Voici comment structurer et publier une documentation d'un projet Python, en utilisant Sphinx pour la génération et Read the Docs pour l'hébergement.

  1. Structuration du Projet 📂

La première étape est d'organiser le projet en suivant les meilleures pratiques, avec des dossiers distincts pour les sources (src/), les tests (tests/), la documentation (docs/) et le script setup.py. Cette structure claire facilite la navigation et la maintenance du projet. (Je ferai un post sur toute cette partie.)

  1. Génération de Documentation avec Sphinx 📖

Sphinx transforme les docstrings en une documentation complète et bien formatée.

Nous utilisons des extensions comme autodoc pour générer automatiquement la documentation à partir des docstrings.

1/ Nous installons Sphinx :

pip install sphinx

2/ Nous créons les dossiers d'initialisation :

sphinx-quickstart docs

3/ Nous nous déplaçons dans docs et nous créons la documentation :

cd docs
make html

Nous pouvons supprimer la documentation avec : zsh make clean

  1. Configuration pour Read the Docs 🌐

Avec un fichier .readthedocs.yaml, nous configurons le processus de build sur Read the Docs, en spécifiant la version de Python et les dépendances nécessaires. Une fois cela fait, rendez-vous sur ReadTheDoc et sélectionnez le repo contenant votre projet. Cela assure que la documentation est automatiquement mise à jour et accessible en ligne à chaque commit. (Vous trouverez un exemple dans l'image de ce post.)

Le résultat ? Une documentation en ligne toujours à jour, facilement accessible par les utilisateurs et les contributeurs.

La documentation ne doit jamais être une réflexion après coup dans le développement logiciel. Elle est essentielle pour la transparence, l'accessibilité et la réussite à long terme d'un projet.