Overview
Auto-Merging Retrieval detects when multiple small child chunks from the same parent section are all retrieved for the same query, then replaces them with the full parent chunk. This produces richer, coherent context instead of a collection of fragmented snippets.
Pattern #23 (Parent-Child RAG) in the 37 RAG Patterns Collection
flowchart TD
DOC --> L1["Leaf chunks ~128 chars — indexed"]
L1 --> L2["Parent chunks ~512 chars — stored"]
L2 --> L3["Root chunks ~1024 chars — stored"]
Q[Query] --> SR[Search leaf chunks top-K]
SR --> AM{"Count: how many retrieved leaves share the same parent?"}
AM --> |majority from same parent| MG[Merge: use parent chunk instead of leaves]
AM --> |leaves from different parents| KL[Keep individual leaves]
MG & KL --> LLM[Generate — richer context when content concentrates]The Problem
Retrieving many small overlapping chunks from the same document section creates fragmented, redundant context:
Query: "What are the side effects of Drug X?"
Retrieved (5 × 100-token child chunks from Section 3):
Chunk 3a: "...nausea was reported in 12% of patients..."
Chunk 3b: "...nausea and headache were the most common..."
Chunk 3c: "...headache affected 8% of trial participants..."
Chunk 3d: "...dizziness was observed in rare cases..."
Chunk 3e: "...rare cases included dizziness and fatigue..."
Problems:
- Overlapping content wastes ~40% of the token budget on repetition
- Sentence fragments lose inter-sentence reasoning chains
- The LLM sees shards instead of the coherent paragraph it needs
Solution
When a sufficient number of child chunks from the same parent are retrieved, swap them out for the full parent:
Retrieve child chunks
↓
Group by parent_id
↓
For each group:
count(children) >= M?
├── YES → fetch full parent chunk (coherent, ~400 tokens)
└── NO → keep individual children (precise, ~100 tokens each)
↓
Send merged context to LLM
The merge threshold M is tunable: lower M merges aggressively (more coherence), higher M stays granular (more precision).
Implementation
1. Index Structure with Parent-Child Linking
def build_hierarchical_index(documents, vectorstore, docstore,
child_size=100, parent_size=400):
"""
Index child chunks into the vector store for retrieval.
Store parent chunks in a separate docstore for lookup.
Each child carries a parent_id reference.
"""
for doc in documents:
# Split into parent chunks
parents = split_text(doc["content"], chunk_size=parent_size)
for p_idx, parent_text in enumerate(parents):
parent_id = f"{doc['id']}::parent::{p_idx}"
docstore.store(parent_id, {"content": parent_text, "source": doc["id"]})
# Split each parent into children
children = split_text(parent_text, chunk_size=child_size)
for c_idx, child_text in enumerate(children):
child_id = f"{parent_id}::child::{c_idx}"
vectorstore.add({
"id": child_id,
"content": child_text,
"parent_id": parent_id,
})2. Auto-Merge Logic
from collections import defaultdict
MERGE_THRESHOLD = 2 # merge when >= 2 children from same parent are retrieved
def auto_merge_results(retrieved_children, docstore, threshold=MERGE_THRESHOLD):
"""
Replace groups of children with their parent when count >= threshold.
"""
# Group retrieved children by their parent
groups = defaultdict(list)
for child in retrieved_children:
groups[child["parent_id"]].append(child)
merged_context = []
for parent_id, children in groups.items():
if len(children) >= threshold:
# Enough children hit — fetch the coherent parent
parent = docstore.get(parent_id)
merged_context.append({
"content": parent["content"],
"source": parent_id,
"merge_type": "parent",
"children_merged": len(children),
})
else:
# Too few children — keep them as retrieved
merged_context.extend([
{**c, "merge_type": "child"}
for c in children
])
return merged_context3. Full Pipeline
def auto_merging_rag(query, vectorstore, docstore, reranker, llm,
top_k=10, merge_threshold=2):
# Step 1: Retrieve child chunks by similarity
children = vectorstore.search(query, top_k=top_k)
# Step 2: Apply auto-merge — replace qualifying groups with parents
context_docs = auto_merge_results(children, docstore, merge_threshold)
# Step 3: Optionally rerank the merged context
reranked = reranker.rerank(query, context_docs, top_k=5)
# Step 4: Generate
context = "\n\n".join([d["content"] for d in reranked])
response = llm.generate(
f"Context: {context}\n\nQuestion: {query}"
)
merged_count = sum(1 for d in context_docs if d["merge_type"] == "parent")
return response, {
"children_retrieved": len(children),
"after_merge": len(context_docs),
"parents_substituted": merged_count,
"final_context": len(reranked),
}Complete Notebook
Run the full implementation:
Includes:
- ▸Hierarchical index construction with parent-child linking
- ▸Auto-merge threshold tuning experiments
- ▸Coherence scoring comparisons
- ▸LlamaIndex vs. manual implementation notes
Performance
| Metric | Auto-Merging | Raw Children |
|---|---|---|
| Coherence score | 0.91 | 0.73 |
| Answer completeness | 85% | 68% |
| Token efficiency | High (fewer repeats) | Low (overlapping fragments) |
| Latency overhead | ~50ms (docstore lookup) | Baseline |
Auto-merging improves coherence score from 0.73 to 0.91 — a 25% gain — with minimal latency cost.
When to Use
Best For
- ▸Long-form documents — books, technical reports, legal contracts where section coherence matters
- ▸Dense reference material — API docs, manuals where surrounding context aids comprehension
- ▸Narrative text — articles, case studies where argument flow spans multiple sentences
- ▸Reducing redundancy — any corpus where small chunks frequently overlap
Combine With
- ▸Parent Document Retrieval (#23) — the underlying architecture that auto-merging builds on; use it when you always want the parent regardless of child count
- ▸Reranking (#4) — apply cross-encoder reranking after the merge decision to select the best final context chunks
- ▸Contextual Compression (#6) — if merged parents are still too large, compress them after merging
Related Papers
- ▸LlamaIndex Auto-Merging Retriever: LlamaIndex Documentation (2023)
- ▸Small-to-Big Retrieval: Small-to-Big: Better RAG with Hierarchical Retrieval (2023)
Next Steps
- ▸Try it: Run the Parent-Child RAG notebook
- ▸Tune: Experiment with merge threshold M — start at 2, adjust based on your document structure
- ▸Combine: Add Reranking (#4) after the merge step to tighten the final context
- ▸Compare: Measure coherence scores with and without merging on your own corpus
Part of: 37 RAG Patterns Collection See also: Complete Pattern Index