Chunking Strategies for Retrieval
How fixed-size, recursive, semantic, structural, and parent-document chunking affect retrieval.
Chunking determines what the embedding model represents and what the retriever can return. A poor boundary can separate a fact from its context or combine unrelated sections into one vector. These errors occur before retrieval and cannot always be repaired by reranking.
Choose the retrieval unit
A chunk is the smallest unit committed to a vector index. It is both the retrieval unit returned for a query and the text summarized by one embedding vector. Retrieval benefits from precise chunks, while embedding quality benefits from enough context to form a coherent meaning.
- The retrieval atom wants to be small. Small chunks are precise: the returned text contains the answer with little surrounding noise, which keeps the generator’s context window lean.
- The embedding payload wants to be coherent. An embedding is a single point summarizing the whole chunk; if the chunk spans five unrelated topics, the average lands nowhere in particular, and the chunk fails to be retrieved for any of those topics.
Chunking is the policy that produces these units from source documents. Reranking and query rewriting can improve ordering, but neither can reconstruct context discarded at index time.
Chunking methods
The main methods differ in the source structure they preserve and their indexing cost.
1. Fixed-size, character or token
Slice the document every N characters or every N tokens. Optionally overlap each chunk with the previous by some fraction (10–20% is conventional). This is the baseline; fast to compute, deterministic, oblivious to structure. It will cut sentences in half, split a code function across two chunks, and orphan headers from their bodies. Use it only when you genuinely don’t know anything about the input format.
2. Recursive character splitting
The default workhorse of production RAG. Given an ordered list of separators (["\n\n", "\n", ". ", " ", ""]), the splitter tries to keep chunks under a target size by splitting on the strongest separator first, then falling back to weaker ones only when a section is still too large. The effect is that paragraph boundaries are preserved when possible, sentence boundaries when not, and word boundaries as a last resort. LangChain’s RecursiveCharacterTextSplitter is the reference implementation; LlamaIndex’s SentenceSplitter does something similar with a sentence-tokenizer twist.
3. Structural chunking
Use the document’s own structure. For markdown: split on headers and treat each section as a chunk, carrying the header path forward in the metadata so retrieval can return “Section 4.2.1 > Tuning ef_search.” For HTML: split on block-level elements. For source code: use an AST parser (Python’s ast, tree-sitter for any language) to split by function or class. For JSON/XML: split by object boundary. This is the highest-fidelity option when the input has structure that maps to retrieval intent.
4. Semantic chunking
Embed every sentence, walk through the document, and start a new chunk whenever cosine similarity between consecutive sentences drops below a threshold. The intuition is that semantic boundaries are real, even when no markup signals them. The cost is that you pay one embedding call per sentence just to chunk the corpus, before you embed anything for retrieval. Worth it for unstructured prose where structural signals are absent; usually overkill when good markup exists. LlamaIndex’s SemanticSplitterNodeParser is the reference implementation.
5. Parent-document and sentence-window retrieval
Decouple the embedding payload from the returned payload. Parent-document retrieval embeds small child chunks (say, 256 tokens each), indexes them, but stores a parent_id pointing back to a larger parent chunk (1–2k tokens). At query time the top-k child chunks are retrieved, deduplicated to their parents, and the parents are what get injected into the prompt. Sentence-window retrieval is a variant: embed each sentence, and at retrieval time return a window of ±N sentences around the match.
Both patterns are the right answer more often than people realize, because they collapse the retrieval-atom-versus-embedding-payload tension into something the user explicitly controls.
Contextual retrieval
A 2024 Anthropic post on contextual retrieval demonstrated that prefixing each chunk with a short LLM-generated description of where the chunk sits in the source document (50–100 tokens) reduces retrieval failures by 35–49% before any reranking. The method adds one model call per chunk at index time and none at query time. It suits small, static corpora where the retrieval gain justifies that cost.
Code: from raw to recursive to semantic
A fixed-size chunker makes the basic contract explicit:
| |
| |
Now the recursive splitter, which is what 80% of production systems should actually use. The LangChain Python package and the LangChain JS package keep the API in step.
| |
| |
The Python version measures chunk size in tokens through from_tiktoken_encoder; the JavaScript version defaults to characters and needs a token counter when precise budgets matter. The separators list defines the cut hierarchy; for markdown corpora, prepend ["\n# ", "\n## ", "\n### "] to keep section headers intact.
For semantic chunking, the basic algorithm is small enough to write directly. Embed each sentence, take a rolling similarity, cut when the similarity to the previous sentence drops below a percentile threshold.
| |
The TypeScript shape is identical with the OpenAI Node SDK; the only meaningful difference is that you’ll want a real sentence tokenizer (compromise or wink-nlp) instead of a regex. For production semantic chunking on real corpora, lean on LlamaIndex’s SemanticSplitterNodeParser rather than rolling your own; there are edge cases (heading-only sentences, very short paragraphs, language detection) that the libraries already handle.
Failure modes to test
Chunk size and the generator are coupled. A 2k-token chunk feels generous until you remember that top-k retrieval means k of them go into the prompt. With k=5 and a 2k chunk size you’ve spent 10k tokens before the system prompt or query. Pick chunk size knowing how many chunks the generator will see and how much headroom you need for instructions and the answer.
Embedding providers may silently truncate input. Every embedding model has a context limit (text-embedding-3-small is 8191 tokens). Chunks that exceed it are silently truncated by the provider; you get an embedding for a different document than the one you stored. Always count tokens before sending, and assert the count against the model’s limit.
Facts can straddle a chunk boundary. “The deployment failed at 03:14 UTC because the readiness probe returned 503.” If the timestamp and the cause end up in different chunks, both chunks individually answer “what happened” badly. Overlap reduces this, but only papers over the symptom. The direct fixes are recursive or structural splitting (keep semantic units intact) and parent-document retrieval (embed small, return the whole surrounding section).
Overlap inflates everything. 20% overlap means 20% more chunks, 20% more embedding cost, 20% more index storage, and a near-guarantee of duplicate hits in top-k results that you’ll need to dedupe in the generator step. It’s a real trade, not a free win.
Small chunks make multi-hop reasoning worse, not better. Multi-hop questions require the model to combine information that lives in two or more places. Smaller chunks scatter that information across more retrieval candidates, increasing the chance that at least one needed hop is missed in top-k. Larger chunks (or parent-document retrieval) keep co-occurring facts co-located.
Structural chunking is fragile to upstream changes. A markdown splitter that relies on ## headers breaks the day someone writes a doc with Bold Title instead. AST-based code splitters break on parse errors. Both modes degrade gracefully if you chain back to a recursive splitter as the fallback path; both fail silently if you don’t.
Semantic chunking is O(N) embeddings before you’ve embedded anything for retrieval. On a corpus of millions of sentences this is the dominant cost. Reserve semantic chunking for high-value, low-volume corpora, or use it only on the long-tail documents that recursive splitting handles badly.
Chunking decisions are not portable across embedding models. A chunk size tuned for an 8191-token asymmetric retrieval model may not suit a 512-token symmetric sentence model. Re-tune model changes against a labeled retrieval eval set.
Further reading
- Anthropic: Introducing Contextual Retrieval: the 2024 post showing that LLM-generated chunk-level context prefixes cut retrieval failures by 35–49%. The methodology and the eval setup are both worth studying; the technique composes cleanly with any chunker.
- Pinecone: Chunking Strategies for LLM Applications: a thorough walk-through of fixed-size, recursive, structural, and document-specific chunking with practical heuristics and code.
- Greg Kamradt: 5 Levels of Text Splitting: the most-cited practitioner notebook on the subject, from fixed-size up to agentic chunking, with live LangChain and LlamaIndex demos.
- LangChain: Text Splitters Conceptual Guide: the framework-side reference for choosing among character, token, semantic, structural, and code-aware splitters.