Chatboard RAG pour les questions-réponses scientifiques
Posez une question sur un corpus d'articles de recherche et recevez une réponse avec ses sources. Tout démarre par une seule commande, sur un ordinateur portable ordinaire.
L'énoncé du problème
La littérature scientifique grossit plus vite qu'un chercheur ne peut la lire, et les questions qui comptent traversent un corpus entier plutôt qu'un seul article. La recherche par mots-clés rend une liste classée et laisse au lecteur le soin d'ouvrir, de parcourir, de comparer et d'assembler la réponse. Ce système renvoie directement la réponse citée, en flux, sur un corpus d'articles de recherche en traitement automatique des langues (TAL), à partir d'une seule commande Docker Compose, sur processeur ou sur GPU.
La tâche est la réponse à des questions en domaine ouvert sur un corpus préindexé. À partir d'une requête en langage naturel, le système retrouve les 7 meilleurs passages dans un index de 47 810 fragments bâti sur FAISS et BM25 (tiré du jeu de référence QASPER, 5 049 paires question-réponse issues d'articles de TAL), puis rédige une réponse ancrée et citée avec un modèle local. Quand le contexte retrouvé passe sous le seuil de confiance, la chaîne s'arrête plutôt que de générer sur des preuves minces.
L'architecture de l'application
La pile compte 2 services sous Docker Compose : un serveur FastAPI sur le port 8080 et une interface de discussion Chainlit sur le port 8001. Les deux conteneurs tournent en network_mode: host, ce qui donne au serveur un accès direct à Ollama sur la machine hôte. L'interface attend un serveur validé par sa sonde de santé (condition: service_healthy), en interrogeant /health toutes les 30 secondes avec une période de démarrage de 300 secondes, pour que le téléchargement des artefacts au premier démarrage ait le temps de finir.
Les index FAISS pèsent environ 230 Mo (dense.index à 147 Mo, les métadonnées à 31 Mo, sparse.pkl à 51 Mo). gdown les récupère depuis Google Drive au premier démarrage et les écrit sur un volume monté, donc chaque redémarrage suivant est immédiat. Le serveur détecte CUDA et choisit HuggingFace Transformers sur GPU ou Ollama sur processeur, sans que l'utilisateur change quoi que ce soit à la configuration.
La chaîne RAG, en 4 étapes
Étape 1. La recherche hybride. La recherche dense FAISS (plongements SPECTER2, 768 dimensions, IndexFlatIP, 47 810 vecteurs) fusionne avec la recherche creuse BM25 par fusion de rang réciproque (k=60) pour rendre les 50 meilleurs candidats. Une requête de moins de 10 mots déclenche HyDE : le modèle rédige un passage hypothétique qui devient la requête dense, ce qui relève le rappel sur les entrées courtes ou ambiguës.
Étape 2. Le reclassement ColBERT v2. Les plongements de tokens des documents sont calculés hors ligne, donc le temps de requête ne coûte qu'un MaxSim sur des plongements de tokens. Cela achète la précision d'un encodeur croisé à l'échelle de la recherche, et ramène les 50 meilleurs candidats aux 7 meilleurs.
Étape 3. La porte CRAG. Trois issues : Correct fait passer les passages vers la génération, Incorrect arrête la génération et renvoie un avertissement de faible confiance, et Ambigu produit une réponse nuancée. Le script calibrate_crag.py du dépôt de recherche fixe le seuil.
Étape 4. La génération en flux. Llama-3.1-8B-Instruct, via Ollama sur processeur ou HuggingFace Transformers sur GPU, rédige une réponse avec sa chaîne de raisonnement, des citations [Doc N] en ligne et 6 tours de mémoire de conversation. Une file asynchrone pousse les jetons vers un point de terminaison de diffusion NDJSON.
La chaîne d'intégration et de déploiement continus
Chaque envoi sur master déclenche un flux GitHub Actions qui s'authentifie auprès de GHCR avec le GITHUB_TOKEN automatique, donc sans aucun secret à gérer, puis construit et envoie les deux images Docker sous 2 étiquettes : :latest et :sha-<commit>. C'est l'étiquette épinglée au commit qui fait d'un retour arrière un changement d'une seule ligne.
L'évaluation
Notée sur 84 questions QASPER. La précision des citations ALCE est passée de 0,057 à 0,8373 grâce à un seul changement, extract_final_answer(). Le bloc de raisonnement ne portait aucune citation et gonflait le compte des faux négatifs du rappel des citations. Retirez-le, et les réponses citées sont notées pour ce qu'elles sont. C'est le genre de défaut qu'un harnais d'évaluation fait remonter et qu'une démonstration cache.
L'effet obtenu
- Un déploiement sans construction. L'utilisateur lance docker compose up et récupère des images GHCR déjà construites. Aucun environnement Python à créer, aucun script de téléchargement de modèle, et aucun index à gérer à la main.
- Le système tourne sur processeur. Le moteur Ollama fonctionne sur du matériel courant, à environ 2 à 5 jetons par seconde.
- Un raisonnement auditable. Chaque réponse montre son bloc de raisonnement avant la réponse finale, pour qu'un lecteur vérifie la logique qui l'a produite.
- Les questions de suite fonctionnent. 6 tours de mémoire évitent à l'utilisateur de redonner le contexte.