Récupération de Document Parent : Contexte Sans Bruit
Recherchez dans de petits fragments, récupérez les documents complets : le meilleur de la précision et du contexte pour les systèmes RAG.
Le problème
Petits chunks :
- ✅ Récupération précise
- ❌ Contexte manquant
Grands chunks :
- ✅ Contexte complet
- ❌ Récupération bruyante
Solution : Rechercher petit, retourner grand.
Comment ça fonctionne
- Indexer : Petits chunks (200 tokens)
- Rechercher : Trouver les petits chunks pertinents
- Récupérer : Retourner le document parent (2000 tokens)
Implémentation de base
DEVELOPERpythonimport uuid # Stocker les chunks avec référence parent chunks = [] documents = [] for doc in raw_documents: parent_id = str(uuid.uuid4()) # Stocker le document complet documents.append({ "id": parent_id, "content": doc, "embedding": embed(doc) }) # Créer de petits chunks for chunk in split_into_chunks(doc, size=200): chunks.append({ "id": str(uuid.uuid4()), "content": chunk, "embedding": embed(chunk), "parent_id": parent_id # Lien vers le parent }) # Indexer uniquement les chunks vector_db.upsert(collection="chunks", documents=chunks)
Récupération
DEVELOPERpythondef parent_document_retrieval(query, k=5): # Rechercher les petits chunks chunk_results = vector_db.search( collection="chunks", query_vector=embed(query), limit=k ) # Obtenir les IDs des documents parents parent_ids = [chunk["parent_id"] for chunk in chunk_results] # Récupérer les documents parents parent_docs = [ doc for doc in documents if doc["id"] in parent_ids ] return parent_docs
Implémentation Langchain
DEVELOPERpythonfrom langchain.retrievers import ParentDocumentRetriever from langchain.storage import InMemoryStore from langchain.vectorstores import Chroma from langchain.text_splitter import RecursiveCharacterTextSplitter # Store pour les documents parents docstore = InMemoryStore() # Vector store pour les chunks vectorstore = Chroma(embedding_function=embeddings) # Splitters child_splitter = RecursiveCharacterTextSplitter(chunk_size=200) parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000) # Créer le retriever retriever = ParentDocumentRetriever( vectorstore=vectorstore, docstore=docstore, child_splitter=child_splitter, parent_splitter=parent_splitter ) # Ajouter des documents retriever.add_documents(documents) # Récupérer (retourne les docs parents complets) results = retriever.get_relevant_documents("machine learning")
Hiérarchie multi-niveaux
DEVELOPERpython# Structure Livre → Chapitre → Paragraphe def create_hierarchy(book): book_id = str(uuid.uuid4()) for chapter in book.chapters: chapter_id = str(uuid.uuid4()) # Indexer les paragraphes (petits) for paragraph in chapter.paragraphs: vector_db.upsert({ "id": str(uuid.uuid4()), "content": paragraph, "embedding": embed(paragraph), "parent_id": chapter_id, # Chapitre "grandparent_id": book_id # Livre }) # Stocker le chapitre chapters[chapter_id] = chapter # Stocker le livre books[book_id] = book def retrieve_with_context(query): # Trouver les paragraphes pertinents paragraphs = vector_db.search(embed(query), limit=3) # Obtenir le contexte environnant 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
Récupération par fenêtre
Retourner le chunk + contexte environnant :
DEVELOPERpythondef windowed_retrieval(query, window_size=2): # Trouver le chunk pertinent chunk_results = vector_db.search(embed(query), limit=5) # Obtenir les chunks avant et après expanded_results = [] for chunk in chunk_results: parent_doc = get_document(chunk["parent_id"]) chunk_index = find_chunk_index(parent_doc, chunk["content"]) # Obtenir la fenêtre 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
Implémentation Qdrant
DEVELOPERpythonfrom qdrant_client import QdrantClient from qdrant_client.models import PointStruct client = QdrantClient("localhost", port=6333) # Créer une collection avec ID parent dans le payload client.create_collection( collection_name="chunks", vectors_config={"size": 1536, "distance": "Cosine"} ) # Insérer les chunks avec référence parent 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) # Récupérer def retrieve_parents(query): results = client.search( collection_name="chunks", query_vector=embed(query), limit=5 ) # Obtenir les IDs parents uniques parent_ids = list(set([r.payload["parent_id"] for r in results])) # Récupérer les parents depuis le document store parents = [get_document(pid) for pid in parent_ids] return parents
Quand utiliser
✅ Utiliser la récupération de document parent quand :
- Les documents ont une structure claire
- Vous avez besoin du contexte complet pour le LLM
- La précision est importante
❌ Ne pas utiliser quand :
- Les documents sont déjà petits (< 500 tokens)
- Vous voulez minimiser l'usage de tokens
- Le contexte n'est pas important
La récupération de document parent vous donne la précision sans sacrifier le contexte. Le meilleur des deux mondes.
Tags
Articles connexes
Recherche Hybride : Combiner Recherche Sémantique et par Mots-clés
Augmentez la Précision de Récupération de 20-30% : Combinez la Recherche Vectorielle avec le Matching par Mots-clés BM25 pour des Performances RAG Supérieures.
Expansion de Requêtes : Récupérer des Résultats Plus Pertinents
Améliorer le recall de 40% : expandez les requêtes utilisateur avec des synonymes, des sous-requêtes et des variations générées par LLM.
MMR : Diversifier les Résultats de Recherche avec la Pertinence Marginale Maximale
Réduisez la redondance dans la récupération RAG : utilisez MMR pour équilibrer pertinence et diversité pour une meilleure qualité de contexte.