RAG : donner vos données au modèle
Les connaissances d'un LLM ont gelé à sa date de coupure d'entraînement ; vos documents changent chaque semaine. La génération augmentée par récupération (RAG) les dégèle : au moment de la requête, rapatriez les tranches pertinentes de vos données, collez-les dans le prompt, et laissez le modèle répondre à partir de preuves avec citations. Le modèle fait le raisonnement ; l'index fait la mémoire.
La pipeline, un exemple détaillé
Prenez un manuel de maintenance de 12,000 jetons et parcourez-le de bout en bout :
- Fragmenter. Découpez d'abord sur les titres (les sections survivent intactes), puis redécoupez toute section au-delà de ~400 jetons, en gardant ~60 jetons de chevauchement (10–15%). Des tailles de fragment sensées vont de 200 à 800 jetons. Résultat : environ 35 fragments.
- Vectoriser. Chaque fragment devient un vecteur — bge-m3 émet 1,024 flottants par fragment. Sur CPU, cela prend des secondes pour tout le manuel.
- Indexer. Stockez vecteurs + texte + métadonnées (fichier source, chemin de titres) dans une base vectorielle.
- Récupérer. Vectorisez la question, rapatriez les k = 5 meilleurs fragments par similarité cosinus (4–10 est la fourchette saine).
- Générer. Assemblez le prompt : instructions, les 5 fragments avec étiquettes d'ID, la question.
Le découpage sur frontières sémantiques bat les fenêtres fixes. Un tableau séparé de sa ligne de titre, ou une procédure numérotée coupée à l'étape 4, est un déchet irrécupérable quel que soit le talent de l'embedder.
Deux extras qui se remboursent immédiatement : stockez le chemin de titres (manual › §4 › Bolts) en métadonnées pour filtrer et citer précisément, et vectorisez la requête exactement comme l'utilisateur l'a tapée — le « nettoyage » par prétraitement rend généralement la récupération pire, pas meilleure.
Embeddings : l'exigence multilingue
L'embedder décide ce que « similaire » signifie. Pour des corpus coréens et mixtes coréen/anglais, le choix est étroit — les embedders anglais uniquement échouent en silence sur les requêtes coréennes, renvoyant des fragments plausibles mais faux :
| Modèle | Dims | Remarques |
|---|---|---|
| bge-m3 | 1024 | multilingue, coréen solide — le défaut local sûr |
| nomic-embed-text-v1.5 | 768 | solide en anglais, contexte long |
| all-MiniLM-L6-v2 | 384 | rapide, centré anglais, faible en coréen |
Les modèles d'embedding sont minuscules (0.1–0.6B paramètres) ; un cœur CPU mâche des milliers de fragments — inférence sur CPU. Vectorisez le corpus une fois, vectorisez les requêtes au moment de la question. Gardez l'embedder figé par index : changer de modèle en cours d'index fabrique des vecteurs d'espaces différents, et les scores de similarité deviennent du bruit.
Bases vectorielles
| Base | Quand |
|---|---|
| sqlite-vec | utilisateur unique, jusqu'à ~1M vecteurs, zéro ops — c'est un fichier |
| ChromaDB | prototypage local-first avec filtres de métadonnées |
| Qdrant | production : filtrage, recherche hybride, échelle |
Les trois tournent en local. Commencez avec sqlite-vec ; changez quand vous touchez vraiment son mur, pas avant.
Assembler le prompt
Réponds uniquement à partir des extraits ci-dessous. Cite chaque affirmation en [n].
Si les extraits ne contiennent pas la réponse, réponds « absent des docs ».
[1] (manuel §4.2) Serrer le boulon M8 à 12 Nm après lubrification...
[2] (manuel §7.1) Ne pas réutiliser les boulons étirés...
Question : quel couple s'applique au boulon M8 ?
Les citations laissent les utilisateurs vérifier au lieu de croire. La sortie « absent des docs » compte : sans elle, le modèle invente une réponse plutôt que de manquer le format.
Recherche hybride et reranking
La recherche vectorielle pure rate les chaînes exactes — codes d'erreur, noms de fonctions, références — que la recherche par mots-clés cloue. Hybride = BM25 + vecteurs, fusionnés par reciprocal rank fusion ; c'est quasi gratuit et régulièrement meilleur que chacun seul.
Pour la qualité, ajoutez un reranker : rapatriez 20–50 candidats, re-classez vers le top 5 avec un cross-encoder (la famille bge-reranker — même histoire multilingue que bge-m3). Le reranker lit fragment et requête ensemble, ce qu'un embedder mono-vecteur ne peut pas. Recette bon marché qui couvre la plupart des besoins : récupération hybride 25, rerank vers 5.
Diagnostiquer : échec de récupération ou de génération ?
Avant de régler quoi que ce soit, répondez à une question : le bon fragment a-t-il été récupéré ?
- Affichez les fragments top-k d'une requête échouée, avec scores.
- Si le fragment porteur de réponse n'est pas dans la liste, c'est un problème de récupération — corrigez le découpage, les embeddings, ou montez k.
- S'il y est mais que la réponse est fausse, c'est un problème de génération — plus gros modèle, meilleur prompt, rerank pour pousser dehors les distracteurs.
Ce seul test épargne des heures à régler la mauvaise moitié de la pipeline.
Modes de défaillance
| Symptôme | Cause | Correctif |
|---|---|---|
| bons documents récupérés, mauvaise réponse | distracteurs dans le top-k | rerank ; réduire k |
| la réponse existe dans les documents mais ne surgit jamais | famine du top-k ou mauvaises frontières | monter k ; fragmenter par titres |
| réponses assurées mais périmées | index plus vieux que les documents | réindexer à chaque changement |
| les requêtes coréennes ratent les documents anglais | embeddings monolingues | bge-m3 |
| les citations pointent le mauvais fragment | le fragment a perdu ses métadonnées source | stocker source + titre par fragment |
Mesurez, ne vibrez pas
Gardez un jeu doré de 20–50 vraies questions à réponses connues. Scorez deux chiffres séparément : taux de réussite de récupération (le bon fragment est-il dans le top-k ?) et exactitude de la réponse. Suivez-les après chaque changement de taille de fragment, d'embedder ou de k. Le travail RAG sans jeu doré, c'est de l'astrologie.
Quand plutôt faire du fine-tuning
Le fine-tuning change le comportement — format, style, terminologie. C'est un mauvais moyen d'ajouter des faits : le modèle ne peut pas citer ce sur quoi il a été réglé, et des documents frais signifient un nouvel entraînement. Règle empirique : faits → RAG, comportement → LoRA, les deux seulement quand vous avez épuisé le prompting : fine-tuning.
Piles locales, de la plus petite d'abord
Open WebUI livre dépôt de documents et récupération hybride intégrés — commencez là et vous n'écrirez peut-être jamais de code de pipeline. Le cœur honnête du RAG fait ~50 lignes ; écrivez-le une fois pour savoir ce que font les frameworks :
chunks = chunk_by_header(read("manual.md"), max_tokens=400, overlap=60)
index = [(embed(c), c) for c in chunks] # fait une fois
def answer(question):
hits = topk_by_cosine(embed(question), index, k=5)
ctx = "\n\n".join("[{}] {}".format(i, c) for i, (v, c) in enumerate(hits))
prompt = TEMPLATE.replace("{ctx}", ctx).replace("{q}", question)
return chat(prompt) # un petit modèle local suffit
Tendez-vous vers LlamaIndex ou LangChain quand vous avez besoin de permissions, de rafraîchissement planifié ou de harnais d'évaluation — pas pour la boucle ci-dessus. Pour les documents coréens spécifiquement, les embeddings multilingues (bge-m3) ne sont pas optionnels, et le découpage par titres devrait respecter la structure documentaire coréenne (titres 장/절) comme il respecte les titres markdown ##.
Voir aussi : fine-tuning · prompting · sortie structurée · inférence sur CPU