Stratégies de découpage dans la comparaison de RAG : alternatives, compromis et exemples
Comparaison des stratégies de découpage dans le RAG
Chunking est le hyperparamètre le plus sous-estimé dans le génération augmentée par recherche (RAG) : il détermine silencieusement ce que votre LLM “voit”, combien coûte l’ingestion, et combien de la fenêtre de contexte de l’LLM vous brûlez par réponse.
Cet article traite du chunking comme un problème d’optimisation d’ingénierie : définir les objectifs, choisir une stratégie, mesurer, puis itérer.
Si vous êtes nouveau dans l’architecture RAG, commencez par le principal Tutoriel sur la Génération Augmentée par Recherche (RAG) : Architecture, Implémentation et Guide de Production.

TL;DR (Résumé exécutif)
Les systèmes RAG récupèrent des fragments, pas des documents. Le chunking définit donc l’unité de récupération, l’unité de coût d’embedding et l’unité de preuve que vous pouvez montrer ou citer. Dans la formulation originale de RAG, la récupération fournit des passages à la génération ; les limites des passages sont effectivement vos limites de chunk.
Une bonne stratégie de chunking vise à atteindre une frontière de Pareto entre : la qualité de la récupération (rappel/précision de la preuve), la cohérence (les chunks doivent être interprétables), et le coût (embedding, stockage et latence de requête). Il n’existe pas de taille ou de méthode de chunk optimal globalement, et les systèmes de production utilisent souvent des stratégies mixtes (par exemple, un chunking structure-aware pour les PDFs + un chunking sémantique pour le texte + un chunking AST pour le code).
Pour la plupart des cas de “QA de documentation” et des bases de connaissances internes, un bon point de départ par défaut est un splitter récursif respectant la structure avec un chevauchement modeste (pour réduire la perte aux limites), soutenu par une
base de vecteurs
avec un filtrage par métadonnées et un ré-rangement optionnel. Le RecursiveCharacterTextSplitter de LangChain est une implémentation courante de cette idée de séparateur hiérarchique ; le chevauchement existe spécifiquement pour atténuer la perte d’information lorsque le contexte pertinent est coupé aux limites.
Lorsque les documents ont une structure forte (PDFs avec des titres, des tableaux, des listes, des légendes), le chunking basé sur les éléments / structure-aware peut surpasser le découpage par le nombre de tokens tout en produisant moins de chunks. Une étude de 2024 sur les documents de la SEC a trouvé que le chunking basé sur le type d’élément a amélioré les résultats RAG et a également réduit le nombre de chunks (et donc de vecteurs) d’environ la moitié par rapport aux méthodes non structurées—réduisant le coût d’indexation et potentiellement améliorant la latence de requête.
Si vous pouvez vous permettre plus de calculs initiaux, le chunking sémantique (séparation aux changements de sujet en utilisant la similarité des embeddings) peut améliorer significativement la fidélité de la récupération pour le texte narratif et les pages à sujets mixtes. Les anciens algorithmes de segmentation de sujet comme TextTiling montrent le principe général : les changements importants du vocabulaire/sémantique sont de bons candidats pour les limites.
Pour des matériels très longs, internement interréférencés (politiques, RFCs, normes, grands manuels), le chunking hiérarchique + récupération/merging hiérarchique (nœuds parent/enfant) peut récupérer plus de contexte contigu sur demande. Le parser hiérarchique de LlamaIndex produit des hiérarchies de chunks de grossière à fine, et le AutoMergingRetriever peut fusionner les nœuds feuilles en nœuds parents au moment de la récupération lorsqu’assez de nœuds enfants liés sont récupérés.
Objectifs et compromis du chunking
Le chunking n’est pas simplement “diviser le texte pour qu’il s’insère dans un modèle d’embedding”. Il contrôle plusieurs comportements à la fois downstream et opérationnels.
Granularité de la récupération vs bruit de la récupération. Les chunks plus petits augmentent la chance que la phrase exacte contenant une réponse soit récupérable (potentiel de rappel plus élevé à top-k fixe). Mais ils produisent également plus de vecteurs, augmentant la taille de l’index et parfois surfacant des “correspondances proches” qui sont sémantiquement similaires mais ne sont pas vraiment des preuves (précision plus faible). Les récupérateurs denses comme DPR ont été construits autour de la récupération efficace de passages pour les questions/réponses, soulignant que les limites des passages importent pour la performance globale des questions/réponses.
Coherence du contexte vs perte aux limites. Les chunks cohérents aident le LLM à raisonner correctement et réduisent les hallucinations en fournissant un contexte local complet (définitions, contraintes, prérequis). Le chevauchement réduit la perte aux limites mais crée du texte dupliqué, ce qui peut entraîner des résultats de récupération redondants et une longueur de prompt gonflée si vous ne dédoublonnez/pas fusionnez.
Coût d’embedding et d’indexation. Le coût d’embedding est généralement proportionnel aux tokens embeddés, et le temps d’ingestion augmente avec le nombre de chunks (plus le surcoût d’écriture de la base de vecteurs). Pour les embeddings d’OpenAI, les requêtes ont une limite maximale de token par entrée (8192 tokens pour tous les modèles d’embedding) et une limite maximale totale de tokens additionnés par requête (300 000 tokens). Pour de grands corpus, l’API Batch peut réduire les coûts d’environ 50 % avec un délai asynchrone de 24 heures—utile pour les mises à jour arrière et les re-indexations périodiques.
Taille de l’index de vecteurs, RAM et latence. Plus de chunks signifie plus de vecteurs et potentiellement plus de mémoire et des requêtes plus lentes (selon le type d’index). FAISS présente explicitement la conception de l’index comme un ensemble de compromis entre temps de recherche, qualité de recherche et mémoire par vecteur indexé ; il propose également des implémentations GPU pour une recherche rapide exacte et approchée.
Longueur du prompt LLM downstream / utilisation de la fenêtre de contexte. La sortie du récupérateur devient le budget de prompt. Une stratégie de chunking qui récupère juste assez de contexte peut améliorer la qualité des réponses et réduire les coûts. En revanche, le chevauchement et les chunks trop grands augmentent la longueur du prompt. En pratique, vous ajustez souvent : (taille de chunk, chevauchement, top-k, ré-rangement/fusion) ensemble.
Coût de mise à jour/ingestion et déduplication. Le chunking affecte le coût de la mise à jour des données. Les chunks plus petits rendent les mises à jour partielles moins coûteuses (vous pouvez ré-embedre uniquement la section modifiée) mais rendent aussi la déduplication plus difficile si des chunks chevauchés ou proches dupliqués prolifèrent.
Où le chunking se situe dans le workflow RAG

