Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Enhancement-overlay (C5) for RAG over long documents — the chunk-paradox resolution. Activate when a single fixed chunk size cannot satisfy both retrieval precision (small chunks) and generation context (large chunks): small chunks lose surrounding context, large chunks dilute embedding relevance into "topic averages". Encodes the core flip — decouple the embed-unit from the return-unit: embed small for retrieval precision, return large for synthesis context — and the SOP to pick a base chunk si
.claude/skills/agentsope-agentsop-multiscale-chunking/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 218% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 183% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 150% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 213% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 290% | 0% |
> Overlay on top of llamaindex]]. The base skill teaches the 5-layer RAG > pipeline and lists DecoupleChunkScope as one optimization knob among many. > This overlay zooms in on that single knob and turns it into a standalone > recipe: how to resolve the chunk paradox when one chunk size is provably > not enough. Third-person analytical view for an agent writing / reviewing > RAG ingestion code — not an end-user tutorial.
Activate this overlay when all three RAG preconditions hold and the chunk paradox has actually surfaced:
contracts, research papers, codebases — where a single answer-bearing fact sits inside a larger context that the LLM needs to interpret it.
precision but the LLM answers from fragments; large chunks (1024–2048) give rich context but recall on specific queries drops because the embedding becomes a "topic average". The official failure-mode checklist documents both poles as separate failures — #2 (wrong chunk from too-small) and #6 (context overflow / dilution from too-large) (cited in llamaindex]] R3).
chunk_size only moves the failure from one pole to the other.
Concrete triggers:
SentenceSplitter(chunk_size=4096) shipped as the fix for"incomplete answers" (this is anti-pattern A1 in llamaindex]]).
Do not activate when:
> Decouple the embed-unit from the return-unit. Embed small for retrieval > precision; return large for generation context.
The naive assumption is that the unit you index is the unit you feed the LLM. That single identity is the source of the paradox: it forces one chunk size to serve two opposing jobs.
NAIVE (one unit, two jobs) MULTI-SCALE (two units, one job each)
───────────────────────── ─────────────────────────────────────
[ chunk ] embed unit → small (precision job)
/ \ │
embed it feed it match
(wants (wants │
small) large) return unit → large (context job)
↓ ↓ ▲
CONFLICT — pick one, expand from
lose the other match → parent / windowThree load-bearing sub-principles:
Node carriesrelationships (PREV/NEXT/PARENT/CHILD). Those links are exactly what let you store a small node for matching and resolve it to a larger node for return (llamaindex]] Principle 2). Multi-scale chunking is "build a chunk-graph", not "split into chunks".
the small match expands into the large return:
(sentence-window). The expansion is positional.
(auto-merging / parent-child). The expansion is hierarchical.
horizontal. Documents with real structure (headings, sections, tables of contents) → vertical. This is the central dilemma case (§5.1).
The overlay's promise: this strictly dominates a compromise chunk size when the sweep frontier is non-flat — you no longer average two bad sizes.
A three-gate protocol. Do not skip Gate 0 — multi-scale chunking is only justified once a single chunk size has been proven insufficient.
Run the canonical sweep from llamaindex]] OP-02 before reaching for any multi-scale machinery:
pythonfrom llama_index.core.evaluation import ( FaithfulnessEvaluator, RelevancyEvaluator, ) # 1. ~20 eval QA pairs via DatasetGenerator.from_documents(docs) # 2. sweep: for cs in (128, 256, 512, 1024, 2048): # overlap = 0.1–0.2 × cs idx = build_index(docs, chunk_size=cs, overlap=int(0.15 * cs)) record(cs, faithfulness=eval_f(idx), relevancy=eval_r(idx), p95=latency(idx))
and stop. LlamaIndex's own Uber 10-K study peaked at 1024 for prose; code lands at 80–160 tokens (§5.2).
no single winner) → proceed to Gate 1. Do not compromise on a middle size.
| Document shape | Strategy | Geometry | |---|---|---| | Flat prose, no clear sectioning | Sentence-Window | horizontal | | Clear hierarchy (headings, sections, ToC) | Auto-Merging (Hierarchical / parent-child) | vertical | | Bursty multi-chunk relevance ("this whole section matters") | Auto-Merging | vertical | | Point-fact needing surrounding paragraph | Sentence-Window | horizontal | | Unknown structure / lowest setup cost | Start Sentence-Window | horizontal |
Set the embed-unit small (single sentence, or 128–256-token leaf) and the return-unit large (the window, or the parent/root chunk).
SentenceWindowNodeParser must be paired withMetadataReplacementPostProcessor — otherwise the metadata-stuffed matched sentence (not the window) reaches the LLM, defeating the entire point (§6 A2).
HierarchicalNodeParser builds leaf+parent nodes; store allleaves in the docstore and AutoMergingRetriever merges children → parent when ≥ threshold siblings match.
top-k on faithfulness; if neither does, the bottleneck is elsewhere (revert).
index metadata, exactly as Gate 0's chunk size would have been pinned.
Each operation: Trigger / Action / Output / Evidence. These refine llamaindex]] OP-02 and OP-05 into executable sub-steps.
chunk_size ∈ {128,256,512,1024,2048},overlap = 10–20%; build one VectorStoreIndex per config; record faithfulness + relevancy + p95 latency.
that authorizes multi-scale chunking.
llamaindex.ai/blog/evaluating-the-ideal-chunk-size-for-a-rag-system-using-llamaindex-6207e5d3fec5; llamaindex]] OP-02.SentenceWindowNodeParser(window_size=3) to embed singlesentences with N-neighbor windows in metadata; at query time apply MetadataReplacementPostProcessor(target_metadata_key="window") so the LLM receives the window, not the lone sentence.
developers.llamaindex.ai SentenceWindow / MetadataReplacement docs; llamaindex]] R3 Dilemma 5.HierarchicalNodeParser.from_defaults(chunk_sizes=[2048,512,128])to build a leaf→parent→root tree; index leaf nodes in a VectorStoreIndex, keep all nodes in a docstore; retrieve with AutoMergingRetriever, which returns the parent once a configured fraction of its children are in the hit set.
developers.llamaindex.ai/python/framework/integrations/retrievers/auto_merging_retriever/; llamaindex]] OP-05.structured/bursty → MSC-03; unknown → start MSC-02 (cheaper), escalate to MSC-03 if it underperforms on multi-chunk queries.
medium.com/@harsh_77214/beyond-naive-rag-comparing-basic-sentence-window-and-auto-merging-retrieval-....same object once the sweep is non-flat. Verify by inspecting what text the retriever actually sends to the synthesizer (must be the large unit).
strip/shorten metadata before shrinking chunks (GitHub #12200, #13792).
github.com/run-llama/llama_index/issues/12200, #13792; llamaindex]] A7.over the best single chunk size; if none, revert to the pinned single size.
困境: Both patterns implement the same core flip. They are not interchangeable — choosing wrong wastes setup cost and underperforms.
约束:
决策步骤:
结果: Both consistently beat naive top-k on faithfulness in published comparisons. Auto-Merging is more principled for structured docs; Sentence-Window is more robust for unstructured prose. The decision is driven by document structure, not theoretical elegance. (Source: llamaindex]] R3 Dilemma 5; developers.llamaindex.ai/.../auto_merging_retriever/; medium.com/@harsh_77214/beyond-naive-rag-comparing-basic-sentence-window-and-auto-merging-retrieval-...)
可提取的操作: MSC-02, MSC-03, MSC-04.
困境: At chunk_size=256 embeddings are precise but the LLM gets fragments; at chunk_size=2048 context is rich but the embedding becomes a "topic average" and recall on specific queries drops. Where to set chunk_size — and what to do when no single value wins?
约束:
#12200, #13792).决策步骤:
chunk_size ∈ {128,256,512,1024,2048}, overlap 10–20%.VectorStoreIndex per config; record faithfulness + relevancy + latency.结果: LlamaIndex's own published evaluation on Uber's 10-K found faithfulness peaked at chunk_size 1024 and relevancy maxed at 1024, with only mild latency growth — so 1024 became the framework default for prose (code lands at 80–160 tokens). But on corpora where the curve does not converge, the multi-scale decoupling pattern wins; never average two bad chunk sizes into one mediocre one. (Source: llamaindex]] R3 Dilemma 1; llamaindex.ai/blog/evaluating-the-ideal-chunk-size-for-a-rag-system-using-llamaindex-6207e5d3fec5; statsig.com/perspectives/llamaindex-rag-retrieval)
可提取的操作: MSC-01, MSC-05, MSC-06.
| # | Anti-pattern | Correct move | |---|---|---| | A1 | Bump chunk_size (e.g. → 4096) when answers feel incomplete | Decouple embed-scope from return-scope (MSC-02/03); do not enlarge the embed-unit | | A2 | Use SentenceWindowNodeParser without MetadataReplacementPostProcessor | Always pair them — else the lone sentence, not the window, reaches the LLM | | A3 | Index parent nodes in the vector store for auto-merging | Index leaf nodes; keep parents in the docstore for merge-on-retrieval | | A4 | Pick a single compromise chunk size on a non-flat frontier | Refuse the compromise; switch to multi-scale (MSC-05) | | A5 | Reach for multi-scale chunking on a short/static corpus | Prompt-stuff with caching; multi-scale is over-engineering (B1) | | A6 | Ship multi-scale config without re-running the eval set | MSC-07: measure the lift or revert | | A7 | Shrink the embed-unit while metadata still dominates the payload | MSC-06: budget metadata <50% before shrinking |
chunk paradox does not arise. (Mirrors llamaindex]] B1.)
it and stop; multi-scale adds complexity with no payoff.
model, the reranker, or the synthesizer (lost-in-the-middle), fix that first — multi-scale chunking only resolves the precision-vs-context axis.
and window expansion add latency; a raw vector store may be the right tool.
SentenceSplitter(chunk_size=4096) introduced as a fix for "incomplete answers" → A1.SentenceWindowNodeParser present but no MetadataReplacementPostProcessor in the query engine → A2.AutoMergingRetriever over an index built from parent nodes (no leaf docstore) → A3.The "embed small, return large" pattern is framework-agnostic; the primitives differ.
| Concept | LlamaIndex | LangChain | Notes | |---|---|---|---| | Horizontal (sentence-window) | SentenceWindowNodeParser + MetadataReplacementPostProcessor | (no direct equivalent; emulate with custom retriever returning neighbor windows) | LlamaIndex's is the cleanest first-class implementation | | Vertical (parent-child / auto-merging) | HierarchicalNodeParser + AutoMergingRetriever | ParentDocumentRetriever (child splitter + parent splitter + docstore) | Same idea: embed children, return parents | | Small-embed unit store | VectorStoreIndex over leaf nodes | child vectorstore | both index the small unit | | Large-return unit store | docstore (nodes with PARENT/CHILD relationships) | InMemoryStore / byte-store for parent docs | the return-unit lives outside the vector index |
Mapping rule: LlamaIndex AutoMergingRetriever/HierarchicalNodeParser ≈ LangChain ParentDocumentRetriever. LlamaIndex additionally offers the horizontal SentenceWindowNodeParser, which LangChain has no first-class analogue for. For a coder agent already inside the LlamaIndex stack, prefer the native parsers; the llamaindex]] base skill governs the surrounding pipeline (ingestion, eval loop, reranking, routing).
> This overlay does not replace llamaindex]] — it deepens the single > DecoupleChunkScope knob into a full recipe. For everything around it > (baseline, eval, hybrid, rerank, routing, production hardening), defer to the > base skill.
references/R1-source-evidence.md — citations and provenance for every claim above.intermediate/operation_candidates.json — machine-readable MSC operation list.SKILL.md + references/R3-dilemma-cases.md Dilemmas 1 & 5).llamaindex.ai/blog/evaluating-the-ideal-chunk-size-for-a-rag-system-using-llamaindex-6207e5d3fec5 (Uber 10-K, 1024 optimum)developers.llamaindex.ai/python/framework/integrations/retrievers/auto_merging_retriever/developers.llamaindex.ai SentenceWindowNodeParser / MetadataReplacementPostProcessor / HierarchicalNodeParser docsdevelopers.llamaindex.ai/python/framework/optimizing/rag_failure_mode_checklist/ (failures #2, #6)medium.com/@harsh_77214/beyond-naive-rag-comparing-basic-sentence-window-and-auto-merging-retrieval-with-llamaindex-f778173bed98statsig.com/perspectives/llamaindex-rag-retrieval (code chunk size 80–160)github.com/run-llama/llama_index/issues/12200, #13792 (metadata-dominates-chunk)ParentDocumentRetriever docs (python.langchain.com/docs/how_to/parent_document_retriever/)| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 14,493 | 15,282 | +5% | 1 | 1 | 0% | 2,266 | 7,207 | +218% | 0 | 0 | — |
case-02 | fail→pass | 15,615 | 9,063 | -42% | 1 | 1 | 0% | 2,403 | 6,808 | +183% | 0 | 0 | — |
case-03 | pass→pass | 17,052 | 15,342 | -10% | 1 | 1 | 0% | 2,589 | 7,668 | +196% | 0 | 0 | — |
case-04 | pass→pass | 14,604 | 15,167 | +4% | 1 | 1 | 0% | 2,338 | 7,820 | +234% | 0 | 0 | — |
case-05 | pass→pass | 8,237 | 8,376 | +2% | 1 | 1 | 0% | 1,538 | 6,836 | +344% | 0 | 0 | — |
case-06 | pass→pass | 12,471 | 10,958 | -12% | 1 | 1 | 0% | 1,979 | 7,209 | +264% | 0 | 0 | — |
case-07 | fail→pass | 17,633 | 8,067 | -54% | 1 | 1 | 0% | 2,636 | 6,601 | +150% | 0 | 0 | — |
case-08 | fail→pass | 14,387 | 8,731 | -39% | 1 | 1 | 0% | 2,176 | 6,807 | +213% | 0 | 0 | — |
case-09 | pass→pass | 12,318 | 10,236 | -17% | 1 | 1 | 0% | 2,171 | 7,111 | +228% | 0 | 0 | — |
case-10 | pass→pass | 11,910 | 5,179 | -57% | 1 | 1 | 0% | 1,944 | 6,101 | +214% | 0 | 0 | — |
case-11 | pass→pass | 10,949 | 2,799 | -74% | 1 | 1 | 0% | 1,689 | 5,738 | +240% | 0 | 0 | — |
case-12 | fail→pass | 11,316 | 11,162 | -1% | 1 | 1 | 0% | 1,809 | 7,049 | +290% | 0 | 0 | — |
case-13 | fail→pass | 12,421 | 10,201 | -18% | 1 | 1 | 0% | 1,781 | 6,972 | +291% | 0 | 0 | — |
case-14 | pass→pass | 5,695 | 7,271 | +28% | 1 | 1 | 0% | 947 | 6,548 | +591% | 0 | 0 | — |
case-15 | pass→pass | 11,434 | 9,809 | -14% | 1 | 1 | 0% | 1,866 | 6,968 | +273% | 0 | 0 | — |
case-16 | pass→pass | 12,168 | 7,495 | -38% | 1 | 1 | 0% | 1,792 | 6,486 | +262% | 0 | 0 | — |
case-17 | pass→pass | 18,530 | 16,294 | -12% | 1 | 1 | 0% | 2,844 | 7,867 | +177% | 0 | 0 | — |
case-18 | pass→pass | 11,945 | 12,443 | +4% | 1 | 1 | 0% | 2,067 | 7,339 | +255% | 0 | 0 | — |
case-19 | pass→pass | 7,213 | 7,551 | +5% | 1 | 1 | 0% | 1,193 | 6,612 | +454% | 0 | 0 | — |
case-20 | pass→pass | 16,211 | 11,838 | -27% | 1 | 1 | 0% | 2,676 | 7,287 | +172% | 0 | 0 | — |
case-21 | pass→pass | 16,367 | 11,728 | -28% | 1 | 1 | 0% | 2,421 | 7,153 | +195% | 0 | 0 | — |
case-22 | pass→pass | 8,237 | 5,139 | -38% | 1 | 1 | 0% | 1,344 | 6,112 | +355% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.