Apogée Consult
Retour au blog
Jules Ginhac
Jules GinhacCo-Founder & ingénieur IA

Je suis Jules Ginhac, Co-Founder & ingénieur IA chez Apogée Consult à Lyon. Je conçois et déploie des architectures IA génératives (RAG, agents, LLMOps) pour des PME, startups et organisations publiques.

Chunking RAG : les heuristiques qui tiennent en production, celles qui lâchent

  • rag

Retour sur ce qui fonctionne vraiment quand on chunk une doc métier hétérogène : tailles, overlap, splitters, et les deux erreurs classiques à éviter.

Chunking RAG : les heuristiques qui tiennent en production, celles qui lâchent

La taille de chunk par défaut de LangChain est 1000 tokens avec un overlap de 200. Cette valeur est partout dans les tutoriels. Sur un corpus de documentation juridique, technique ou contractuelle, elle donne des résultats médiocres à chaque fois qu'on l'a appliquée sans ajustement.

Ce texte documente ce qu'on a observé sur des corpus métier réels, ce qui a tenu et ce qui s'est effondré dès qu'on a mis plus de 50 000 tokens en index.

Le problème fondamental du chunking fixe

Un chunk de taille fixe ne respecte pas la structure sémantique du document. Couper à 1000 tokens peut tomber au milieu d'une clause contractuelle, d'un tableau de données ou d'une procédure en étapes. Ce qui entre dans l'embedding est alors partiellement hors contexte.

Le résultat : deux chunks consécutifs partagent du sens que ni l'un ni l'autre ne porte complètement. Le retrieval remonte le mauvais des deux, ou les deux, et le LLM hallucine la jonction.

On a mesuré ce phénomène sur un corpus de 800 fiches techniques industrielles (PDF convertis en texte). Avec un chunking fixe à 1000/200, environ 18 % des requêtes de test remontaient un chunk coupé en plein milieu d'une valeur de seuil ou d'une unité de mesure. Les réponses générées sur ces cas étaient systématiquement erronées.

Ce qui tient : le recursive character splitter avec hiérarchie de séparateurs

Le RecursiveCharacterTextSplitter de LangChain, ou son équivalent dans llama-index, découpe en priorité sur les séparateurs forts (double saut de ligne, saut de ligne simple, point, espace) avant de passer à une coupure dure.

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    # Taille calibrée sur le modèle d'embedding cible
    # text-embedding-3-small : fenêtre 8191 tokens
    # On vise 400-600 tokens par chunk pour laisser de la marge
    chunk_size=512,
    chunk_overlap=64,
    separators=["\n\n", "\n", ". ", " ", ""],
    length_function=len,  # caractères, pas tokens, attention
)

Deux points importants sur ce code :

  1. length_function=len compte des caractères, pas des tokens. Pour un texte anglais, 1 token ~ 4 caractères. Pour du français technique, le ratio monte. Calibrer chunk_size en tokens réels nécessite d'utiliser le tokenizer du modèle d'embedding cible ou d'utiliser tiktoken comme fonction de mesure.
  1. L'overlap à 64 tokens (~ 256 caractères) est délibérément bas. Un overlap élevé multiplie le nombre de chunks, donc le coût d'index et le bruit au retrieval. On préfère un overlap minimal et compenser avec du reranking.

Ce qui lâche : le semantic chunking sur corpus hétérogène

Le semantic chunking coupe quand la similarité cosinus entre phrases consécutives chute sous un seuil. En théorie, il respecte mieux le sens. En pratique, sur un corpus hétérogène (mélange de prose, de tableaux HTML, de listes à puces et de code), il produit des chunks extrêmement variables en taille.

On a testé la méthode de Greg Kamradt (percentile 95 de la dérivée de similarité cosinus) sur un corpus de documentation d'API et de manuels utilisateur mélangés. Résultat :

  • Chunks les plus petits : 40 tokens (une ligne de titre)
  • Chunks les plus grands : 2100 tokens (un bloc de tableau HTML interprété comme une seule unité sémantique)

Le modèle d'embedding saturait sur les grands chunks, et les petits ne portaient pas assez de contexte pour être utiles. Le recall sur les requêtes de test était inférieur de 11 points par rapport au recursive splitter calibré.

Le semantic chunking est pertinent sur un corpus homogène, articles Wikipedia, textes longs de même nature. Sur de la doc métier mixte, le coût de calibration est disproportionné.

La variable qu'on sous-estime : la granularité dépend du modèle d'embedding

Chaque modèle d'embedding a une fenêtre contextuelle et une façon de compresser l'information. text-embedding-ada-002 (fenêtre 8191 tokens) peut théoriquement ingérer de grands chunks, mais ses performances de retrieval se dégradent sur des chunks au-delà de 512 tokens selon les benchmarks MTEB (source).

bge-m3 ou e5-mistral-7b-instruct tolèrent mieux les grands chunks car ils sont entraînés à encoder du contexte long. Si vous changez de modèle d'embedding sans rechunker, vos heuristiques deviennent invalides.

L'overlap : à quoi il sert vraiment

L'overlap ne sert pas à capturer "plus d'information". Il sert à s'assurer qu'une phrase clé à la jonction de deux chunks n'est pas perdue. Un overlap de 10 % de la taille du chunk est généralement suffisant.

Au-delà de 20 %, on introduit de la redondance dans l'index. Le retrieval remonte plusieurs fois la même information dans des chunks légèrement différents. Le LLM doit alors arbitrer entre des versions très proches, ce qui augmente le risque de confusion.

Ce qu'on fait sur nos projets

Sur chaque nouveau corpus, on applique ce processus avant de fixer les paramètres :

  1. Échantillonner 200 documents représentatifs du corpus complet
  2. Mesurer la distribution des longueurs de sections (H2, H3, paragraphes)
  3. Fixer chunk_size à P75 de cette distribution, plafonné à 512 tokens
  4. Fixer overlap à 10 % de chunk_size
  5. Vérifier manuellement 20 chunks aléatoires pour détecter les coupures aberrantes
  6. Mesurer le recall@5 sur 50 requêtes de test avant de passer à l'étape suivante

Ce calibrage prend 2 à 4 heures. Il évite de déboguer des problèmes de retrieval 3 semaines plus tard en production.

La question qui reste ouverte : quand on a un corpus qui évolue continuellement (base de connaissances mise à jour chaque semaine), comment arbitrer entre rechunking complet et mise à jour incrémentale sans dégrader la cohérence de l'index existant ?

Disponible pour de nouveaux projets

Un projet à concrétiser ?
Parlons-en, sans engagement.

Un échange de 30 minutes pour cadrer votre besoin, qualifier la faisabilité et vous proposer une trajectoire claire.

1// kick-off : réponse sous 24h
2const project = await apogee.scope({
3 type: 'web | mobile | IA',
4 timeline: '6 à 16 semaines',
5 approach: 'sur-mesure'
6})
7// → cadrage offert
Chunking RAG : heuristiques qui tiennent en prod | Apogée Consult