Hierarchische Aufteilung: Die Struktur Ihrer Dokumente bewahren
Die hierarchische Aufteilung erhält die Eltern-Kind-Beziehungen in Ihren Dokumenten. Lernen Sie, wie Sie diese fortgeschrittene Technik implementieren, um die Retrieval-Qualität von RAG zu verbessern.
TL;DR
- Hierarchisches Chunking = die Abschnitte, Unterabschnitte und Paragraphen bewahren
- Vorteil : reichhaltiger Kontext + feine Granularität gleichzeitig
- Implementierung : verschachtelte Chunks mit Hierarchie-Metadaten
- Typischer Gewinn : +20-35% Relevanz bei strukturierten Dokumenten
- Testen Sie das hierarchische Chunking auf Ailog
Warum hierarchisches Chunking ?
Reale Dokumente haben eine Struktur :
- Kapitel > Abschnitte > Unterabschnitte > Paragraphen
- Diese Hierarchie trägt semantische Bedeutung
Das klassische Chunking (feste Größe oder semantisch) ignoriert diese Struktur :
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
Prinzip des hierarchischen Chunkings
Erstelle Chunks auf mehreren Ebenen mit Parent-Child-Verknüpfungen :
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" } }
Implementierung 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]: """ Extrahiert die hierarchische Struktur eines Dokuments. patterns: Regex zur Erkennung der Ebenen """ 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): """ Indiziert die Chunks mit ihrem hierarchischen Kontext. """ for chunk in chunks: # Erzeuge den angereicherten Kontext path_context = " > ".join(chunk.path) enriched_content = f"{path_context}\n\n{chunk.content}" # Erzeuge das embedding embedding = embed(enriched_content) # Speichere mit Metadaten 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 } )
Kontextuelle Retrieval
Strategie : Small-to-Big
Recherchiere in den feinen Chunks, gib den Elternkontext zurück :
DEVELOPERpythondef hierarchical_retrieve(query: str, vector_db, k: int = 3) -> List[dict]: """ Gibt die relevanten Chunks mit ihrem Elternkontext zurück. """ # 1. Feinere Suche (niedrigste Ebene) results = vector_db.query( query_embedding=embed(query), filter={"level": {"$gte": 3}}, # Unter-sections und höher limit=k * 2 ) # 2. Mit Elternkontext anreichern enriched_results = [] seen_parents = set() for result in results: parent_id = result.metadata.get("parent_id") # Elternkette abrufen 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]
Strategie : Big-to-Small
Suche auf Abschnittsebene, dann drill-down :
DEVELOPERpythondef drill_down_retrieve(query: str, vector_db, k: int = 3) -> List[dict]: """ Beginnt auf Abschnittsebene und verfeinert dann zu den Details. """ # 1. Suche auf Abschnittsebene sections = vector_db.query( query_embedding=embed(query), filter={"level": 2}, limit=k ) # 2. Für jeden relevanten Abschnitt die Details suchen detailed_results = [] for section in sections: # Kinder dieses Abschnitts suchen 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 bietet eine native Implementierung :
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. Hierarchischer Parser node_parser = HierarchicalNodeParser.from_defaults( chunk_sizes=[2048, 512, 128] # Granularitätsstufen ) # 2. Erzeuge die Nodes nodes = node_parser.get_nodes_from_documents(documents) # 3. Indexieren index = VectorStoreIndex(nodes) # 4. Retriever mit 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 für verschiedene Ebenen parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000) child_splitter = RecursiveCharacterTextSplitter(chunk_size=400) # Store für die Eltern docstore = InMemoryStore() # Vectorstore für die Kinder (feine Suche) vectorstore = Chroma(embedding_function=embeddings) # Parent Document Retriever retriever = ParentDocumentRetriever( vectorstore=vectorstore, docstore=docstore, child_splitter=child_splitter, parent_splitter=parent_splitter, ) # Dokumente hinzufügen retriever.add_documents(documents) # Suche: Kinder matchen, Eltern werden zurückgegeben results = retriever.get_relevant_documents("Question sur les méthodes")
Optimierung der Metadaten
Den semantischen Pfad anreichern
DEVELOPERpythondef create_semantic_path(chunk: HierarchicalChunk) -> str: """ Erstellt einen für das LLM lesbaren semantischen Pfad. """ path_parts = [] for i, title in enumerate(chunk.path): level_prefix = { 0: "Dokument:", 1: "Kapitel:", 2: "Abschnitt:", 3: "Unterabschnitt:", 4: "Absatz:" }.get(i, "") path_parts.append(f"{level_prefix} {title}") return " → ".join(path_parts) # Beispiel-Ausgabe: # "Dokument: Manuel technique → Kapitel: Installation → Abschnitt: Prérequis"
Breadcrumbs zum Kontext hinzufügen
DEVELOPERpythondef format_context_with_breadcrumbs(chunks: List[dict]) -> str: """ Formatiert den Kontext mit Breadcrumbs für das LLM. """ formatted = [] for chunk in chunks: breadcrumb = chunk['path'] content = chunk['content'] formatted.append(f""" 📍 {breadcrumb} {content} """) return "\n---\n".join(formatted)
Wann hierarchisches Chunking verwenden
Verwenden Sie es, wenn :
- Lange, strukturierte Dokumente (Handbücher, technische Docs)
- Klare Hierarchie (Kapitel, Abschnitte, Unterabschnitte)
- Bedarf an großem Kontext UND feiner Präzision
- Fragen, die mehrere Ebenen durchqueren
Vermeiden Sie es, wenn :
- Flache Dokumente (E-Mails, Chats, Logs)
- Sehr homogener Inhalt
- Strikte Latenzanforderungen (Overhead bei der Retrieval)
- Sehr kurze Dokumente (< 2000 tokens)
Benchmarks
| Dokumenttyp | Feste Aufteilung | Semantisch | Hierarchisch |
|---|---|---|---|
| Documentation technique | 65% | 72% | 88% |
| Rapports structurés | 58% | 68% | 85% |
| Articles scientifiques | 62% | 75% | 82% |
| Texte narratif | 70% | 78% | 72% |
MRR@5 auf internen Testdatensätzen
Das Hierarchische glänzt bei strukturierten Dokumenten, bringt aber keinen Vorteil beim narrativen Inhalt.
Verwandte Leitfäden
Chunking :
- Chunking-Strategien - Überblick über die Ansätze
- Découpage Sémantique - Découpage basierend auf dem Sinn
- Découpage à Taille Fixe - Klassischer Ansatz
Retrieval :
- Parent Document Retrieval - Retrieval mit Elternkontext
- Stratégies de Récupération - Fortgeschrittene Techniken
Brauchen Sie Hilfe bei der Implementierung des hierarchischen Chunkings für Ihre komplexen Dokumente ? Lassen Sie uns über Ihr Projekt sprechen →
Tags
Verwandte Artikel
Strategien für Chunking RAG 2025: Optimale Größen & Techniken
Beherrschen Sie Chunking für RAG: optimale Größen (512–1024 tokens), Überlappungsstrategien, semantische vs. feste Segmentierung. +25% Retrieval-Genauigkeit.
Semantische Aufteilung für besseren Abruf
Teilen Sie Dokumente intelligent nach Bedeutung, nicht nur nach Länge. Lernen Sie Techniken der semantischen Aufteilung für RAG.
Chunking mit fester Größe: Schnell und zuverlässig
Beherrschen Sie die Grundlagen: Implementieren Sie Chunking mit fester Größe und Überlappungen für konsistente und vorhersehbare RAG-Leistung.