Découpage Hiérarchique : Préserver la Structure de vos Documents
Le découpage hiérarchique conserve les relations parent-enfant dans vos documents. Apprenez à implémenter cette technique avancée pour améliorer la qualité de récupération RAG.
TL;DR
- Découpage hiérarchique = préserver les sections, sous-sections et paragraphes
- Avantage : contexte riche + granularité fine simultanément
- Implémentation : chunks imbriqués avec métadonnées de hiérarchie
- Gain typique : +20-35% de pertinence sur documents structurés
- Testez le chunking hiérarchique sur Ailog
Pourquoi le Découpage Hiérarchique ?
Les documents réels ont une structure :
- Chapitres > Sections > Sous-sections > Paragraphes
- Cette hiérarchie porte du sens sémantique
Le découpage classique (taille fixe ou sémantique) ignore cette structure :
Document original:
├── Chapitre 1: Introduction
│ ├── 1.1 Contexte
│ └── 1.2 Objectifs
└── Chapitre 2: Méthodes
├── 2.1 Approche A
└── 2.2 Approche B
Découpage classique:
[Chunk 1: "...fin du contexte. 1.2 Objectifs..."] ❌ Mélange de sections
[Chunk 2: "...début méthodes..."] ❌ Perte de hiérarchie
Principe du Découpage Hiérarchique
Créer des chunks à plusieurs niveaux avec des liens parent-enfant :
DEVELOPERpython# Structure hiérarchique préservée { "id": "doc1_ch2_s1", "content": "2.1 Approche A - Description détaillée...", "metadata": { "level": 3, "parent_id": "doc1_ch2", "path": ["Chapitre 2: Méthodes", "2.1 Approche A"], "document_id": "doc1" } }
Implémentation Python
Extraction de la Hiérarchie
DEVELOPERpythonimport re from dataclasses import dataclass from typing import List, Optional @dataclass class HierarchicalChunk: id: str content: str level: int title: str parent_id: Optional[str] path: List[str] children_ids: List[str] def extract_hierarchy(text: str, patterns: dict = None) -> List[HierarchicalChunk]: """ Extrait la structure hiérarchique d'un document. patterns: Regex pour détecter les niveaux """ if patterns is None: patterns = { 1: r'^# (.+)$', # Titre principal 2: r'^## (.+)$', # Sections 3: r'^### (.+)$', # Sous-sections 4: r'^#### (.+)$', # Sous-sous-sections } chunks = [] current_path = [] parent_stack = [] # Stack of (level, chunk_id) # Split by headers lines = text.split('\n') current_content = [] current_title = "Document" current_level = 0 chunk_counter = 0 for line in lines: header_found = False for level, pattern in patterns.items(): match = re.match(pattern, line, re.MULTILINE) if match: # Save previous chunk if current_content: chunk_id = f"chunk_{chunk_counter}" parent_id = parent_stack[-1][1] if parent_stack else None chunk = HierarchicalChunk( id=chunk_id, content='\n'.join(current_content).strip(), level=current_level, title=current_title, parent_id=parent_id, path=current_path.copy(), children_ids=[] ) chunks.append(chunk) chunk_counter += 1 # Update hierarchy current_title = match.group(1) current_level = level current_content = [] # Update path and parent stack while parent_stack and parent_stack[-1][0] >= level: parent_stack.pop() if current_path: current_path.pop() current_path.append(current_title) parent_stack.append((level, f"chunk_{chunk_counter}")) header_found = True break if not header_found: current_content.append(line) # Don't forget last chunk if current_content: chunk_id = f"chunk_{chunk_counter}" parent_id = parent_stack[-1][1] if parent_stack else None chunk = HierarchicalChunk( id=chunk_id, content='\n'.join(current_content).strip(), level=current_level, title=current_title, parent_id=parent_id, path=current_path.copy(), children_ids=[] ) chunks.append(chunk) return chunks
Indexation Multi-Niveaux
DEVELOPERpythondef index_hierarchical_chunks(chunks: List[HierarchicalChunk], vector_db): """ Indexe les chunks avec leur contexte hiérarchique. """ for chunk in chunks: # Créer le contexte enrichi path_context = " > ".join(chunk.path) enriched_content = f"{path_context}\n\n{chunk.content}" # Générer l'embedding embedding = embed(enriched_content) # Stocker avec métadonnées vector_db.upsert( id=chunk.id, embedding=embedding, metadata={ "content": chunk.content, "title": chunk.title, "level": chunk.level, "parent_id": chunk.parent_id, "path": path_context, "path_list": chunk.path } )
Récupération Contextuelle
Stratégie : Small-to-Big
Rechercher dans les chunks fins, retourner le contexte parent :
DEVELOPERpythondef hierarchical_retrieve(query: str, vector_db, k: int = 3) -> List[dict]: """ Récupère les chunks pertinents avec leur contexte parent. """ # 1. Recherche fine (niveau le plus bas) results = vector_db.query( query_embedding=embed(query), filter={"level": {"$gte": 3}}, # Sous-sections et plus limit=k * 2 ) # 2. Enrichir avec contexte parent enriched_results = [] seen_parents = set() for result in results: parent_id = result.metadata.get("parent_id") # Récupérer la chaîne de parents context_chain = [result.metadata["content"]] current_parent = parent_id while current_parent and current_parent not in seen_parents: parent = vector_db.get(current_parent) if parent: context_chain.insert(0, parent.metadata["content"]) seen_parents.add(current_parent) current_parent = parent.metadata.get("parent_id") else: break enriched_results.append({ "chunk": result, "full_context": "\n\n---\n\n".join(context_chain), "path": result.metadata["path"] }) return enriched_results[:k]
Stratégie : Big-to-Small
Rechercher au niveau section, puis drill-down :
DEVELOPERpythondef drill_down_retrieve(query: str, vector_db, k: int = 3) -> List[dict]: """ Commence par les sections, puis affine vers les détails. """ # 1. Recherche au niveau section sections = vector_db.query( query_embedding=embed(query), filter={"level": 2}, limit=k ) # 2. Pour chaque section pertinente, chercher les détails detailed_results = [] for section in sections: # Chercher les enfants de cette section children = vector_db.query( query_embedding=embed(query), filter={ "parent_id": section.id }, limit=3 ) detailed_results.append({ "section": section, "details": children, "combined_context": ( section.metadata["content"] + "\n\n" + "\n".join([c.metadata["content"] for c in children]) ) }) return detailed_results
LlamaIndex : Parent Document Retriever
LlamaIndex offre une implémentation native :
DEVELOPERpythonfrom llama_index import VectorStoreIndex, ServiceContext from llama_index.node_parser import HierarchicalNodeParser from llama_index.retrievers import AutoMergingRetriever from llama_index.query_engine import RetrieverQueryEngine # 1. Parser hiérarchique node_parser = HierarchicalNodeParser.from_defaults( chunk_sizes=[2048, 512, 128] # Niveaux de granularité ) # 2. Créer les nodes nodes = node_parser.get_nodes_from_documents(documents) # 3. Indexer index = VectorStoreIndex(nodes) # 4. Retriever avec auto-merging retriever = AutoMergingRetriever( index.as_retriever(similarity_top_k=6), index.storage_context, verbose=True ) # 5. Query engine query_engine = RetrieverQueryEngine.from_args(retriever) response = query_engine.query("Quelles sont les méthodes utilisées ?")
LangChain : Parent Document Retriever
DEVELOPERpythonfrom langchain.retrievers import ParentDocumentRetriever from langchain.storage import InMemoryStore from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.vectorstores import Chroma # Splitters pour différents niveaux parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000) child_splitter = RecursiveCharacterTextSplitter(chunk_size=400) # Store pour les parents docstore = InMemoryStore() # Vectorstore pour les enfants (recherche fine) vectorstore = Chroma(embedding_function=embeddings) # Parent Document Retriever retriever = ParentDocumentRetriever( vectorstore=vectorstore, docstore=docstore, child_splitter=child_splitter, parent_splitter=parent_splitter, ) # Ajouter les documents retriever.add_documents(documents) # Recherche : enfants matchent, parents retournés results = retriever.get_relevant_documents("Question sur les méthodes")
Optimisation des Métadonnées
Enrichir le Chemin Sémantique
DEVELOPERpythondef create_semantic_path(chunk: HierarchicalChunk) -> str: """ Crée un chemin sémantique lisible pour le LLM. """ path_parts = [] for i, title in enumerate(chunk.path): level_prefix = { 0: "Document:", 1: "Chapitre:", 2: "Section:", 3: "Sous-section:", 4: "Paragraphe:" }.get(i, "") path_parts.append(f"{level_prefix} {title}") return " → ".join(path_parts) # Exemple de sortie: # "Document: Manuel technique → Chapitre: Installation → Section: Prérequis"
Ajouter des Breadcrumbs au Contexte
DEVELOPERpythondef format_context_with_breadcrumbs(chunks: List[dict]) -> str: """ Formate le contexte avec des breadcrumbs pour le LLM. """ formatted = [] for chunk in chunks: breadcrumb = chunk['path'] content = chunk['content'] formatted.append(f""" 📍 {breadcrumb} {content} """) return "\n---\n".join(formatted)
Quand Utiliser le Découpage Hiérarchique
Utilisez-le quand :
- Documents longs et structurés (manuels, docs techniques)
- Hiérarchie claire (chapitres, sections, sous-sections)
- Besoin de contexte large ET de précision fine
- Questions qui traversent plusieurs niveaux
Évitez quand :
- Documents plats (emails, chats, logs)
- Contenu très homogène
- Contraintes de latence strictes (overhead de récupération)
- Documents très courts (< 2000 tokens)
Benchmarks
| Type de document | Découpage fixe | Sémantique | Hiérarchique |
|---|---|---|---|
| Documentation technique | 65% | 72% | 88% |
| Rapports structurés | 58% | 68% | 85% |
| Articles scientifiques | 62% | 75% | 82% |
| Texte narratif | 70% | 78% | 72% |
MRR@5 sur datasets de test internes
Le hiérarchique excelle sur les documents structurés mais n'apporte pas de gain sur le contenu narratif.
Guides connexes
Chunking :
- Stratégies de Chunking - Vue d'ensemble des approches
- Découpage Sémantique - Découpage basé sur le sens
- Découpage à Taille Fixe - Approche classique
Retrieval :
- Parent Document Retrieval - Récupération avec contexte parent
- Stratégies de Récupération - Techniques avancées
Besoin d'aide pour implémenter le chunking hiérarchique sur vos documents complexes ? Parlons de votre projet →
Tags
Articles connexes
Stratégies de Chunking RAG 2025 : Tailles Optimales & Techniques
Maîtrisez le chunking pour RAG : tailles optimales (512-1024 tokens), stratégies de chevauchement, découpage sémantique vs fixe. +25% de précision retrieval.
Découpage Sémantique pour une Meilleure Récupération
Divisez les documents intelligemment en fonction du sens, pas seulement de la longueur. Apprenez les techniques de découpage sémantique pour le RAG.
Chunking à Taille Fixe : Rapide et Fiable
Maîtrisez les bases : implémentez le chunking à taille fixe avec chevauchements pour des performances RAG cohérentes et prévisibles.