Back to Blog

Advanced RAG: Implementing Parent Document Retrieval for Richer Context

8 min read

Retrieval-Augmented Generation (RAG) has emerged as the standard architecture for ground truth grounding in Large Language Model (LLM) applications. However, standard RAG pipelines suffer from a fundamental tension: the retrieval chunk size tradeoff.

If you embed and store small chunks (e.g., 100-200 tokens), the vector search is highly precise, identifying specific facts easily. However, when these small chunks are fed directly to the LLM, they often lack the surrounding context needed to synthesize a coherent or accurate answer. Conversely, if you embed and store large chunks (e.g., 1000-2000 tokens), the vector embeddings represent a broader mix of topics, which dilutes the semantic signal and results in lower retrieval precision.

Parent Document Retrieval resolves this tension by decoupling the chunks used for vector search from the chunks used for LLM generation.

Parent Document Retrieval ArchitectureParent Document Retrieval Architecture

The Mechanics of Parent Document Retrieval

The core idea is simple:

  1. Divide large documents (called "Parent Documents") into smaller, overlapping segments (called "Child Documents").
  2. Embed and index only the Child Documents in your vector database.
  3. Keep a mapping in a key-value store (e.g., Redis or an in-memory dictionary) linking each Child Document ID to its corresponding Parent Document.
  4. During query time, perform vector search against the Child Documents.
  5. Once the top-scoring Child Documents are retrieved, retrieve their Parent Documents from the key-value store and pass those entire Parent Documents (or a larger window around the child) to the LLM context.

Mathematically, let D represent a parent document and c_i represent the child chunks generated from D such that:

D = \bigcup_{i=1}^{n} c_i

If the query is q, we compute the cosine similarity between the query embedding E(q) and child embeddings E(c_i):

\text{sim}(q, c_i) = \frac{E(q) \cdot E(c_i)}{\|E(q)\| \|E(c_i)\|}

Instead of sending the retrieved child chunk c^* = \text{argmax}_{c_i} \text{sim}(q, c_i) directly to the prompt, we map it back to D and feed D to the LLM.

Python Implementation using LangChain

Here is a step-by-step implementation of Parent Document Retrieval using LangChain:

from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.storage import InMemoryStore
from langchain.retrievers import ParentDocumentRetriever
from langchain_core.documents import Document

# 1. Initialize parent and child splitters
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=200)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=50)

# 2. Setup database and store
vectorstore = Chroma(embedding_function=OpenAIEmbeddings())
store = InMemoryStore()

# 3. Initialize retriever
retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)

# 4. Load dummy documents
docs = [
    Document(
        page_content="Large language models (LLMs) are deep learning models trained on massive text datasets. They can perform tasks like translation, summarization, and reasoning. Retrieval-Augmented Generation (RAG) is a technique that enhances LLMs by retrieving relevant context from external sources before generation. This reduces hallucinations.",
        metadata={"source": "llm_guide.txt"}
    )
]

# 5. Add documents to retriever
retriever.add_documents(docs, ids=None)

# 6. Retrieve relevant documents
query = "What is the primary benefit of RAG?"
retrieved_docs = retriever.invoke(query)
print("Retrieved parent document length:", len(retrieved_docs[0].page_content))

Key Considerations and Tuning Parameters

When implementing Parent Document Retrieval, several hyperparameters require careful tuning:

  1. Parent-to-Child Ratio: A parent chunk of 1500-2000 tokens with child chunks of 200-400 tokens is a solid starting baseline. If child chunks are too small, their embeddings may lose sentence-level semantics.
  2. Context Limits: If you retrieve multiple parent documents, you can easily exceed the context window or trigger the "lost in the middle" effect in the LLM. Applying a reranker (e.g., Cohere) or a context compressor before feeding to the LLM is highly recommended.
  3. Storage Strategy: Unlike vector databases which only house embeddings and child chunks, Parent Document Retrieval requires an external document store. In production, persistent databases like PostgreSQL (with JSONB) or Redis are preferred over in-memory key-value stores.

Conclusion

Parent Document Retrieval is one of the most effective advanced RAG patterns for production systems. Decoupling semantic search from context representation enables high retrieval precision without sacrificing the contextual depth necessary for accurate LLM generation.