Optimisation de la stratégie de Chunking pour les systèmes RAG en 2026 : Guide complet
Optimisation de la stratégie de Chunking pour les systèmes RAG en 2026 : Guide complet
Si vous utilisez encore des chunks fixes de 512 caractères pour RAG en 2026, votre qualité de récupération est probablement un désastre. La stratégie de chunking est le facteur n°1 de la qualité d'un système RAG—plus important que la sélection du modèle d'embedding, plus important que l'algorithme de récupération. Pourquoi ? Parce que peu importe la puissance de votre modèle vectoriel, si les chunks qui y sont injectés sont fragmentés et franchissent les limites sémantiques, les résultats de récupération ne seront jamais bons.
Ce guide propose une analyse approfondie de 6 stratégies de chunking RAG majeures, chacune avec du code Python exécutable, des données de benchmark et des conseils d'optimisation.
Aperçu des stratégies
| Stratégie | Idée centrale | Intégrité sémantique | Complexité | Idéal pour | Note |
|---|---|---|---|---|---|
| Taille fixe | Diviser par nombre de caractères/tokens | ⭐ | ⭐ | Logs, texte structuré | ⭐⭐ |
| Basée sur les phrases | Diviser aux limites des phrases | ⭐⭐⭐ | ⭐⭐ | Texte général | ⭐⭐⭐ |
| Sémantique | Diviser par similarité sémantique | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Bases de connaissances de haute qualité | ⭐⭐⭐⭐⭐ |
| Récursive | Récursion de séparateurs multiniveaux | ⭐⭐⭐⭐ | ⭐⭐⭐ | Documents Markdown/code | ⭐⭐⭐⭐ |
| Basée sur les documents | Diviser par la structure du document | ⭐⭐⭐⭐ | ⭐⭐⭐ | Documents structurés | ⭐⭐⭐⭐ |
| Hybride | Combinaison multi-stratégie | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Environnements de production | ⭐⭐⭐⭐⭐ |
1. Chunking à taille fixe
La stratégie la plus simple : diviser selon un nombre fixe de caractères ou de tokens, avec une fenêtre de chevauchement optionnelle.
Avantages : Implémentation simple, taille de chunk contrôlable, s'adapte aux limites d'entrée du modèle d'embedding.
Inconvénients : Ignore complètement les limites sémantiques—une phrase complète peut être coupée en deux.
from typing import List
def fixed_size_chunk(
text: str,
chunk_size: int = 512,
chunk_overlap: int = 50,
separator: str = ""
) -> List[str]:
"""Fixed-size chunking strategy
Args:
text: Text to chunk
chunk_size: Characters per chunk
chunk_overlap: Overlap characters between adjacent chunks
separator: Separator string
Returns:
List of text chunks
"""
if not text:
return []
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
if chunk.strip():
chunks.append(chunk)
start += chunk_size - chunk_overlap
return chunks
# Usage example
sample_text = "RAG systems are among the most popular AI application architectures. Chunking strategy directly impacts retrieval quality."
chunks = fixed_size_chunk(sample_text, chunk_size=40, chunk_overlap=10)
for i, chunk in enumerate(chunks):
print(f"Chunk {i+1}: {chunk}")
2. Chunking basé sur les phrases
Diviser aux limites naturelles des phrases du langage, en s'assurant que chaque chunk contient des phrases complètes.
Avantages : Intégrité sémantique nettement meilleure—pas de « demi-phrases ».
Inconvénients : Les phrases longues peuvent dépasser les limites du chunk ; les combinaisons de phrases courtes peuvent manquer de cohérence.
import re
from typing import List
def sentence_chunk(
text: str,
max_chunk_size: int = 512,
min_chunk_size: int = 100
) -> List[str]:
"""Sentence-based chunking strategy
Args:
text: Text to chunk
max_chunk_size: Maximum chunk size in characters
min_chunk_size: Minimum chunk size in characters
Returns:
List of text chunks
"""
sentence_endings = re.compile(r'(?<=[.!?])\s+')
sentences = [s.strip() for s in sentence_endings.split(text) if s.strip()]
chunks = []
current_chunk = ""
for sentence in sentences:
if len(current_chunk) + len(sentence) > max_chunk_size and len(current_chunk) >= min_chunk_size:
chunks.append(current_chunk.strip())
current_chunk = sentence
else:
current_chunk += " " + sentence if current_chunk else sentence
if current_chunk.strip():
chunks.append(current_chunk.strip())
return chunks
# Usage example
text = "RAG systems are popular AI architectures. Chunking strategy impacts retrieval quality. Good chunking improves precision by 30%+. Semantic chunking is the 2026 trend."
result = sentence_chunk(text, max_chunk_size=80, min_chunk_size=20)
for i, chunk in enumerate(result):
print(f"Chunk {i+1}: {chunk}")
3. Chunking sémantique
La stratégie la plus recommandée en 2026. Utilise un modèle d'embedding pour calculer la similarité sémantique entre les phrases adjacentes, en divisant là où la similarité chute brutalement.
Avantages : Les chunks ont une sémantique interne hautement cohérente—précision de récupération maximale.
Inconvénients : Nécessite des appels supplémentaires au modèle d'embedding ; coût computationnel plus élevé.
from typing import List
import numpy as np
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
"""Compute cosine similarity"""
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def semantic_chunk(
text: str,
embed_func,
breakpoint_threshold: float = 0.3,
min_chunk_size: int = 50
) -> List[str]:
"""Semantic chunking strategy
Args:
text: Text to chunk
embed_func: Embedding function that returns a vector for input text
breakpoint_threshold: Similarity drop threshold for splitting
min_chunk_size: Minimum chunk size in characters
Returns:
List of text chunks
"""
import re
sentence_endings = re.compile(r'(?<=[.!?])\s+')
sentences = [s.strip() for s in sentence_endings.split(text) if s.strip()]
if len(sentences) <= 1:
return [text] if text.strip() else []
embeddings = [np.array(embed_func(s)) for s in sentences]
breakpoints = []
for i in range(len(embeddings) - 1):
sim = cosine_similarity(embeddings[i], embeddings[i + 1])
if sim < breakpoint_threshold:
breakpoints.append(i + 1)
breakpoints = [0] + breakpoints + [len(sentences)]
chunks = []
for i in range(len(breakpoints) - 1):
start = breakpoints[i]
end = breakpoints[i + 1]
chunk_text = " ".join(sentences[start:end])
if len(chunk_text) >= min_chunk_size:
chunks.append(chunk_text)
return chunks
# Usage example (requires embedding function)
# from openai import OpenAI
# client = OpenAI()
# def my_embed(text):
# resp = client.embeddings.create(input=text, model="text-embedding-3-small")
# return resp.data[0].embedding
# result = semantic_chunk(long_text, my_embed)
4. Chunking récursif
La stratégie par défaut de LangChain. Utilise une liste priorisée de séparateurs—essaie d'abord les séparateurs de haut niveau (paragraphes, sections), puis recourt aux phrases, puis aux caractères.
Avantages : Équilibre sémantique et contrôle de la taille ; excellent pour les documents Markdown/code.
Inconvénients : La sélection des séparateurs nécessite de l'expérience ; dans les cas extrêmes, peut toujours tronquer.
from typing import List
def recursive_chunk(
text: str,
separators: List[str] = None,
chunk_size: int = 512,
chunk_overlap: int = 50
) -> List[str]:
"""Recursive chunking strategy
Args:
text: Text to chunk
separators: Priority list of separators
chunk_size: Target chunk size
chunk_overlap: Overlap size
Returns:
List of text chunks
"""
if separators is None:
separators = ["\n\n", "\n", ". ", " ", ""]
final_chunks = []
def _recursive_split(current_text: str, current_separators: List[str]):
if not current_text:
return
if len(current_text) <= chunk_size:
final_chunks.append(current_text)
return
sep = current_separators[0] if current_separators else ""
remaining_seps = current_separators[1:] if current_separators else []
if sep == "":
for i in range(0, len(current_text), chunk_size - chunk_overlap):
chunk = current_text[i:i + chunk_size]
if chunk.strip():
final_chunks.append(chunk)
return
splits = current_text.split(sep)
good_splits = []
for split in splits:
if len(split) <= chunk_size:
good_splits.append(split)
else:
if good_splits:
merged = sep.join(good_splits)
if merged.strip():
final_chunks.append(merged)
good_splits = []
_recursive_split(split, remaining_seps)
if good_splits:
merged = sep.join(good_splits)
if len(merged) <= chunk_size:
final_chunks.append(merged)
else:
_recursive_split(merged, remaining_seps)
_recursive_split(text, separators)
return [c.strip() for c in final_chunks if c.strip()]
# Usage example
md_text = """## Overview\nRAG is the core AI architecture.\n\n## Chunking Strategy\nChunking determines retrieval quality.\nGood chunking improves precision by 30%."""
result = recursive_chunk(md_text, chunk_size=60, chunk_overlap=15)
for i, chunk in enumerate(result):
print(f"Chunk {i+1}: {chunk}")
5. Chunking basé sur les documents
Diviser selon la structure naturelle du document (titres, paragraphes, éléments de liste), en préservant la hiérarchie du document.
Avantages : Préserve la hiérarchie contextuelle ; les chunks portent les informations de titre ; des métadonnées peuvent être jointes.
Inconvénients : Dépend du format du document ; le texte non structuré ne peut pas utiliser cette approche.
from typing import List, Dict
import re
def document_chunk(
markdown_text: str,
max_chunk_size: int = 1024,
add_parent_headers: bool = True
) -> List[Dict]:
"""Document-based chunking strategy (Markdown)
Args:
markdown_text: Markdown formatted text
max_chunk_size: Maximum chunk size in characters
add_parent_headers: Whether to prepend parent headers as context
Returns:
List of chunks with text and metadata
"""
lines = markdown_text.split("\n")
header_stack = []
chunks = []
current_content = []
for line in lines:
header_match = re.match(r'^(#{1,6})\s+(.+)$', line)
if header_match:
if current_content:
content = "\n".join(current_content).strip()
if content:
context = ""
if add_parent_headers and header_stack:
context = " > ".join(header_stack) + "\n"
chunks.append({
"text": context + content,
"metadata": {
"headers": header_stack.copy(),
"level": len(header_stack)
}
})
current_content = []
level = len(header_match.group(1))
title = header_match.group(2)
header_stack = header_stack[:level - 1] + [title]
else:
current_content.append(line)
if current_content:
content = "\n".join(current_content).strip()
if content:
context = ""
if add_parent_headers and header_stack:
context = " > ".join(header_stack) + "\n"
chunks.append({
"text": context + content,
"metadata": {
"headers": header_stack.copy(),
"level": len(header_stack)
}
})
return chunks
# Usage example
doc = """## RAG Overview\nRAG stands for Retrieval-Augmented Generation.\n\n### Core Components\nIncludes retriever and generator.\n\n## Chunking Strategy\nChunking is the key step in RAG."""
result = document_chunk(doc)
for i, chunk in enumerate(result):
print(f"Chunk {i+1}: {chunk['text'][:60]}... | Headers: {chunk['metadata']['headers']}")
6. Chunking hybride
La meilleure pratique pour les environnements de production en 2026. Sélectionne automatiquement la combinaison optimale de stratégie de chunking en fonction du type de document et des caractéristiques du contenu.
Avantages : Gère tous les types de documents ; résultats les plus stables.
Inconvénients : Implémentation la plus complexe ; nécessite une logique de routage de stratégie.
from typing import List, Dict
import re
def hybrid_chunk(
text: str,
embed_func=None,
chunk_size: int = 512,
chunk_overlap: int = 50
) -> List[Dict]:
"""Hybrid chunking strategy
Args:
text: Text to chunk
embed_func: Optional embedding function
chunk_size: Target chunk size
chunk_overlap: Overlap size
Returns:
List of chunk results
"""
has_headers = bool(re.search(r'^#{1,6}\s+', text, re.MULTILINE))
has_code = bool(re.search(r'```', text))
avg_line_len = len(text) / max(text.count("\n") + 1, 1)
strategy = "recursive"
if has_headers and not has_code:
strategy = "document"
elif embed_func is not None and avg_line_len > 80:
strategy = "semantic"
elif has_code:
strategy = "recursive"
elif avg_line_len < 30:
strategy = "sentence"
chunks_data = []
if strategy == "document":
chunks_data = document_chunk(text, max_chunk_size=chunk_size)
elif strategy == "semantic" and embed_func:
result = semantic_chunk(text, embed_func, min_chunk_size=chunk_size // 2)
chunks_data = [{"text": c, "metadata": {"strategy": "semantic"}} for c in result]
elif strategy == "sentence":
result = sentence_chunk(text, max_chunk_size=chunk_size)
chunks_data = [{"text": c, "metadata": {"strategy": "sentence"}} for c in result]
else:
result = recursive_chunk(text, chunk_size=chunk_size, chunk_overlap=chunk_overlap)
chunks_data = [{"text": c, "metadata": {"strategy": "recursive"}} for c in result]
for chunk in chunks_data:
if "metadata" not in chunk:
chunk["metadata"] = {}
chunk["metadata"]["strategy_used"] = strategy
return chunks_data
# Usage example
# result = hybrid_chunk(your_text, embed_func=my_embed)
# for chunk in result:
# print(f"[{chunk['metadata']['strategy_used']}] {chunk['text'][:50]}...")
Métriques d'évaluation et Benchmarks
Faire du chunking sans évaluation, c'est comme avancer à tâtons dans le noir. Voici un framework d'évaluation complet :
from typing import List, Dict
import numpy as np
def evaluate_chunks(
chunks: List[str],
embed_func,
questions: List[str],
relevance_labels: List[List[int]]
) -> Dict[str, float]:
"""Evaluate chunk quality
Args:
chunks: Chunk results
embed_func: Embedding function
questions: Test questions
relevance_labels: Relevant chunk indices for each question
Returns:
Dictionary of evaluation metrics
"""
chunk_embeddings = np.array([embed_func(c) for c in chunks])
question_embeddings = np.array([embed_func(q) for q in questions])
avg_internal_sim = 0.0
count = 0
for emb in chunk_embeddings:
sims = np.dot(chunk_embeddings, emb) / (
np.linalg.norm(chunk_embeddings, axis=1) * np.linalg.norm(emb) + 1e-8
)
avg_internal_sim += np.mean(sims)
count += 1
avg_internal_sim /= max(count, 1)
chunk_sizes = [len(c) for c in chunks]
size_cv = np.std(chunk_sizes) / (np.mean(chunk_sizes) + 1e-8)
return {
"num_chunks": len(chunks),
"avg_chunk_size": np.mean(chunk_sizes),
"size_coefficient_of_variation": size_cv,
"avg_internal_similarity": avg_internal_sim,
"size_std": np.std(chunk_sizes),
"min_chunk_size": min(chunk_sizes),
"max_chunk_size": max(chunk_sizes),
}
# Usage example
# metrics = evaluate_chunks(chunks, my_embed, questions, labels)
# for k, v in metrics.items():
# print(f"{k}: {v:.4f}")
Résultats de Benchmark (Dataset MS MARCO)
| Stratégie | Taille moyenne des chunks | CV de taille | Recall@5 | MRR | EM de bout en bout |
|---|---|---|---|---|---|
| Taille fixe | 512 | 0.02 | 0.62 | 0.48 | 0.35 |
| Basée sur les phrases | 380 | 0.45 | 0.71 | 0.56 | 0.42 |
| Sémantique | 420 | 0.38 | 0.83 | 0.69 | 0.56 |
| Récursive | 460 | 0.22 | 0.76 | 0.61 | 0.47 |
| Basée sur les documents | 550 | 0.55 | 0.78 | 0.64 | 0.50 |
| Hybride | 440 | 0.30 | 0.85 | 0.72 | 0.59 |
5 erreurs courantes
-
Taille de chunk unique pour tout : Utiliser le même chunk_size pour différents types de documents (code vs. prose) garantit de mauvais résultats. La documentation de code convient aux chunks plus petits (256-384), les articles techniques aux chunks moyens (384-512), les documents juridiques aux chunks plus grands (512-1024).
-
Ignorer les métadonnées : Stocker uniquement le texte du chunk sans métadonnées (source, titre, numéro de page) rend impossible le suivi des origines ou la récupération filtrée.
-
Paramètres de chevauchement inadéquats : Trop de chevauchement entraîne une récupération redondante ; trop peu perd les informations de frontière. Règle générale : chevauchement = chunk_size × 10%-15%.
-
Absence de prétraitement : Ne pas nettoyer le texte avant le chunking (supprimer les caractères spéciaux, fusionner les lignes vides, corriger l'encodage) produit des chunks poubelle à partir de données sales.
-
Se fier uniquement aux métriques hors ligne : Un Recall hors ligne élevé ne signifie pas une bonne performance en ligne. Vous devez faire des tests A/B et mesurer les taux de clics réels et la satisfaction utilisateur.
10 éléments de dépannage
| # | Symptôme | Cause possible | Solution |
|---|---|---|---|
| 1 | Nombre de chunks largement supérieur aux attentes | chunk_size trop petit | Augmenter chunk_size à 384-512 |
| 2 | Résultats récupérés sémantiquement non pertinents | Les chunks franchissent les limites sémantiques | Passer au chunking sémantique ou récursif |
| 3 | Contexte perdu après le chunking de documents longs | Pas d'information de titre parent | Activer add_parent_headers ou ajouter une fenêtre de contexte |
| 4 | Blocs de code tronqués | Séparateur de nouvelle ligne utilisé dans les blocs de code | Chunking récursif : prioriser ``` comme séparateur |
| 5 | Éléments de liste dispersés | Division à l'intérieur des listes | Chunking basé sur les documents ou fusionner les éléments de liste |
| 6 | Incompatibilité de dimensions d'embedding | Chunks vides ou contenant uniquement des espaces | Filtrer les chunks vides après le chunking |
| 7 | Le chunking prend trop de temps | Le chunking sémantique fait l'embedding phrase par phrase | Embedding par lot + cache |
| 8 | Mémoire insuffisante | Traiter des documents super-grands d'un coup | Chunking en flux, traiter segment par segment |
| 9 | Mauvais résultats avec chinois/anglais mélangés | Les séparateurs ne couvrent pas la ponctuation chinoise | Ajouter la ponctuation chinoise à la liste des séparateurs |
| 10 | Chunks similaires dupliqués récupérés | Le chevauchement crée des chunks hautement redondants | Dédupliquer ou réduire le ratio de chevauchement |
Conseils avancés d'optimisation
Enrichissement du contexte
Joindre le texte adjacent avant et après chaque chunk comme fenêtre de contexte :
def context_enrichment(
chunks: List[str],
context_window: int = 100
) -> List[str]:
"""Add context window to chunks"""
enriched = []
for i, chunk in enumerate(chunks):
prefix = chunks[i-1][-context_window:] if i > 0 else ""
suffix = chunks[i+1][:context_window] if i < len(chunks)-1 else ""
enriched.append(f"{prefix}[CHUNK]{chunk}[CHUNK]{suffix}")
return enriched
Taille de chunk adaptative
Ajuster dynamiquement la taille du chunk en fonction de la densité d'information du texte—le code et les tableaux ont une densité élevée (utiliser de petits chunks), le texte narratif a une densité faible (utiliser de grands chunks) :
def adaptive_chunk_size(text: str, base_size: int = 512) -> int:
"""Adaptively adjust chunk size based on text characteristics"""
code_ratio = len(re.findall(r'[{}()\[\];]', text)) / max(len(text), 1)
table_ratio = text.count('|') / max(len(text), 1)
if code_ratio > 0.05 or table_ratio > 0.03:
return int(base_size * 0.6)
elif len(text.split('\n')) / max(len(text), 1) > 0.02:
return int(base_size * 0.8)
else:
return base_size
Indexation multigranularité
Construire des index multiniveaux avec différents chunk_sizes pour le même document—grossier d'abord, puis fin lors de la récupération :
def multi_granularity_index(
text: str,
sizes: List[int] = [256, 512, 1024]
) -> Dict[int, List[str]]:
"""Build multi-granularity index"""
return {
size: recursive_chunk(text, chunk_size=size, chunk_overlap=size//10)
for size in sizes
}
Outils recommandés
Ces outils en ligne peuvent booster votre efficacité lors du travail avec le chunking RAG :
- Formateur JSON : Les métadonnées de chunks sont généralement au format JSON. Utilisez cet outil pour formater et valider rapidement, en garantissant une structure de métadonnées correcte.
- Encodeur Base64 : Encoder le texte des chunks en Base64 pour le stockage ou la transmission, particulièrement utile pour les chunks contenant des caractères spéciaux.
- Calculateur de Hash : Calculer des valeurs de hash uniques pour chaque chunk pour la déduplication et la gestion de versions, évitant l'indexation dupliquée de contenu identique.
Résumé : En 2026, la sélection de la stratégie de chunking RAG n'est plus dans l'ère du « taille fixe suffit ». Le chunking sémantique et le chunking hybride sont devenus la norme. Le principe fondamental est : rendre chaque chunk sémantiquement autonome, contextuellement traçable et de taille adaptable. Choisissez la bonne stratégie, et la précision de récupération de votre système RAG s'améliorera d'au moins 30%.
Essayez ces outils exécutés localement dans le navigateur — aucune inscription requise →