Assistant documentaire RAG souverain
Contexte
J'ai conçu et développé de A à Z une chaîne RAG (Retrieval-Augmented Generation) 100 % locale, hébergée sur un serveur GPU sur site, sans aucun appel à un service cloud. L'objectif : permettre d'interroger en langage naturel l'ensemble d'un fonds documentaire d'entreprise hétérogène (PDF natifs et scannés, Word, Excel, CSV, présentations, images), soit une dizaine de milliers de documents découpés en plus de 100 000 extraits indexés, dans un contexte où la confidentialité des données est une contrainte non négociable.
Architecture
- DocumentsPDF natifs et scannés, Word, Excel, CSV, images
- IngestionExtraction, OCR, découpage adapté au format
- IndexationQdrant : vecteur sémantique et vecteur mots-clés (BM25)
- Recherche hybrideFusion des candidats puis reranking
- LLM localModèle Qwen servi par Ollama, sans cloud
- APICompatible OpenAI, déployée sous Docker
Résultats
- > 100 000extraits indexés
- 79 % → 91 %qualité de la recherche
- 99 %réponses correctes, 0 inventée
- ≈ 6 stemps de réponse médian
Voir le détail technique
Benchmark de solutions concurrentes
Une part importante du projet a consisté à comparer objectivement plusieurs architectures avant de choisir. J'ai d'abord évalué une solution clé en main (le RAG intégré d'Open WebUI), puis construit et fait tourner en parallèle trois chaînes RAG complètes sur le même corpus : PostgreSQL + pgvector, Qdrant + bge-m3, et Qdrant + Qwen3-VL-Embedding (versions 2B puis 8B). Toutes ont été mesurées sur les mêmes jeux de questions, ce qui m'a permis de trancher sur des chiffres et non sur des impressions. Au sein de la solution retenue, j'ai ensuite testé systématiquement chaque paramètre : recherche dense seule contre recherche hybride, profondeur de fusion (30 / 50 / 100 / 150 candidats), nombre d'extraits envoyés au LLM, reformulation des questions, découpage par taille fixe contre découpage par structure (titres et sections), et trois formats de représentation des tableurs. Chaque variante a été indexée dans sa propre collection pour pouvoir comparer et revenir en arrière sans risque. Plusieurs pistes séduisantes sur le papier (reformulation, découpage « intelligent ») ont été écartées parce que les mesures ne montraient pas de gain réel.
Ingestion et indexation
Extraction du texte par format (avec OCR pour les documents scannés), découpage en extraits adapté à chaque type de document, et traitement spécifique des tableurs : chaque ligne est réécrite sous la forme « Colonne : valeur » après détection automatique de la ligne d'en-tête, ce qui rend les tableaux interrogeables par le modèle. Un garde-fou de marquage exclut automatiquement de l'index les documents portant une mention de protection.
Architecture retenue
Chaque extrait est indexé dans Qdrant avec deux représentations : un vecteur dense sémantique (Qwen3-VL-Embedding-8B, 4096 dimensions) et un vecteur creux lexical BM25. La recherche est hybride : 200 candidats de chaque côté, fusion par Reciprocal Rank Fusion, puis reranking par cross-encoder (bge-reranker-v2-m3) pour ne garder que les 12 meilleurs extraits, transmis à un LLM local (famille Qwen, quantifié, servi par Ollama). Un routage oriente les questions de comptage ou de liste vers le catalogue de fichiers et les demandes de résumé vers le document complet. Le tout est exposé via une API compatible OpenAI et déployé en conteneurs Docker.
Méthode d'évaluation et résultats
Pour rendre ces comparaisons fiables, j'ai mis en place des bancs d'évaluation automatiques : plusieurs centaines de questions générées à partir du corpus (contenu, noms de fichiers, images, tableurs), mesurées en recall@k et MRR, complétées par un LLM-juge qui note la justesse des réponses et détecte les hallucinations, ainsi qu'une analyse des échecs (doublons, mauvais classement, information absente). Une variante n'est mise en service que si elle améliore le score global sans régression sur les autres bancs. Résultat : les itérations successives ont fait passer la qualité de la recherche de 79 % à 91 %, avec 99 % de réponses jugées correctes et zéro réponse inventée, refus correct sur les questions pièges, pour un temps de réponse médian d'environ 6 secondes.
Pilotage d'agents IA
Le développement a été mené en pilotant des agents IA de programmation : je définissais l'architecture, les contraintes et les critères de réussite, les agents écrivaient et exécutaient le code sur le serveur, et je validais chaque étape par les mesures.