Stratégies de chunking et alternatives
Voici les principales familles de chunking que vous rencontrerez dans le RAG moderne. En pratique, vous combinerez souvent deux : le chunking structure-first (respecter les limites du document) plus le enforcement du budget de token (assurer que les chunks s’adaptent à vos budgets d’embedding et de prompt).
Chunking à taille fixe
Qu’est-ce que c’est. Diviser le texte en blocs de taille égale par caractères ou tokens.
Pourquoi il existe. C’est simple, rapide, prévisible et facile à paralléliser. C’est aussi la stratégie la plus simple pour l’ingestion en flux où vous n’avez pas le contexte complet du document.
Où il échoue. Il ignore les limites (phrases, sections, blocs de code) et peut donc briser les définitions ou diviser les “paires question/réponse” à travers les chunks, augmentant ainsi l’erreur de récupération.
Profil opérationnel. Complexité d’ingestion la plus basse ; nombre de chunks prévisible ; plus facile de mettre en cache. Mais vous avez généralement besoin de chevauchement (ci-dessous) pour éviter la perte aux limites.
Chunking avec chevauchement
Qu’est-ce que c’est. Toute stratégie où les chunks consécutifs partagent une zone de chevauchement fixe (par exemple, 10–20 % de la taille du chunk). Le chevauchement est standard dans de nombreux cadres car il réduit la perte d’information lorsque le contexte est divisé.
Pourquoi c’est important. Le chevauchement est effectivement une “frontière douce” — il permet à la récupération de capturer un fait qui traverse une frontière.
Coûts et pièges. Plus de tokens embeddés ; plus de texte dupliqué dans l’index ; risque accru de récupérer plusieurs chunks presque identiques sauf si vous dédoublonnez lors de la récupération (par exemple, fusionner par décalage source ou utiliser MMR).
Chunking basé sur les phrases et les paragraphes
Qu’est-ce que c’est. Diviser le texte aux limites de phrases ou de paragraphes, puis regrouper les phrases/paragraphes en chunks jusqu’à un budget de token.
Pourquoi les ingénieurs l’aiment. Il améliore la cohérence pour le langage naturel et est robuste pour les documents avec des ponctuations et espacements conventionnels.
Outils. sent_tokenize() de NLTK utilise par défaut la détection des limites de phrases par Punkt, et spaCy propose des outils basés sur des règles pour les limites de phrases comme Sentencizer (utile si vous souhaitez des divisions de phrases sans un modèle de dépendance complet).
Modes d’échec. La ponctuation non standard (logs, transcriptions de chat), les tableaux, le code et les listes à puces peuvent briser les hypothèses de segmentation de phrases.
Chunking par fenêtre glissante
Qu’est-ce que c’est. Créer des chunks à l’aide d’une taille de fenêtre fixe et d’un pas (décalage). C’est la version “systématique du chevauchement” du chunking.
Quand c’est bon. Les textes de séries temporelles, les transcriptions, les journaux de chat, les minutes de réunion — tout ce qui contient des faits pertinents à travers des zones locales et où vous souhaitez un rappel robuste.
Quand c’est mauvais. Il amplifie la redondance et peut être coûteux à grande échelle. Il tend aussi à récupérer des fenêtres redondantes sauf si dédoublonné.
Chunking récursif / hiérarchie de séparateurs
Qu’est-ce que c’est. Commencez avec de grands “séparateurs naturels” (par exemple, \n\n pour les paragraphes) et divisez récursivement en unités plus petites (phrases, espaces) uniquement si nécessaire pour rester sous un budget de taille. LangChain documente explicitement ce comportement : un splitter récursif tente de garder les unités plus grandes intactes et ne recourt qu’aux séparateurs plus petits si une unité dépasse toujours la taille du chunk.
Pourquoi c’est un bon point de départ. Il respecte la structure sans nécessiter un parsing complexe des documents. C’est un point de compromis pratique pour le Markdown, le HTML en tant que texte et la documentation.
Boutons de réglage clés. chunk_size, chunk_overlap, et la length_function (caractères vs tokens), plus des séparateurs personnalisés pour les bases de code multilingues.
Chunking sémantique (sensible aux embeddings)
Qu’est-ce que c’est. Détecter les changements de sujet à l’aide de représentations sémantiques (embeddings) et diviser là où la similarité diminue. Cela reflète les idées classiques de segmentation comme TextTiling, qui utilise les changements de cohésion lexicale pour trouver les limites de sous-sujets.
Pourquoi il peut surpasser le chunking basé sur la taille. Vous arrêtez de diviser à des compteurs de tokens arbitraires et alignez les chunks sur les limites de sujets — souvent améliorant la précision de la récupération pour les documents à sujets multiples (blogs, documents de conception, tickets, rapports d’incident).
Coûts. Vous pouvez avoir besoin d’embeddings supplémentaires pendant le chunking (embeddings de phrases ou de paragraphes) avant les embeddings finaux. Cela peut doubler ou tripler les appels d’embeddings sauf si vous réutilisez les embeddings intermédiaires.
Astuce pratique. “Package sémantique” : calculez les embeddings des phrases une fois, groupez les phrases en segments cohérents par sujet, puis embouteillez chaque segment final.
Chunking hiérarchique (parent/enfant)
Qu’est-ce que c’est. Construire une représentation à plusieurs granularités : des chunks parents plus grossiers (par exemple, de la taille de section) avec des chunks enfants plus fins (par exemple, de la taille de paragraphe). Le parsing hiérarchique de LlamaIndex produit par défaut des hiérarchies “grossière-fine” (par exemple, échelles de 2048 → 512 → 128 tokens), et le AutoMergingRetriever peut fusionner les nœuds enfants en parents au moment de la récupération lorsque suffisamment de nœuds enfants liés sont récupérés.
Pourquoi ça aide. Il évite de choisir entre “des chunks petits pour le rappel” et “des chunks grands pour la cohérence” en stockant les deux, et en les sélectionnant au moment de la requête.
Coûts. Une logique d’ingestion et de récupération plus complexe, plus potentiellement du stockage (car vous stockez plusieurs granularités).
Chunking adaptatif / basé sur l’LLM
Qu’est-ce que c’est. Utiliser un LLM pour décider des limites de chunk (et générer des résumés ou des en-têtes contextuels en option). Weaviate décrit explicitement le chunking basé sur l’LLM comme ayant l’LLM créer des chunks sémantiquement cohérents, au lieu de dépendre de règles fixes ou de la similarité des embeddings.
Quand c’est rentable. Des corpus à haute valeur où la précision domine le coût (juridique, conformité, runbooks de support), et où les documents sont désordonnés, hétérogènes et mal segmentés.
Risques. Coût, latence et non-déterminisme. Vous voudrez du cache, du décodage déterministe et des tests de régression (voir la section d’évaluation).
Chunking basé sur la structure et les éléments (les documents ne sont pas du texte plat)
Qu’est-ce que c’est. Parsez le document en éléments (titres, paragraphes, listes, tableaux, légendes) à l’aide d’une couche de compréhension des documents, puis chunkez à l’aide de ces éléments. Les fonctions de chunking d’Unstructured utilisent explicitement les métadonnées et les éléments du document (produits par le partitionnement) pour produire des chunks pour le RAG. Le HierarchicalChunker de Docling crée des chunks par élément détecté du document et attache des métadonnées structurelles comme les en-têtes/légendes.
Preuves de la recherche récente. Une étude de 2024 sur les documents de la SEC argue que le chunking basé uniquement sur les paragraphes néglige la structure du document et propose le chunking par éléments structurels ; elle rapporte des résultats RAG améliorés et moins de chunks/vecteurs que les approches non structurées.
Pourquoi c’est important pour le multimodal. Les tableaux, figures et légendes contiennent souvent la vérité sur le terrain. “Platifier” les éléments en texte simple peut détruire les signaux que la récupération utiliserait autrement.
Chunking orienté code (AST/structure)
Qu’est-ce que c’est. Chunkez le code par unités syntaxiques (fonctions, classes, modules), avec l’option d’inclure des docstrings et des commentaires.
Pourquoi c’est important. Les divisions de tokens fixes tendent à couper les fonctions en deux et à séparer les docstrings des implémentations — mauvais pour la recherche de code et les cas d’utilisation RAG “expliquez cette fonction”.
Options d’implémentation. Pour Python, le module ast intégré est souvent suffisant. Pour les dépôts multilingues, les chunkers basés sur tree-sitter sont courants.
Dimensions d’évaluation et comment comparer les stratégies de chunking
Le chunking doit être benchmarké comme composant du système.
Métriques de qualité de la récupération
Utilisez des métriques IR standards pour la couche de récupération :
- Recall@k / Precision@k : Le top-k contient-il la preuve d’or ?
- MRR / nDCG : La preuve d’or est-elle classée haut ?
BEIR est un benchmark hétérogène largement utilisé pour l’évaluation IR à travers les tâches et les domaines, et met en évidence les compromis entre les approches sparses, denses, de late-interaction et de ré-rangement.
Le chunking affecte ces métriques car il définit ce qui compte comme “un élément récupéré pertinent”.
Métriques de qualité de réponse RAG en bout de ligne
Si vous construisez des QA ou des assistants, les métriques de récupération sont nécessaires mais insuffisantes. Vous avez également besoin de :
- Rappel / précision du contexte : si les contextes récupérés contiennent des preuves pertinentes et évitent le bruit.
- Fidélité : si la réponse générée est soutenue par le contexte récupéré.
RAGAS fournit des définitions concrètes et des implémentations pour la “fidélité” et d’autres métriques orientées RAG.
Dimensions de coût et de performance du système
Le chunking change ces leviers :
Latence (p50/p95). La latence de la requête augmente généralement avec plus de vecteurs et plus de post-traitement. Votre index vectoriel compte aussi : FAISS échange le temps de recherche, la qualité, la mémoire et le temps d’entraînement/ajout.[^faiss]
Coût et débit d’embedding. Les embeddings d’OpenAI sont facturés par token ; l’API d’embedding a des limites explicites par entrée et par requête.[^openai_embed_create] Pour l’ingestion hors ligne, l’API Batch réduit les coûts et propose un quota plus élevé en échange d’un délai non en temps réel.[^openai_batch]
Taille de l’index et mémoire. Environ, le stockage de N vecteurs float32 de dimension d coûte ~4 * N * d octets juste pour les vecteurs bruts (plus les métadonnées + surcoût de l’index). Le chunking affecte N. La dimensionnalité des embeddings affecte d, et l’API d’embedding d’OpenAI permet de contrôler la dimensionnalité de sortie via le paramètre dimensions.[^openai_embed_create]
Budget de prompt LLM. Les chunks plus grands et le chevauchement gonflent les tokens de prompt. Cela peut augmenter la latence et le coût, et augmenter les modes de défaillance “perdu au milieu” où les modèles portent moins d’attention à certains contextes. En pratique, vous faites souvent :
- récupérer de petits chunks,
- fusionner/dédoublonner,
- résumer optionnellement,
- envoyer un ensemble compact de preuves à l’LLM.
Coût de mise à jour/ingestion. Les chunks plus petits permettent des re-embeddings partiels mais augmentent le suivi. Pour l’ingestion en flux, préférez le chunking déterministe, incrémental (fixe ou fenêtre glissante) et attachez des ID stables (document_id, plages d’offsets, hachage).
Conception expérimentale : un cycle de benchmark pragmatique
Un benchmark de chunking reproductible a généralement :
- Une capture d’ensemble fixe + un ensemble fixe de requêtes avec des preuves d’or (ou au moins des spans d’answer attendus).
- Un modèle d’embedding fixe et une configuration fixe d’index vectoriel.
- Une évaluation “seulement récupération” (recall@k, nDCG) plus une évaluation “RAG” (fidélité, pertinence de l’answer).
- Des mesures de coût : nombre de chunks, tokens embeddés, $/mois de stockage, latence p95 de requête, tokens de prompt.
L’article Unstructured sur les documents SEC est un bon exemple d’évaluation des stratégies de chunking avec à la fois des métriques orientées récupération et des mesures de précision QA.
Lignes directrices pratiques, matrice de décision et recommandations par défaut
Recommandations par défaut qui fonctionnent étonnamment bien
Si vous avez besoin d’une stratégie robuste “jour 1” pour la QA de documentation générale :
- Parsez légèrement : préservez les titres et les métadonnées de base (source, titre de section, URL/chemin, horodatage).
- Chunkez avec un splitter de séparateur récursif (paragraphe → phrase → mot), avec un chevauchement modeste.
- Embeddez avec un modèle d’embedding général fort.
- Indexez avec des métadonnées (ID de document, section, ACL) et dédoublonnez lors de la récupération.
- Ajoutez ré-rangement ou une fusion hiérarchique uniquement si votre évaluation montre un écart.
Cela correspond à la manière dont les cadres RAG courants décrivent le chevauchement des chunks et le découpage respectant la structure.
Quelle méthode de chunking utiliser - Matrice de décision

