La pertinence de votre RAG se joue au découpage, avant l'embedding. Stratégies 2026 du fixe au sémantique, late chunking et contextual retrieval, avec arbitrages, pièges et recommandations concrètes.
Quand un système RAG répond à côté, le premier réflexe est d'incriminer le modèle d'embedding, le reranker ou le LLM. Dans la grande majorité des cas, le coupable est en amont, dans une étape que presque personne ne traite avec le sérieux qu'elle mérite : le chunking. C'est lui qui décide de ce qui sera vectorisé, donc de ce qui pourra — ou non — remonter à la requête. Aucun reranker ne récupère une information que le découpage a fait disparaître.
Le chunking est sous-estimé parce qu'il paraît trivial : couper un texte en morceaux, où est la difficulté ? La difficulté est que chaque coupure est une décision sémantique. Mal posée, elle scinde une idée en deux, mélange deux sujets dans un même vecteur, ou produit des fragments si génériques que la similarité vectorielle ne les distingue plus. La pertinence du RAG se joue largement ici, avant l'embedding. Cet article fait le tour des stratégies de découpage, des plus naïves aux approches 2026 (late chunking, contextual retrieval), avec arbitrages et recommandations concrètes.
Pourquoi un mauvais chunking casse le RAG
Un embedding est une compression. On résume un passage de texte en un vecteur de quelques centaines à quelques milliers de dimensions. Plus le passage est cohérent et focalisé, plus le vecteur est discriminant. Un mauvais découpage attaque cette propriété de plusieurs façons.
Perte de contexte. Un chunk isolé perd ce qui le précède. La phrase « Cette limite ne s'applique pas aux contrats antérieurs à 2023 » est inutilisable seule : à quelle limite, quels contrats ? Le pronom et le déictique renvoient à un contexte qui vit dans le chunk précédent. Vectorisé tel quel, ce passage embarque une ambiguïté que l'embedding ne sait pas lever, et il ne remontera jamais sur la bonne requête.
Frontières arbitraires au milieu d'une idée. Le découpage à taille fixe coupe à 512 tokens, point. Si l'argument clé d'un paragraphe s'étale sur la frontière, il se retrouve réparti entre deux chunks dont aucun ne contient l'idée complète. La requête qui cherche cette idée matche faiblement les deux moitiés, et le passage pertinent est noyé sous des résultats partiels.
Dilution sémantique des gros chunks. Tentés d'éviter la fragmentation, certains montent à 1500-2000 tokens. Problème : un gros chunk couvre souvent plusieurs sous-sujets. Son vecteur devient une moyenne floue qui ne ressemble fortement à aucune requête précise. On récupère le bon document mais on noie le LLM sous du bruit, ce qui dégrade la fidélité de la réponse et gonfle la facture de tokens en génération.
Sur-fragmentation des petits chunks. À l'inverse, des chunks de 50-100 tokens produisent des vecteurs très spécifiques mais privés de contexte. On multiplie les entrées dans l'index, on augmente le risque de matcher un fragment hors-sujet, et on force le LLM à recoller mentalement des morceaux épars.
À retenir : le chunking est un compromis entre spécificité (chunks petits et focalisés) et complétude du contexte (chunks larges et autosuffisants). Tout l'art consiste à trouver le point d'équilibre pour votre corpus et votre modèle d'embedding.
Les stratégies, de la plus naïve à la plus avancée
Découpage fixe (tokens/caractères + overlap)
C'est le point de départ de tout le monde : on coupe tous les N tokens (ou caractères) avec un recouvrement (overlap) de quelques dizaines de tokens entre chunks consécutifs. L'overlap sert de filet de sécurité : une idée coupée en bordure a une chance d'être complète dans le chunk voisin.
Avantages : simple, rapide, déterministe, aucun appel externe. Inconvénients : il ignore totalement la structure du document. Il coupe au milieu d'une phrase, d'un tableau, d'un bloc de code. C'est un point de départ acceptable pour prototyper, rarement un point d'arrivée pour de la production.
Découpage récursif respectant la structure
Le découpage récursif essaie de couper sur des séparateurs hiérarchisés : d'abord les sauts de section (titres), puis les paragraphes (\n\n), puis les phrases, et seulement en dernier recours au milieu d'une phrase. On vise une taille cible, mais on préfère toujours une frontière naturelle proche plutôt qu'une coupure brutale.
C'est aujourd'hui le défaut raisonnable pour du texte prosé. Il respecte les paragraphes, garde les phrases entières, et reste peu coûteux. La plupart des bibliothèques RAG l'implémentent sous le nom de recursive character/text splitter. Pour 80 % des corpus textuels, bien réglé, il suffit.
Découpage sémantique
Plutôt que de couper sur des caractères, on coupe quand le sens change. On embarque les phrases (ou groupes de phrases), on mesure la similarité entre voisines, et on pose une frontière là où la similarité chute en dessous d'un seuil — signe d'un changement de sujet. Les chunks suivent ainsi les unités thématiques réelles du texte.
C'est élégant et souvent meilleur sur des documents hétérogènes (rapports mélangeant plusieurs thèmes). Le coût : il faut embarquer toutes les phrases en amont, le résultat dépend du seuil (à calibrer), et la taille des chunks devient variable, ce qui peut compliquer le budget de contexte. À réserver aux corpus où la qualité justifie l'effort.
Structure-aware (Markdown/HTML/tableaux/code)
Les documents techniques ne sont pas de la prose. Un fichier Markdown a des titres et des listes ; un HTML a un DOM ; un tableau a des lignes et des colonnes ; du code a des fonctions et des classes. Un découpage structure-aware parse le format et coupe sur ses frontières naturelles : un chunk par section sous son titre, un tableau gardé entier (ou découpé ligne par ligne avec l'en-tête répété), une fonction par chunk pour du code.
À retenir : il n'existe pas de stratégie universelle. Le bon découpage dépend du type de document. Un pipeline de production sérieux route chaque format vers le splitter adapté plutôt que d'appliquer un découpage fixe à tout.
Late chunking : embedder d'abord, découper ensuite
Le late chunking, popularisé par Jina, renverse l'ordre habituel. Au lieu d'embarquer chaque chunk indépendamment, on passe le document entier (ou un long passage) dans un modèle d'embedding à long contexte, puis on récupère les embeddings au niveau des tokens, et c'est seulement après qu'on agrège ces embeddings par chunk (typiquement par pooling sur les tokens de chaque chunk).
L'intérêt est direct : chaque embedding de chunk a « vu » tout le document grâce au mécanisme d'attention. Le fameux « Cette limite ne s'applique pas… » bénéficie du contexte amont parce que les représentations de tokens ont été calculées en présence des phrases précédentes. On préserve le contexte long sans gonfler la taille des chunks ni multiplier les appels.
Conditions : il faut un modèle d'embedding à long contexte (Jina embeddings v3, ou d'autres encodeurs supportant de grandes fenêtres). Limite : la fenêtre de contexte du modèle reste finie, donc les très longs documents doivent être traités par blocs. Mais pour des documents de taille moyenne, le late chunking offre un gain de contexte quasi gratuit, sans LLM générateur dans la boucle.
Contextual retrieval : préfixer chaque chunk d'un contexte généré
L'approche de contextual retrieval d'Anthropic attaque le même problème — la perte de contexte — par un autre angle. Avant de vectoriser chaque chunk, on demande à un LLM de produire un court contexte explicatif situant ce chunk dans le document, puis on préfixe ce contexte au chunk avant l'embedding (et avant l'index BM25).
Concrètement, un chunk parlant de « la limite de 5 000 € » devient quelque chose comme : « Extrait du contrat-cadre 2024, section Plafonds de remboursement, concernant les frais de déplacement. La limite de 5 000 €… ». Le chunk porte désormais en lui les coordonnées qui lui manquaient, ce qui améliore nettement le rappel — Anthropic rapporte une réduction substantielle du taux d'échec de récupération, encore renforcée en combinant dense, BM25 et reranking.
À retenir : late chunking et contextual retrieval visent le même mal — le chunk amnésique. Le premier le résout par l'attention du modèle d'embedding (peu coûteux, pas de LLM), le second par génération de contexte (plus coûteux, plus universel et compatible BM25).
Le coût est le vrai sujet. Générer un contexte par chunk à l'indexation représente un appel LLM par chunk. À l'échelle de centaines de milliers de chunks, ce n'est pas neutre. Deux mitigations : utiliser un petit modèle bon marché pour la génération de contexte (Mistral Small 3, Gemma 3, ou un modèle ouvert hébergé en EU pour la souveraineté), et exploiter le prompt caching en gardant le document complet en cache pour traiter tous ses chunks d'affilée. C'est un coût d'indexation ponctuel, à amortir sur la durée de vie du corpus.
Tableau comparatif
| Stratégie |
Complexité |
Coût à l'indexation |
Gain de pertinence |
Quand l'utiliser |
| Fixe + overlap |
Très faible |
Quasi nul |
Faible |
Prototype, corpus homogène simple |
| Récursif structurel |
Faible |
Quasi nul |
Moyen |
Défaut raisonnable pour du texte prosé |
| Sémantique |
Moyenne |
Embeddings de phrases |
Moyen à élevé |
Documents hétérogènes, multi-sujets |
| Structure-aware |
Moyenne |
Faible (parsing) |
Élevé (sur formats structurés) |
Markdown, HTML, code, tableaux |
| Late chunking |
Moyenne |
Faible (1 passe encodeur) |
Élevé |
Modèle d'embedding long contexte dispo |
| Contextual retrieval |
Élevée |
Élevé (1 LLM/chunk) |
Très élevé |
Corpus à forte valeur, rappel critique |
Ces approches ne sont pas exclusives. En production, on combine volontiers un découpage récursif/structure-aware avec du contextual retrieval, puis recherche hybride et reranking par-dessus.
Pièges concrets
- Tableaux coupés. Un découpage fixe scinde un tableau en plein milieu : les lignes orphelines perdent leurs en-têtes et deviennent ininterprétables. Détectez les tableaux et gardez-les entiers, ou répétez la ligne d'en-tête dans chaque sous-chunk.
- Code mutilé. Couper une fonction au milieu produit des chunks syntaxiquement absurdes. Pour du code, découpez sur les frontières de fonctions/classes et conservez les imports ou la signature en contexte.
- Listes éclatées. Une liste à puces coupée perd la phrase d'introduction qui lui donne son sens. Traitez l'intro + la liste comme une unité.
- Overlap mal réglé. Trop faible, il ne protège rien ; trop élevé (par ex. 50 %), il duplique massivement le contenu, gonfle l'index, fausse les scores de similarité et augmente les coûts. Visez 10-20 %.
- Métadonnées perdues. C'est l'erreur la plus fréquente et la plus coûteuse. Si vous ne stockez pas la source, le titre de section, la page et la position du chunk, vous ne pourrez ni citer correctement, ni filtrer, ni déboguer un mauvais résultat. Les métadonnées ne sont pas optionnelles.
Recommandations pratiques
Points de départ raisonnables, à valider sur votre golden set avec RAGAS (context recall, context precision) :
- Taille : ~256-512 tokens. Calez-la sur votre modèle d'embedding. Beaucoup de modèles récents (
text-embedding-3-large, voyage-3, bge-m3) sont à l'aise autour de 512 ; descendez vers 256 si vos requêtes sont très précises.
- Overlap : 10-20 %, soit ~50-100 tokens pour des chunks de 512. Pas plus.
- Respectez la structure. Récursif au minimum, structure-aware pour les formats riches.
- Conservez source et section en métadonnées, systématiquement.
- Envisagez late chunking ou contextual retrieval quand le rappel devient critique, en arbitrant sur le coût d'indexation.
Pseudo-extrait de configuration d'un pipeline :
chunking:
strategy: recursive # recursive | semantic | structure_aware
target_tokens: 512
overlap_tokens: 80 # ~16 %
separators: ["\n## ", "\n\n", "\n", ". ", " "]
keep_tables_intact: true
code_split_on: ["function", "class"]
contextual_retrieval:
enabled: true
context_model: mistral-small-3 # petit modèle EU, prompt caching activé
metadata:
- source_uri
- section_title
- page
- chunk_index
Et le pseudo-pipeline d'indexation correspondant :
for doc in corpus:
blocks = structure_aware_split(doc, target=512, overlap=80)
for i, block in enumerate(blocks):
context = small_llm.summarize_context(doc, block) # contextual retrieval
text = f"{context}\n\n{block.text}"
vector = embed(text) # ou late chunking
index.upsert(
id=f"{doc.id}:{i}",
vector=vector,
text=block.text,
metadata={
"source_uri": doc.uri,
"section_title": block.section,
"page": block.page,
"chunk_index": i,
},
)
Le chunking n'est pas un détail d'implémentation qu'on règle une fois pour toutes. C'est un levier de qualité de premier ordre, à mesurer et à itérer comme n'importe quelle autre brique du pipeline. Soignez-le, et la moitié de vos problèmes de pertinence disparaîtront avant même d'avoir touché au reranker.
Pour aller plus loin
- Architecture RAG de production : du chunking au reranking, le guide complet
- Recherche hybride et reranking : pourquoi la similarité vectorielle seule échoue