Parent-Document Retriever¶
📖 Overview¶
A Parent-Document Retriever solves an important RAG problem:
Small chunks are often better for retrieval, but larger parent documents are often better for understanding context.
Traditional RAG commonly follows:
Small chunks improve retrieval precision because they represent focused pieces of information.
However, returning only a small chunk can remove important surrounding context.
Parent-Document Retrieval separates these two concerns:
The basic idea is:
flowchart TD
A["Parent Document"] --> B["Child Chunking"]
B --> C["Child Chunk 1"]
B --> D["Child Chunk 2"]
B --> E["Child Chunk 3"]
C --> F["Embedding"]
D --> F
E --> F
F --> G["Vector Store"]
H["User Query"] --> I["Query Embedding"]
I --> G
G --> J["Matching Child Chunk"]
J --> K["Parent Document Lookup"]
K --> L["Parent Context"]
L --> M["LLM"]
This allows the system to retrieve with fine-grained precision while generating with richer context.
🎯 Learning Objectives¶
After completing this chapter, you will be able to:
- Understand Parent-Document Retrieval
- Understand the difference between parent and child documents
- Understand why small chunks can improve retrieval
- Understand why larger context can improve generation
- Design a Parent-Document Retriever
- Understand parent-child relationships
- Implement a basic Parent-Document Retriever
- Understand metadata propagation
- Understand document and chunk identifiers
- Understand storage requirements
- Combine Parent-Document Retrieval with vector search
- Understand its relationship with hierarchical retrieval
- Identify common implementation mistakes
- Apply Parent-Document Retrieval to enterprise RAG systems
1. The Core Problem¶
Consider a policy document:
Remote Work Policy
Section 1 — Eligibility
...
Section 2 — Working Arrangements
...
Section 3 — Office Attendance
...
Section 4 — Exceptions
...
Section 5 — Manager Responsibilities
...
Suppose the document is split into small chunks:
Chunk 1:
Employees are eligible for remote work...
Chunk 2:
Employees may work remotely up to three days...
Chunk 3:
Managers may approve additional remote work...
Chunk 4:
Exceptions may apply to specific roles...
A user asks:
The best matching chunk might be:
That is excellent for retrieval.
But perhaps the answer depends on information in:
Returning only Chunk 3 may lose important context.
2. Small Chunks vs Large Chunks¶
There is a fundamental trade-off.
Small chunks¶
Advantages
──────────
Focused semantic meaning
Better retrieval precision
Less irrelevant text
Smaller embeddings
But:
Disadvantages
─────────────
Less surrounding context
May split related information
Can lose references
Large chunks¶
But:
Disadvantages
─────────────
Less precise retrieval
More irrelevant information
Larger embedding representation
More context tokens
Parent-Document Retrieval attempts to combine the advantages.
3. Parent and Child Documents¶
The terminology is important.
Parent¶
The larger logical unit.
Examples:
Child¶
The smaller searchable unit.
Examples:
Relationship:
The child is indexed for retrieval.
The parent is returned as context.
4. Basic Architecture¶
flowchart LR
A["Original Document"] --> B["Parent Splitter"]
B --> C["Parent Documents"]
C --> D["Child Splitter"]
D --> E["Child Chunks"]
E --> F["Embedding Model"]
F --> G["Vector Store"]
C --> H["Parent Document Store"]
I["User Query"] --> J["Query Embedding"]
J --> G
G --> K["Matching Child Chunks"]
K --> L["Parent IDs"]
L --> H
H --> M["Parent Documents"]
M --> N["LLM"]
There are therefore two storage concerns:
5. Why Retrieve Children?¶
Suppose a parent document contains:
and the user asks:
Embedding the entire document may produce a broad representation.
Instead, the document can be divided:
The query can then identify:
as the best semantic match.
The system then maps:
and retrieves the larger parent context.
6. Parent-Child Mapping¶
Each child should maintain a reference to its parent.
For example:
{
"child_id": "chunk-047",
"parent_id": "policy-012",
"content": "Employees may work remotely...",
"metadata": {
"document_type": "policy",
"department": "HR"
}
}
The relationship is:
This identifier is essential for parent lookup.
7. Example¶
Suppose we have:
Parent:
remote-work-policy
Children:
remote-work-policy#001
remote-work-policy#002
remote-work-policy#003
remote-work-policy#004
The query:
retrieves:
The system then performs:
and returns the parent context.
8. Standard RAG vs Parent-Document RAG¶
Standard RAG¶
flowchart LR
A["Document"] --> B["Chunk"]
B --> C["Embedding"]
C --> D["Vector Store"]
E["Query"] --> D
D --> F["Chunk"]
F --> G["LLM"]
Parent-Document RAG¶
flowchart LR
A["Document"] --> B["Parent"]
B --> C["Children"]
C --> D["Embeddings"]
D --> E["Vector Store"]
B --> F["Parent Store"]
G["Query"] --> E
E --> H["Child"]
H --> I["Parent ID"]
I --> F
F --> J["Parent"]
J --> K["LLM"]
The key difference is:
Standard RAG:
Retrieve → Generate from chunk
Parent-Document RAG:
Retrieve child → Recover parent → Generate from parent
9. Parent Size and Child Size¶
The most important design decisions are:
For example:
This is only an example.
There is no universal optimal configuration.
The correct sizes depend on:
- Document structure
- Query type
- Embedding model
- LLM context window
- Retrieval quality
- Document semantics
- Expected answer complexity
10. Hierarchical Chunking¶
A document can be represented hierarchically:
Document
│
├── Section
│ ├── Child Chunk
│ ├── Child Chunk
│ └── Child Chunk
│
├── Section
│ ├── Child Chunk
│ └── Child Chunk
│
└── Section
├── Child Chunk
└── Child Chunk
This creates a hierarchy:
Parent-Document Retrieval is therefore closely related to hierarchical retrieval, although the implementation can be simpler.
11. LangChain Implementation¶
LangChain provides a ParentDocumentRetriever abstraction.
A simplified example:
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain_chroma import Chroma
vectorstore = Chroma(
collection_name="child_chunks",
embedding_function=embeddings
)
store = InMemoryStore()
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter
)
Documents can then be added:
retriever.add_documents(documents)
results = retriever.invoke(
"How many days can employees work remotely?"
)
The conceptual implementation is:
Original Documents
↓
Parent Splitter
↓
Parent Documents
↓
Child Splitter
↓
Child Chunks
↓
Embedding
↓
Vector Store
At query time:
12. Parent Splitter¶
The parent splitter defines the larger retrieval context.
Example:
from langchain_text_splitters import RecursiveCharacterTextSplitter
parent_splitter = RecursiveCharacterTextSplitter(
chunk_size=2000,
chunk_overlap=200
)
This might produce:
The parent is not necessarily stored in the vector database.
It can be stored in a separate document store.
13. Child Splitter¶
The child splitter creates smaller searchable units.
The relationship becomes:
The child chunks are what the vector search indexes.
14. Why Two Splitters?¶
Using separate parent and child splitters provides control over two different objectives.
Parent splitter¶
Optimizes:
Child splitter¶
Optimizes:
Therefore:
This is one of the most important ideas in Parent-Document Retrieval.
15. Parent Document Store¶
The vector store and parent store serve different purposes.
Vector Store
────────────
Child embeddings
Child content
Child metadata
Parent Store
────────────
Parent content
Parent metadata
Architecture:
flowchart LR
A["Child Chunk"] --> B["Embedding"]
B --> C["Vector Store"]
D["Parent Document"] --> E["Parent Store"]
C --> F["Child Match"]
F --> G["Parent ID"]
G --> E
E --> H["Parent Context"]
A production system may use:
depending on the application's requirements.
16. Metadata Propagation¶
Metadata should generally be associated with both parent and child representations.
Example:
Parent¶
{
"parent_id": "policy-001",
"department": "HR",
"country": "Germany",
"document_type": "policy",
"year": 2026
}
Child¶
{
"child_id": "policy-001-chunk-04",
"parent_id": "policy-001",
"department": "HR",
"country": "Germany",
"document_type": "policy",
"year": 2026
}
This allows the child retrieval layer to apply metadata filters while still resolving the parent.
17. Parent-Document Retrieval with Metadata¶
Suppose the query is:
The retrieval pipeline can be:
This combines the concepts from the previous chapter:
A more advanced architecture becomes:
flowchart TD
A["Natural Language Query"] --> B["Self-Query Analyzer"]
B --> C["Semantic Query"]
B --> D["Metadata Filters"]
C --> E["Vector Search"]
D --> E
E --> F["Child Chunks"]
F --> G["Parent IDs"]
G --> H["Parent Store"]
H --> I["Parent Context"]
18. Multiple Child Matches¶
A query may match multiple children belonging to the same parent.
For example:
If the application simply retrieves parents, it may end up with:
The system should deduplicate parent IDs:
This is an important implementation detail.
19. Parent Expansion¶
Another design choice is how much parent context to return.
Full parent¶
Partial parent¶
Neighbor expansion¶
The appropriate approach depends on document structure.
20. Parent Retrieval Does Not Always Mean "Entire Document"¶
A common misconception is:
This is not necessary.
A parent could be:
or:
or:
or:
or:
A better definition is:
A parent is the larger logical context associated with the retrieved child unit.
21. Example: Technical Documentation¶
Suppose a technical manual contains:
Kubernetes Deployment Guide
Chapter 1 — Architecture
Chapter 2 — Cluster Setup
Chapter 3 — Networking
Chapter 4 — Security
Chapter 5 — Monitoring
Each chapter can become a parent:
The query:
might retrieve:
The system can then return:
instead of only the single chunk.
This can preserve definitions and related explanations.
22. Example: Enterprise Policy¶
Suppose:
Parent:
Remote Work Policy — Section 4
Children:
4.1 Eligibility
4.2 Approval
4.3 Exceptions
4.4 Manager Responsibilities
The query:
might match:
The parent section can provide:
This may provide the LLM with enough surrounding context to generate a more reliable answer.
23. Parent-Document Retrieval vs Larger Chunks¶
An obvious alternative is:
For example:
This can work, but it introduces a trade-off:
Parent-Document Retrieval separates:
This is its central architectural advantage.
24. Retrieval Granularity vs Generation Granularity¶
The concept can be visualized as:
flowchart LR
A["Document"] --> B["Large Parent"]
B --> C["Small Child 1"]
B --> D["Small Child 2"]
B --> E["Small Child 3"]
C --> F["Retrieval"]
D --> F
E --> F
F --> G["Parent Context"]
G --> H["Generation"]
Therefore:
This separation is one of the most useful patterns for enterprise RAG.
25. Parent-Document Retriever vs Multi-Query Retriever¶
These techniques solve different problems.
| Technique | Main Problem |
|---|---|
| VectorStore Retriever | Basic semantic retrieval |
| Multi-Query Retriever | Multiple query perspectives |
| Self-Query Retriever | Metadata-aware retrieval |
| Parent-Document Retriever | Retrieval precision vs context completeness |
Multi-Query¶
Parent-Document¶
They can be combined.
26. Parent-Document Retriever with Multi-Query¶
A more advanced architecture:
flowchart TD
A["User Query"] --> B["Multi-Query Generator"]
B --> C["Query 1"]
B --> D["Query 2"]
B --> E["Query 3"]
C --> F["Child Vector Search"]
D --> F
E --> F
F --> G["Child Candidates"]
G --> H["Parent ID Resolution"]
H --> I["Parent Deduplication"]
I --> J["Parent Documents"]
J --> K["Context Selection"]
K --> L["LLM"]
This combines:
27. Parent-Document Retriever with Re-ranking¶
Another powerful architecture is:
Query
↓
Child Retrieval
↓
Candidate Children
↓
Re-ranking
↓
Top Child Matches
↓
Parent Resolution
↓
Parent Context
Alternatively:
Which approach is better depends on:
- Number of candidate children
- Parent size
- Ranking model
- Retrieval latency
- Application requirements
This is a production architecture decision rather than a universal rule.
28. Storage Architecture¶
A production implementation may look like:
flowchart TD
A["Document Ingestion"] --> B["Parent Splitter"]
B --> C["Parent Store"]
B --> D["Child Splitter"]
D --> E["Embedding Model"]
E --> F["Vector Database"]
G["Query"] --> H["Query Embedding"]
H --> F
F --> I["Child Results"]
I --> J["Parent IDs"]
J --> C
C --> K["Parent Context"]
K --> L["RAG Pipeline"]
Possible storage technologies include:
Vector Database
↓
FAISS
Chroma
Milvus
pgvector
Managed Vector Databases
Parent Store
↓
Object Storage
Document Database
Key-Value Store
Relational Database
The exact combination depends on the production architecture.
29. Performance Considerations¶
Parent-Document Retrieval introduces an additional lookup.
This means the system may have:
For high-throughput systems, parent lookup should be designed carefully.
Potential optimizations include:
- Batch parent lookups
- Cache frequently accessed parents
- Store parent documents close to the retrieval service
- Use efficient parent identifiers
- Avoid unnecessary parent retrieval
- Deduplicate parent IDs before lookup
30. Parent Deduplication¶
Suppose retrieval returns:
Instead of performing:
the application should first derive:
and then perform a batch lookup.
parent_ids = list({
document.metadata["parent_id"]
for document in child_documents
})
parents = parent_store.get_many(parent_ids)
This can significantly reduce unnecessary storage calls.
31. Context Explosion¶
Parent retrieval introduces another important risk.
Suppose:
and each child belongs to a different parent:
The context sent to the LLM may become too large.
Therefore:
is often better than:
Parent retrieval should therefore be followed by a context selection strategy when parent documents are large.
32. Parent Selection Strategies¶
Possible strategies include:
Strategy 1 — Return all unique parents¶
Simple but potentially expensive.
Strategy 2 — Limit parent count¶
Strategy 3 — Re-rank parents¶
Strategy 4 — Extract relevant sections¶
The right strategy depends on the application.
33. Metadata and Parent Context¶
Parent metadata can also be used to construct better prompts.
For example:
{
"document_title": "Remote Work Policy",
"section": "Exceptions",
"country": "Germany",
"version": "2026.1"
}
The context builder can create:
This improves traceability and can support citations later in the production RAG pipeline.
34. Framework-Agnostic Implementation¶
A simple abstraction can separate parent retrieval from the underlying storage technology.
class ParentDocumentStore:
def __init__(self, storage):
self.storage = storage
def get(self, parent_id):
return self.storage.get(parent_id)
def get_many(self, parent_ids):
return self.storage.get_many(parent_ids)
The retriever can then compose:
class ParentDocumentRetriever:
def __init__(
self,
child_retriever,
parent_store
):
self.child_retriever = child_retriever
self.parent_store = parent_store
def retrieve(self, query):
children = self.child_retriever.retrieve(query)
parent_ids = list({
child["metadata"]["parent_id"]
for child in children
})
return self.parent_store.get_many(parent_ids)
Architecture:
This keeps the architecture modular.
35. Testing Parent-Child Relationships¶
Parent-Document Retrieval should be tested during ingestion.
For each child:
must be valid.
A simple validation could be:
for child in child_documents:
assert child.metadata.get("parent_id")
parent = parent_store.get(
child.metadata["parent_id"]
)
assert parent is not None
This catches broken parent-child relationships before they reach production.
36. Common Mistakes¶
Mistake 1 — Making parents too large¶
Mistake 2 — Making children too small¶
Mistake 3 — Losing parent IDs¶
Without:
the retrieval system cannot recover the parent context.
Mistake 4 — Duplicating parents¶
Multiple children may map to the same parent.
Always deduplicate parent IDs.
Mistake 5 — Returning every parent¶
Retrieving 20 children could produce 20 large parents.
Use context selection when necessary.
Mistake 6 — Treating parent retrieval as a substitute for re-ranking¶
Parent retrieval solves:
Re-ranking solves:
They are complementary.
37. When Should You Use Parent-Document Retrieval?¶
It is particularly useful when:
- Documents contain logical sections
- Individual chunks are too small to answer questions independently
- Context from surrounding sections matters
- Retrieval precision benefits from smaller chunks
- Documents have strong parent-child structure
- Enterprise policies or technical manuals are being searched
Good examples include:
Policy Documents
Technical Manuals
Product Documentation
Legal Documents
Compliance Documents
Research Papers
Engineering Documentation
Enterprise Procedures
38. When It May Not Be Necessary¶
You may not need Parent-Document Retrieval when:
or:
or:
For example:
where each record is:
may not benefit significantly from parent expansion.
39. Decision Flow¶
flowchart TD
A["Do small chunks retrieve well?"] -->|No| B["Improve Chunking / Embeddings"]
A -->|Yes| C["Is surrounding context important?"]
C -->|No| D["Use Standard Retriever"]
C -->|Yes| E["Use Parent-Document Retrieval"]
E --> F["Are Parents Large?"]
F -->|Yes| G["Add Context Selection / Compression"]
F -->|No| H["Return Relevant Parents"]
40. Production RAG Pattern¶
A mature production architecture can combine the retrieval techniques covered so far:
flowchart TD
A["User Query"] --> B["Query Processing"]
B --> C["Multi-Query"]
B --> D["Self-Query"]
C --> E["Child Retrieval"]
D --> E
E --> F["Metadata Filtering"]
F --> G["Candidate Children"]
G --> H["Re-ranking"]
H --> I["Parent ID Resolution"]
I --> J["Parent Deduplication"]
J --> K["Parent Store"]
K --> L["Context Selection"]
L --> M["Prompt Assembly"]
M --> N["LLM"]
N --> O["Response Validation"]
O --> P["Enterprise Response"]
This demonstrates how retrieval techniques are composable capabilities, rather than isolated features.
41. Parent-Document Retrieval in an Enterprise Architecture¶
A production system can expose a generic interface:
Different implementations can then provide:
VectorStoreRetriever
MultiQueryRetriever
SelfQueryRetriever
ParentDocumentRetriever
HybridRetriever
RerankingRetriever
AgenticRetriever
The application does not need to know the internal retrieval strategy.
flowchart LR
A["RAG Application"] --> B["Retriever Interface"]
B --> C["VectorStore"]
B --> D["MultiQuery"]
B --> E["SelfQuery"]
B --> F["ParentDocument"]
B --> G["Hybrid"]
This is particularly useful for an enterprise AI platform where retrieval strategy may vary by use case.
42. Evaluation¶
Parent-Document Retrieval should be compared against a baseline.
Baseline¶
Parent-Document Retrieval¶
Measure:
| Metric | Baseline | Parent-Document |
|---|---|---|
| Retrieval Recall | Measure | Measure |
| Context Relevance | Measure | Measure |
| Answer Correctness | Measure | Measure |
| Faithfulness | Measure | Measure |
| Context Size | Measure | Measure |
| Latency | Measure | Measure |
| Token Usage | Measure | Measure |
| Cost | Measure | Measure |
The goal is not simply:
but:
43. Practical Example¶
Consider an enterprise compliance system.
Parent¶
Child chunks¶
Child 1:
Personal data must be retained...
Child 2:
Financial records must be retained...
Child 3:
Deletion requests must be processed...
Child 4:
Exceptions apply to legal holds...
User:
Retrieval:
The parent context may contain:
This can help the LLM distinguish the specific rule from its surrounding conditions.
44. The Central Design Principle¶
Parent-Document Retrieval can be summarized as:
or:
This separation is one of the most important patterns for advanced RAG.
45. Key Takeaways¶
- Parent-Document Retrieval separates retrieval granularity from generation context.
- Small child chunks are indexed for precise retrieval.
- Larger parent documents provide richer context to the LLM.
- Every child should maintain a reliable parent identifier.
- Parent and child splitters can use different chunk sizes.
- The parent does not have to be the entire original document.
- Parents can represent sections, chapters, policies, or other logical units.
- Vector stores typically hold searchable child chunks.
- Parent documents can be stored separately in a document or key-value store.
- Parent IDs should be deduplicated before parent lookup.
- Large parent documents can cause context explosion.
- Context selection or compression may be necessary after parent resolution.
- Metadata should be propagated appropriately between parent and child representations.
- Parent-Document Retrieval complements Multi-Query, Self-Query, Hybrid Search, and Re-ranking.
- It is particularly useful for structured enterprise documents where surrounding context matters.
- It is not always necessary for short, self-contained documents.
- Production implementations should measure retrieval quality, context relevance, latency, token usage, and cost.
- A framework-independent abstraction makes Parent-Document Retrieval easier to integrate into enterprise AI platforms.
The core pattern is:
DOCUMENT
│
↓
Parent Context
│
┌─────────┼─────────┐
↓ ↓ ↓
Child 1 Child 2 Child 3
│ │ │
└─────────┼─────────┘
↓
Vector Search
↓
Matching Child
↓
Parent ID
↓
Parent Context
↓
LLM
The goal is not to choose between small chunks and large context. Parent-Document Retrieval lets us use small chunks for retrieval and larger logical context for generation.
🧭 Chapter Navigation¶
Part V — Advanced Retrieval-Augmented Generation¶
Previous:
03. Self-Query Retriever
Next:
05. Retriever Comparison
Section:
01 — Core Retrieval Engineering
Retrieval Engineering Path¶
01 VectorStore Retriever
↓
02 Multi-Query Retriever
↓
03 Self-Query Retriever
↓
04 Parent-Document Retriever
↓
05 Retriever Comparison
Enterprise AI Engineering Handbook
Building Production-Grade Enterprise AI Systems — One Chapter at a Time.