| Cas d’utilisation | Méthode de chunking recommandée par défaut | Paramètres clés à ajuster | Mode de défaillance courant | Chemin d’amélioration |
|---|---|---|---|---|
| QA courte sur des documents (FAQ, wiki interne) | Chunking récursif/séparateur + chevauchement | chunk_size, chevauchement, top-k |
Manque de preuves trans-sentences aux limites | Ajouter le chunking sémantique ou le ré-rangement |
| QA longue (politiques, normes, manuels) | Chunking hiérarchique + récupérateur de fusion | tailles parent/enfant, seuil de fusion | Récupère de petits fragments ; l’LLM manque de contexte complet | Fusion automatique/récupération hiérarchique |
| Résumé (par document / par section) | Chunking structure-aware (sections) | détection de section, max tokens | Les résumés manquent les liens trans-section | Résumé hiérarchique + graphe de section |
| Recherche de code & “expliquez cette fonction” | Chunking au niveau AST/fonction | inclure docstring/commentaires, max tokens | Fonction coupée ; perd la signature/utilisation | Hiérarchie repo-aware (module→classe→fonction) |
| PDF multimodal (tableaux/figures) | Chunking basé sur les éléments (titre/table/légende) | sérialisation de table, fusion de légendes | Contenu de table perdu ou corrompu | Utiliser Docling/Unstructured + sérialiseurs structurés |
| Ingestion en flux (logs, chats, tickets) | Fenêtre glissante ou tokens fixes | fenêtre, pas, déduplication | Récupération excessive de fenêtres redondantes | Ajouter la détection de limites sémantiques sur les lots |
chunking - Comparaison de performance qualitative
Traitez cela comme “direction attendue des changements” (validez avec vos propres données).
| Stratégie | Potentiel de précision de la récupération | Coherence du contexte récupéré | Complexité d’ingestion | Nombre de vecteurs / taille de l’index | Coût d’embedding | Impact sur la latence de requête | Meilleur pour |
|---|---|---|---|---|---|---|---|
| Fixe (sans chevauchement) | Moyen | Faible | Faible | Moyen | Faible | Moyen | prototypes rapides, texte homogène |
| Fixe + chevauchement | Moyen–Élevé | Moyen | Faible | Élevé | Moyen–Élevé | Moyen–Élevé | QA où la perte aux limites affecte |
| Packing par phrase/paragraphe | Élevé (prose) | Élevé | Moyen | Moyen | Moyen | Moyen | docs, articles, prose propre |
| Fenêtre glissante | Rappel élevé | Moyen | Moyen | Très élevé | Très élevé | Élevé | transcriptions, logs, chat |
| Récursif/séparateur | Élevé | Élevé | Moyen | Moyen | Moyen | Moyen | “défaut” pour la RAG de docs |
| Chunking sémantique | Élevé–Très élevé | Élevé | Élevé | Moyen | Élevé | Moyen | pages à sujets multiples, texte narratif |
| Hiérarchique (parent/enfant) | Très élevé | Très élevé | Élevé | Élevé | Élevé | Moyen | manuels longs / normes |
| LLM-based/adaptive | Très élevé | Très élevé | Très élevé | Moyen | Très élevé | Moyen–Élevé | corpus à haute valeur |
| Basé sur les éléments/structure | Élevé–Très élevé | Élevé | Élevé | Faible–Moyen | Moyen | Moyen | PDFs, rapports, tableaux, dispositions mixtes |
| Code-aware (AST) | Élevé (code) | Élevé | Moyen | Moyen | Moyen | Moyen | recherche de code, assistants de repo |
Notes DevOps et matériel (souvent négligées)
Les choix de chunking affectent la quantité d’infrastructure dont vous avez besoin :
- Plus petits chunks → plus de vecteurs → plus grands index et plus de RAM/disk. Pour FAISS auto-hébergé, cela peut forcer le sharding ou les index sur disque.
- Si vous embouteillez localement, le débit d’embedding devient un problème de planification GPU ; si vous embouteillez via une API, le volume de tokens devient un problème FinOps (l’API Batch est votre amie pour les mises à jour arrière).
- Certains moteurs (FAISS) offrent une recherche accélérée par GPU ; cela peut déplacer le coût de la mémoire CPU limitée vers la mémoire GPU et le débit PCIe.
- Le parsing structure-aware (disposition PDF, OCR, extraction de tableaux) est souvent CPU-bound et peut surpasser le coût d’embedding pour les documents scannés ; prévoyez un budget séparé.
Chunking - Implémentation Python
Tous les exemples sont conçus pour être lisibles et exécutables. Si vous avez besoin d’une clé API ou d’une base de données en cours d’exécution, cela est clair à partir du code.
Outils partagés : comptage de tokens et IDs de chunk stables
from __future__ import annotations
import hashlib
from dataclasses import dataclass
from typing import Any, Iterable, Optional
from transformers import AutoTokenizer # pip install transformers
@dataclass(frozen=True)
class Chunk:
text: str
meta: dict[str, Any]
def sha1_id(*parts: str) -> str:
h = hashlib.sha1()
for p in parts:
h.update(p.encode("utf-8"))
h.update(b"\x1e")
return h.hexdigest()
# Utilisez tout tokenizer qui correspond approximativement à la tokenisation de votre LLM/embedding.
TOKENIZER = AutoTokenizer.from_pretrained("bert-base-uncased")
def token_len(text: str) -> int:
return len(TOKENIZER.encode(text, add_special_tokens=False))
Chunking de taille fixe en tokens
def chunk_fixed_tokens(
text: str,
*,
chunk_size: int = 512,
) -> list[Chunk]:
token_ids = TOKENIZER.encode(text, add_special_tokens=False)
out: list[Chunk] = []
for i in range(0, len(token_ids), chunk_size):
window = token_ids[i : i + chunk_size]
chunk_text = TOKENIZER.decode(window)
out.append(
Chunk(
text=chunk_text,
meta={"strategy": "fixed_tokens", "start_token": i, "end_token": i + len(window)},
)
)
return out
Taille fixe avec chevauchement + fenêtre glissante
def chunk_sliding_window(
text: str,
*,
window_tokens: int = 512,
stride_tokens: int = 384, # pas plus petit = plus de chevauchement
) -> list[Chunk]:
assert 1 <= stride_tokens <= window_tokens, "le pas doit être compris dans (0, fenêtre]"
token_ids = TOKENIZER.encode(text, add_special_tokens=False)
out: list[Chunk] = []
start = 0
while start < len(token_ids):
end = min(start + window_tokens, len(token_ids))
window = token_ids[start:end]
out.append(
Chunk(
text=TOKENIZER.decode(window),
meta={"strategy": "sliding_window", "start_token": start, "end_token": end},
)
)
if end == len(token_ids):
break
start += stride_tokens
return out
Chunking basé sur les phrases (NLTK) avec emballage par budget de tokens
# pip install nltk
import nltk
from nltk.tokenize import sent_tokenize
nltk.download("punkt", quiet=True)
def chunk_by_sentences_nltk(
text: str,
*,
max_tokens: int = 512,
overlap_sentences: int = 1,
) -> list[Chunk]:
sents = [s.strip() for s in sent_tokenize(text) if s.strip()]
out: list[Chunk] = []
buf: list[str] = []
buf_tokens = 0
def flush():
nonlocal buf, buf_tokens
if not buf:
return
chunk_text = " ".join(buf).strip()
out.append(
Chunk(
text=chunk_text,
meta={"strategy": "sentences_nltk", "sent_count": len(buf)},
)
)
# Chevauchement en conservant les dernières N phrases
if overlap_sentences > 0:
buf = buf[-overlap_sentences:]
buf_tokens = token_len(" ".join(buf))
else:
buf, buf_tokens = [], 0
for s in sents:
s_tokens = token_len(s)
# Si une seule phrase dépasse le budget, revenir à un chunking de taille fixe sur cette plage
if s_tokens > max_tokens:
flush()
out.extend(chunk_fixed_tokens(s, chunk_size=max_tokens))
continue
if buf_tokens + s_tokens > max_tokens and buf:
flush()
buf.append(s)
buf_tokens += s_tokens
flush()
return out
Chunking basé sur les phrases (spaCy) lorsque vous souhaitez un SBD basé sur des règles ou un modèle
# pip install spacy
# python -m spacy download en_core_web_sm
import spacy
def chunk_by_sentences_spacy(
text: str,
*,
max_tokens: int = 512,
) -> list[Chunk]:
# Pour des divisions légères basées sur des règles (sans analyse syntaxique), utilisez le sentencizer.
nlp = spacy.blank("en")
nlp.add_pipe("sentencizer") # détection de limites de phrases basée sur des règles
doc = nlp(text)
sents = [sent.text.strip() for sent in doc.sents if sent.text.strip()]
return chunk_by_sentences_nltk(" ".join(sents), max_tokens=max_tokens, overlap_sentences=1)
Chunking récursif par séparateur (LangChain)
# pip install langchain-text-splitters
from langchain_text_splitters import RecursiveCharacterTextSplitter
def chunk_recursive_langchain(
text: str,
*,
chunk_size: int = 1200,
chunk_overlap: int = 150,
) -> list[Chunk]:
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=token_len, # budgétisation consciente des tokens
separators=["\n\n", "\n", ". ", " ", ""], # personnalisez selon votre contenu (ex. code)
)
pieces = splitter.split_text(text)
return [
Chunk(text=p, meta={"strategy": "recursive_langchain", "chunk_size": chunk_size, "overlap": chunk_overlap})
for p in pieces
]
Chunking sémantique avec similarité d’embedding (embeddings OpenAI)
Cette approche calcule des embeddings pour des unités candidates (phrases/paragraphes), puis trouve des “points de rupture” sémantiques.
# pip install openai numpy
import os
import numpy as np
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY", "YOUR_OPENAI_API_KEY"))
def embed_texts_openai(texts: list[str], *, model: str = "text-embedding-3-small") -> np.ndarray:
# NOTE : pour de grands lots, respectez les limites de tokens de requête et les limites de taille de lot.
resp = client.embeddings.create(model=model, input=texts)
embs = np.array([d.embedding for d in resp.data], dtype=np.float32)
return embs
def cosine_sim(a: np.ndarray, b: np.ndarray) -> float:
denom = (np.linalg.norm(a) * np.linalg.norm(b)) + 1e-8
return float(np.dot(a, b) / denom)
def chunk_semantic(
text: str,
*,
max_tokens: int = 800,
breakpoint_threshold: float = 0.70,
) -> list[Chunk]:
# 1) Commencez par des candidats de phrases
sents = [s.strip() for s in sent_tokenize(text) if s.strip()]
if len(sents) <= 1:
return [Chunk(text=text, meta={"strategy": "semantic", "note": "no_split"})]
# 2) Embouteiller chaque phrase
embs = embed_texts_openai(sents)
# 3) Calculer les chutes de similarité
sims = [cosine_sim(embs[i], embs[i + 1]) for i in range(len(sents) - 1)]
# 4) Créer des segments aux points de rupture
out: list[Chunk] = []
buf: list[str] = []
buf_tokens = 0
for i, s in enumerate(sents):
# Ajouter la phrase
s_tok = token_len(s)
if buf and buf_tokens + s_tok > max_tokens:
out.append(Chunk(text=" ".join(buf), meta={"strategy": "semantic", "reason": "max_tokens"}))
buf, buf_tokens = [], 0
buf.append(s)
buf_tokens += s_tok
# Décider du point de rupture après la phrase i (basé sur la similarité avec la suivante)
if i < len(sims) and sims[i] < breakpoint_threshold:
out.append(
Chunk(
text=" ".join(buf),
meta={"strategy": "semantic", "reason": "sim_drop", "sim_to_next": sims[i]},
)
)
buf, buf_tokens = [], 0
if buf:
out.append(Chunk(text=" ".join(buf), meta={"strategy": "semantic", "reason": "final"}))
return out
Chunking hiérarchique + fusion de récupération (LlamaIndex)
# pip install llama-index llama-index-llms-openai
from llama_index.core import Document, StorageContext, VectorStoreIndex
from llama_index.core.node_parser import HierarchicalNodeParser, get_leaf_nodes
from llama_index.core.retrievers import AutoMergingRetriever
from llama_index.core.storage.docstore import SimpleDocumentStore
def build_hierarchical_index(text: str):
docs = [Document(text=text)]
node_parser = HierarchicalNodeParser.from_defaults() # par défaut, tailles de finesse croissantes
nodes = node_parser.get_nodes_from_documents(docs)
docstore = SimpleDocumentStore()
docstore.add_documents(nodes)
storage_context = StorageContext.from_defaults(docstore=docstore)
leaf_nodes = get_leaf_nodes(nodes)
base_index = VectorStoreIndex(leaf_nodes, storage_context=storage_context)
base_retriever = base_index.as_retriever(similarity_top_k=6)
retriever = AutoMergingRetriever(base_retriever, storage_context, verbose=True)
return retriever
Chunking basé sur les éléments pour les PDF (Docling)
# pip install docling
# NOTE : la qualité de l'analyse des PDF dépend de votre environnement (polices, OCR, etc.).
from docling.document_converter import DocumentConverter
from docling.transforms.chunker.hierarchical_chunker import HierarchicalChunker
def chunk_pdf_docling(pdf_path: str) -> list[Chunk]:
converter = DocumentConverter()
doc = converter.convert(pdf_path).document # DoclingDocument
chunker = HierarchicalChunker()
doc_chunks = list(chunker.chunk(doc))
out: list[Chunk] = []
for c in doc_chunks:
# c.text contient le contenu du chunk sérialisé ; c.meta porte les informations de structure
out.append(Chunk(text=c.text, meta={"strategy": "docling_hierarchical", **dict(c.meta)}))
return out
Chunking conscient du code pour Python (AST)
import ast
def chunk_python_by_ast(code: str, *, filepath: str = "<memory>") -> list[Chunk]:
tree = ast.parse(code)
out: list[Chunk] = []
for node in tree.body:
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
start = getattr(node, "lineno", None)
end = getattr(node, "end_lineno", None)
if start is None or end is None:
continue
lines = code.splitlines()
snippet = "\n".join(lines[start - 1 : end])
kind = "class" if isinstance(node, ast.ClassDef) else "function"
name = getattr(node, "name", "<anon>")
out.append(
Chunk(
text=snippet,
meta={
"strategy": "python_ast",
"kind": kind,
"name": name,
"filepath": filepath,
"start_line": start,
"end_line": end,
},
)
)
return out
Exemples d’indexation
FAISS (local) — base minimale, rapide
# pip install faiss-cpu numpy
import numpy as np
import faiss
def build_faiss_index(vectors: np.ndarray) -> faiss.Index:
# vectors: forme (N, d), type float32
d = vectors.shape[1]
index = faiss.IndexFlatIP(d) # produit scalaire ; similitude cosinus si les vecteurs sont normalisés
faiss.normalize_L2(vectors)
index.add(vectors)
return index
def faiss_search(index: faiss.Index, query_vec: np.ndarray, k: int = 5):
q = query_vec.astype(np.float32).reshape(1, -1)
faiss.normalize_L2(q)
scores, ids = index.search(q, k)
return ids[0].tolist(), scores[0].tolist()
Chroma (local) — persistance simple pour le prototypage RAG
# pip install chromadb
import chromadb
def build_chroma_collection(chunks: list[Chunk], embeddings: np.ndarray, *, path: str = "./chroma_store"):
client = chromadb.PersistentClient(path=path)
col = client.get_or_create_collection(name="docs")
ids = [sha1_id(c.meta.get("strategy", "chunk"), str(i), c.text[:50]) for i, c in enumerate(chunks)]
col.upsert(
ids=ids,
documents=[c.text for c in chunks],
metadatas=[c.meta for c in chunks],
embeddings=embeddings.tolist(),
)
return col
Weaviate (auto-hébergé / cloud) — vecteurs fournis par l’utilisateur
# pip install weaviate-client
import weaviate
from weaviate.classes.config import Configure
def weaviate_upsert_self_provided(
chunks: list[Chunk],
embeddings: np.ndarray,
):
client = weaviate.connect_to_local() # ou connect_to_weaviate_cloud(...)
try:
collection = client.collections.create(
name="Chunk",
vector_config=Configure.Vectors.self_provided(), # vous fournissez les vecteurs
)
with collection.batch.dynamic() as batch:
for c, v in zip(chunks, embeddings):
batch.add_object(
properties={"text": c.text, **{f"m_{k}": str(vv) for k, vv in c.meta.items()}},
vector=v.tolist(),
)
# Requête par vecteur proche
q = embeddings[0]
res = collection.query.near_vector(near_vector=q.tolist(), limit=5)
for obj in res.objects:
print(obj.properties.get("text")[:200])
finally:
client.close()
Quelques Sources
- Patrick Lewis et al., “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks”, NeurIPS 2020; arXiv:2005.11401.
- Vladimir Karpukhin et al., “Dense Passage Retrieval for Open-Domain Question Answering”, EMNLP 2020; arXiv:2004.04906.
- Documentation de l’API OpenAI : Créer des embeddings (
/v1/embeddings) — limites de tokens (8192 par entrée ; 300k totaux par requête) et paramètredimensions. - Guide de l’API OpenAI Batch + Page de tarification OpenAI (les requêtes Batch économisent environ 50 % avec un délai de 24 heures).
- Documentation de LangChain : RecursiveCharacterTextSplitter et guide d’intégration des splitters (taille de chunk / chevauchement, hiérarchie des séparateurs récursifs).
- Documentation de LlamaIndex : HierarchicalNodeParser et AutoMergingRetriever (nœuds de finesse croissante ; fusion au moment de la récupération).
- Blog Weaviate : “Stratégies de chunking pour améliorer les performances des pipelines RAG LLM” (description et compromis du chunking basé sur un LLM).
- Documentation Docling : HierarchicalChunker crée des chunks à partir de la structure des éléments du document et attache les métadonnées des titres/legendes.
- Jimeno Yepes et al., “Financial Report Chunking for Effective Retrieval Augmented Generation” (arXiv:2402.05131v3, 2024).
- Marti A. Hearst, “TextTiling: Segmenting Text into Multi-Paragraph Subtopic Passages”, Computational Linguistics, 1997 (ACL Anthology: J97-1003).
- Documentation FAISS / dépôt GitHub : compromis entre temps de recherche, qualité, mémoire ; support GPU optionnel.
- Nandan Thakur et al., “BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models”, NeurIPS 2021; arXiv:2104.08663.
- Documentation RAGAS : “Metric de Fidélité” et métriques RAG associées.
- Documentation NLTK :
nltk.tokenize.sent_tokenize- tokenizer de phrases recommandé basé sur Punkt. - Documentation API spaCy :
Sentencizer- détection de limites de phrases basée sur des règles sans analyse syntaxique.
Autres liens utiles
- RAG avancé : LongRAG, Self-RAG et GraphRAG expliqués
- Comparaison des bases de vecteurs pour RAG
- Reranking avec des modèles d’embedding
- Reranking de documents textuels avec Ollama et Qwen3 Embedding model - en Go
- Reranking de documents textuels avec Ollama et Qwen3 Reranker model - en Go
- Auto-hébergement de Cognee (mémoire LLM) : Tests de performance des LLM
- Hébergement des LLM : Infrastructure locale, auto-hébergée et cloud comparée
- Performance des LLM : Benchmarks, goulets d’étranglement et optimisation
- Tutoriel sur le RAG (Retrieval-Augmented Generation) : Architecture, Implémentation et Guide de Production