Welcome to your first lesson in our course on advanced Retrieval-Augmented Generation. Given your experience building RAG pipelines, we'll dive directly into sophisticated techniques that address the common failure points of simpler approaches.
This lesson focuses on a foundational challenge in RAG: document chunking. We'll analyze why the straightforward method of splitting documents into fixed-size pieces often falls short and then implement a more robust solution, the Parent-Document Retriever, using LangChain. This will be our first step toward building production-grade retrieval systems that are both precise and context-aware.
The Trade-Off in Naive Chunking
You're already familiar with the basic RAG workflow: load documents, split them into chunks, embed them, and store them in a vector database for retrieval. The most critical and deceptively complex step here is the splitting, or chunking. The size of your chunks presents a fundamental trade-off:
- Small Chunks: These are great for creating precise, specific embeddings. A small chunk about a single concept (e.g., a specific function's return value in an API document) will have a very distinct vector representation. This leads to high accuracy during similarity search. However, the retrieved chunk may lack the surrounding context needed for the LLM to generate a comprehensive answer.
- Large Chunks: These retain broad context, which is excellent for the LLM's generation step. The problem is that their embeddings become "diluted" or "washy." A large chunk covering multiple topics will have a vector that represents the average of those topics, making it less likely to be a top match for a very specific query.
This tension is a primary failure mode in basic RAG. A crucial piece of information might be split across two chunks, meaning neither chunk contains the full answer and the system fails to retrieve it.

The following article section and video explain this dilemma clearly.
RAG IX — Parent Document Retriever | by DhanushKumar
This article clearly outlines the challenge of balancing embedding accuracy with context retention.
Please read the section titled What Problem Does This Solve?, focusing on the two key challenges described: Embedding Accuracy vs. Context Retention and Efficient Retrieval.
Advanced RAG 02 - Parent Document Retriever
This video offers a great visual and conceptual breakdown of why standard chunking can be problematic.
Watch the segment from the start of the explanation to understand how embedding specificity changes with chunk size. The speaker uses the term "washy" to describe the diluted embeddings of large chunks.
The Solution: The Parent-Document Retriever
To get the best of both worlds—precise search and contextual richness—we can use a strategy called the Parent-Document Retriever (PDR). It's a form of multi-vector retrieval where we index our data in one way but use it to retrieve data in another.
The core mechanism is as follows:
- Splitting: Each original document is first split into larger "parent" chunks. Then, each parent chunk is further split into smaller "child" chunks.
- Indexing: Only the child chunks are embedded and stored in the vector database. Their small size ensures the embeddings are specific and semantically precise.
- Storage: The parent chunks are stored in a separate, simple key-value document store (
docstore). A reference to the parent chunk's ID is stored in the metadata of each child chunk. - Retrieval: When a query comes in, the system performs a similarity search against the small, specific child chunks.
- Contextualization: Instead of returning the retrieved child chunks, the system uses their metadata to look up and return their corresponding parent chunks from the docstore.
This way, we leverage the precision of small-chunk embeddings for the search, but provide the LLM with the rich, surrounding context from the larger parent chunks for generation.

This video segment provides an excellent conceptual animation of this process.
Advanced RAG 02 - Parent Document Retriever
This clip visually explains the PDR mechanism.
Watch from the introduction of the PDR idea. The diagram illustrates how you search over child documents but pass the parent document back to the LLM.
Implementing the Parent-Document Retriever in LangChain
Your experience with Python, LangChain, and system design will make this implementation straightforward. We'll use the components you're already familiar with (TextSplitter, VectorStore) and introduce the ParentDocumentRetriever and InMemoryStore.
The following resource provides a complete, hands-on code walkthrough. We will follow its structure to build our retriever.
Parent Document Retriever | Full Stack Retrieval
This guide offers a clear, step-by-step implementation of the Parent Document Retriever in LangChain, from loading data to integrating it into a full RAG chain.
I recommend reading through the entire code walkthrough, from the beginning to the end. As you read, pay close attention to the following key steps, which I've summarized for you below.
Let's break down the implementation process based on the guide above.
1. Define Parent and Child Splitters
First, you need two instances of a text splitter, typically RecursiveCharacterTextSplitter.
- The
child_splitteris configured with a smallchunk_size(e.g., 400 characters) to create the small documents for embedding. - The
parent_splitteris configured with a largerchunk_size(e.g., 2000 characters) to create the context-rich documents that will be passed to the LLM.
from langchain.text_splitter import RecursiveCharacterTextSplitter
# This text splitter is used to create the child documents
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
# This text splitter is used to create the parent documents
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
2. Set Up the Vector Store and Document Store
Next, you need two storage components:
- A vector store (e.g.,
Chroma,FAISS) to hold the embeddings of the child documents. This is the component that will be searched during retrieval. - A document store (
docstore) to hold the parent documents. LangChain provides a simpleInMemoryStorefor this, which acts as a basic Python dictionary. We'll discuss more robust, production-ready stores shortly.
from langchain_community.vectorstores import Chroma
from langchain.storage import InMemoryStore
from langchain_openai import OpenAIEmbeddings
# The vectorstore to use to index the child chunks
vectorstore = Chroma(
collection_name="split_parents",
embedding_function=OpenAIEmbeddings()
)
# The storage layer for the parent documents
store = InMemoryStore()
3. Instantiate and Use the Retriever
Finally, you instantiate the ParentDocumentRetriever. You pass it the stores and splitters you just configured.
from langchain.retrievers import ParentDocumentRetriever
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter, # Optional, if you want to split parents from original docs
)
When you call retriever.add_documents(docs), it automates the entire indexing workflow:
- Splits the original documents using
parent_splitter. - Saves the resulting parent chunks to the
docstore. - Further splits the parent chunks using
child_splitter. - Embeds these child chunks and adds them to the
vectorstore, including metadata that links each child to its parent.
When you call retriever.invoke("your query"), it finds the relevant child documents in the vectorstore but returns the corresponding full parent documents from the docstore.
From In-Memory to Production: Persistent Document Stores
The InMemoryStore is convenient for notebooks and prototypes, but it's unsuitable for production. Since your goal is to learn production-level techniques, it's crucial to consider persistence. If your application restarts, an in-memory store loses all its data, forcing a costly re-indexing process.
For a production system, you would replace InMemoryStore with a persistent key-value store like Redis, or even a relational database like PostgreSQL. To do this, you would create a custom class that inherits from LangChain's BaseStore and implements its methods (mget, mset, mdelete, yield_keys) to interact with your chosen database.
The following video provides an excellent deep dive into this exact process, showing how to build a custom Docstore using PostgreSQL. This is a powerful technique for building robust, scalable RAG systems.
LangChain - Parent-Document Retriever Deepdive with Custom PgVector Store
This video is a deep-dive into creating a custom, persistent docstore for the Parent-Document Retriever using pgvector and PostgreSQL. It's a perfect example of moving from a prototype to a production-ready component.
I highly recommend watching these segments: Conceptual Overview: Watch the introduction from the start for a recap of the problem PDR solves. In-Memory Implementation: The clip from this timestamp shows the basic in-memory setup, similar to what we just discussed. Building a Custom PostgreSQL Store: The core of the video is from this section. Pay close attention to how the BaseStore interface is implemented with methods like mget and mset that execute SQL queries. Integration and Verification: Finally, watch from the integration part to see how this custom store is plugged into the ParentDocumentRetriever and how the database tables are inspected to verify that parent and child documents are stored correctly.
This hands-on approach of implementing a custom store is directly aligned with the system design skills you've developed and is a key step in operationalizing advanced RAG.
Conclusion
In this lesson, we dissected a critical weakness of basic RAG pipelines and introduced a powerful solution. Here are the key takeaways:
- The Problem: Naive chunking creates a trade-off between the precision of small chunks and the context of large chunks, often leading to incomplete or irrelevant retrievals.
- The Solution: The Parent-Document Retriever resolves this by indexing small, specific "child" chunks for accurate searching but retrieving larger, context-rich "parent" chunks for the LLM.
- The Implementation: In LangChain, this involves using two splitters (
child_splitter,parent_splitter), aVectorStorefor the children, and aDocstorefor the parents. - Production-Readiness: For real-world applications, the default
InMemoryStoreshould be replaced with a custom, persistent document store (e.g., using PostgreSQL or Redis) to ensure scalability and data durability.
By mastering the Parent-Document Retriever, you've taken a significant step toward building more accurate and reliable RAG systems.
In our next lesson, we will explore the Multi-Vector Retriever. This is a generalization of the PDR concept, where we can index documents based on various alternative representations, such as LLM-generated summaries, to further enhance retrieval performance.