Abruf übergeordneter Dokumente: rauschfreier Kontext
Durchsuchen Sie kleine Fragmente, rufen Sie die vollständigen Dokumente ab: die beste Kombination aus Präzision und Kontext für RAG-Systeme.
Das Problem
Petits chunks :
- ✅ Präzise retrieval
- ❌ Kontext fehlt
Grands chunks :
- ✅ Vollständiger Kontext
- ❌ Rauschhafte retrieval
Solution : Klein suchen, groß zurückgeben.
Wie es funktioniert
- Indexer : Kleine chunks (200 tokens)
- Rechercher : Relevante kleine chunks finden
- Récupérer : Parent-Dokument zurückgeben (2000 tokens)
Basisimplementierung
DEVELOPERpythonimport uuid # Chunks mit Parent-Referenz speichern chunks = [] documents = [] for doc in raw_documents: parent_id = str(uuid.uuid4()) # Gesamtes Dokument speichern documents.append({ "id": parent_id, "content": doc, "embedding": embed(doc) }) # Kleine Chunks erstellen for chunk in split_into_chunks(doc, size=200): chunks.append({ "id": str(uuid.uuid4()), "content": chunk, "embedding": embed(chunk), "parent_id": parent_id # Verweis auf den Parent }) # Nur die Chunks indexieren vector_db.upsert(collection="chunks", documents=chunks)
Retrieval
DEVELOPERpythondef parent_document_retrieval(query, k=5): # Kleine Chunks suchen chunk_results = vector_db.search( collection="chunks", query_vector=embed(query), limit=k ) # Parent-Dokument-IDs erhalten parent_ids = [chunk["parent_id"] for chunk in chunk_results] # Parent-Dokumente abrufen parent_docs = [ doc for doc in documents if doc["id"] in parent_ids ] return parent_docs
Langchain-Implementierung
DEVELOPERpythonfrom langchain.retrievers import ParentDocumentRetriever from langchain.storage import InMemoryStore from langchain.vectorstores import Chroma from langchain.text_splitter import RecursiveCharacterTextSplitter # Store für die Parent-Dokumente docstore = InMemoryStore() # Vectorstore für die chunks vectorstore = Chroma(embedding_function=embeddings) # Splitter child_splitter = RecursiveCharacterTextSplitter(chunk_size=200) parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000) # Retriever erstellen retriever = ParentDocumentRetriever( vectorstore=vectorstore, docstore=docstore, child_splitter=child_splitter, parent_splitter=parent_splitter ) # Dokumente hinzufügen retriever.add_documents(documents) # Abrufen (gibt die vollständigen Parent-Dokumente zurück) results = retriever.get_relevant_documents("machine learning")
Mehrstufige Hierarchie
DEVELOPERpython# Struktur Buch → Kapitel → Absatz def create_hierarchy(book): book_id = str(uuid.uuid4()) for chapter in book.chapters: chapter_id = str(uuid.uuid4()) # Absätze indexieren (klein) for paragraph in chapter.paragraphs: vector_db.upsert({ "id": str(uuid.uuid4()), "content": paragraph, "embedding": embed(paragraph), "parent_id": chapter_id, # Kapitel "grandparent_id": book_id # Buch }) # Kapitel speichern chapters[chapter_id] = chapter # Buch speichern books[book_id] = book def retrieve_with_context(query): # Relevante Absätze finden paragraphs = vector_db.search(embed(query), limit=3) # Umgebenden Kontext erhalten results = [] for p in paragraphs: chapter = chapters[p["parent_id"]] book = books[p["grandparent_id"]] results.append({ "match": p["content"], "chapter": chapter, "book_title": book.title }) return results
Retrieval per Fenster
Chunk plus umgebenden Kontext zurückgeben :
DEVELOPERpythondef windowed_retrieval(query, window_size=2): # Relevanten Chunk finden chunk_results = vector_db.search(embed(query), limit=5) # Chunks davor und danach erhalten expanded_results = [] for chunk in chunk_results: parent_doc = get_document(chunk["parent_id"]) chunk_index = find_chunk_index(parent_doc, chunk["content"]) # Fenster erhalten start = max(0, chunk_index - window_size) end = min(len(parent_doc.chunks), chunk_index + window_size + 1) expanded_chunk = "".join(parent_doc.chunks[start:end]) expanded_results.append(expanded_chunk) return expanded_results
Qdrant-Implementierung
DEVELOPERpythonfrom qdrant_client import QdrantClient from qdrant_client.models import PointStruct client = QdrantClient("localhost", port=6333) # Collection erstellen mit Parent-ID im Payload client.create_collection( collection_name="chunks", vectors_config={"size": 1536, "distance": "Cosine"} ) # Chunks mit Parent-Referenz einfügen points = [] for i, chunk in enumerate(chunks): points.append(PointStruct( id=i, vector=chunk["embedding"], payload={ "content": chunk["content"], "parent_id": chunk["parent_id"] } )) client.upsert(collection_name="chunks", points=points) # Abrufen def retrieve_parents(query): results = client.search( collection_name="chunks", query_vector=embed(query), limit=5 ) # Einzigartige Parent-IDs erhalten parent_ids = list(set([r.payload["parent_id"] for r in results])) # Parents aus dem Document Store abrufen parents = [get_document(pid) for pid in parent_ids] return parents
Wann verwenden
✅ Parent-Dokument-Retrieval verwenden, wenn :
- Die Dokumente eine klare Struktur haben
- Sie den vollständigen Kontext für das LLM benötigen
- Präzision wichtig ist
❌ Nicht verwenden, wenn :
- Die Dokumente bereits klein sind (< 500 tokens)
- Sie die Nutzung von tokens minimieren wollen
- Der Kontext nicht wichtig ist
Das Parent-Dokument-Retrieval bietet Präzision, ohne den Kontext zu opfern. Das Beste aus beiden Welten.
Tags
Verwandte Artikel
Grundlagen des Retrievals: Wie die RAG-Suche funktioniert
Beherrschen Sie die Grundlagen des Retrievals in RAG-Systemen: Embeddings, vector search, chunking und indexing für relevante Ergebnisse.
Hybride RAG-Suche: BM25 + Vektorsuche (2025)
+20–30% RAG-Genauigkeit mit hybrider Suche. Schritt-für-Schritt-Anleitung zum Kombinieren von BM25 und Vektorsuche mit Weaviate, Qdrant oder Pinecone.
Abfrageerweiterung: Relevantere Ergebnisse erhalten
Den Recall um 40 % verbessern: erweitern Sie Nutzeranfragen mit Synonymen, Unterabfragen und von LLM generierten Variationen.