All writing

The Hidden Chunking Variable Breaking Your RAG Pipeline

Most RAG pipelines fail before a single vector is queried. The embedding model gets blamed, the reranker gets added, the context window gets expanded — and the real culprit, sitting at the very front of the pipeline, goes untouched: chunking strategy.

Get chunking wrong and you're retrieving fragments that are semantically meaningless in isolation, or retrieving walls of text that dilute the signal for the LLM. Neither retrieval nor generation can compensate for that.

Why Chunking Isn't a "Set It and Forget It" Step

Most teams make a single decision — "we'll use 512 tokens with 50-token overlap" — copy it from a tutorial, and ship. That default works fine for clean, uniform prose. It degrades badly on everything else: PDFs with tables, API docs with code blocks, legal contracts with defined terms, multilingual content, conversational transcripts.

The reason is structural. A fixed-size chunker doesn't know that a table cell without its header row is meaningless. It doesn't know that a Python function split across two chunks loses its callable context. It doesn't know that a legal clause split at "provided, however" cuts off the only part that matters.

Chunking is a data modeling decision, not a preprocessing step. It shapes what your retriever can possibly surface.

The Four Chunking Patterns and When to Use Each

1. Fixed-size with overlap

  • Best for: homogeneous prose (news articles, product descriptions, simple FAQs)
  • Typical config: 256–512 tokens, 10–15% overlap
  • Breaks on: structured content, code, tables, documents where sentences straddle meaningful boundaries
  • Rule: if your content is consistent in structure and density, this is fine — don't over-engineer it

2. Semantic / sentence-boundary chunking

  • Best for: long-form documents with mixed paragraph density
  • How it works: split on sentence boundaries, then merge adjacent sentences until a similarity threshold drops — tools like LangChain's SemanticChunker or the semantic-chunkers library implement this
  • Breaks on: documents where individual sentences are meaningless without surrounding context (e.g., legal "whereas" clauses, step-by-step instructions)
  • Rule: if sentence length variance is high, semantic chunking outperforms fixed-size; measure before committing

3. Document-structure-aware chunking

  • Best for: markdown docs, HTML pages, PDFs with clear heading hierarchies, code repositories
  • How it works: split on structural signals — ## headings, <h2> tags, function definitions, section delimiters — not token counts
  • Breaks on: PDFs that were scanned (no structure), content with inconsistent or missing heading hierarchies
  • Rule: if your documents have reliable structure, use it — structure is free metadata

4. Agentic / recursive chunking with metadata injection

  • Best for: heterogeneous document collections where no single strategy fits all content types
  • How it works: a routing layer classifies document type, applies the appropriate chunking strategy per type, and injects document-level metadata (title, section, source, date) into each chunk
  • Breaks on: teams that don't maintain the routing logic as document types evolve
  • Rule: this is the pattern you grow into, not the one you start with

The Overlap Trap Nobody Talks About

Overlap is meant to preserve context across chunk boundaries. In practice it creates a subtle retrieval problem: two chunks that are 80% identical both get embedded and indexed. When your retriever fires, both often score similarly for the same query. You retrieve near-duplicate content, burn context window, and the LLM either repeats itself or, worse, synthesizes a slightly inconsistent answer because the two overlapping chunks have slightly different terminal sentences.

The fix is deduplication at retrieval time, not at chunking time — keep the overlap for embedding quality, but apply MMR (Maximal Marginal Relevance) or a simple cosine similarity threshold (anything above ~0.92 on normalized vectors is likely a near-duplicate) to the retrieved set before passing to the LLM.

python
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np

def deduplicate_chunks(chunks: list[dict], threshold: float = 0.92) -> list[dict]:
    """
    chunks: list of {"text": str, "embedding": list[float], ...}
    Returns deduplicated list, keeping the first occurrence.
    """
    kept = []
    kept_embeddings = []
    for chunk in chunks:
        emb = np.array(chunk["embedding"]).reshape(1, -1)
        if kept_embeddings:
            sims = cosine_similarity(emb, np.vstack(kept_embeddings))[0]
            if sims.max() >= threshold:
                continue  # near-duplicate, skip
        kept.append(chunk)
        kept_embeddings.append(emb)
    return kept

Note: this assumes embeddings are already L2-normalized (as most embedding APIs return them). If they're not, normalize first — raw dot product is not cosine similarity.

Chunk Size vs. Retrieval Granularity: The Real Tradeoff

There's a tension that most teams resolve incorrectly:

Chunk sizeRetrieval precisionGeneration qualityFailure mode
Small (64–128 tokens)HighLowRetrieved chunk lacks enough context for the LLM to generate a coherent answer
Medium (256–512 tokens)MediumMediumDefault — reasonable but rarely optimal
Large (1024+ tokens)LowHigh (if retrieved)Wrong chunk retrieved; correct information buried inside it

The pattern that resolves this is parent-child chunking: index small child chunks for retrieval precision, but return the parent chunk (larger, surrounding context) to the LLM for generation. LlamaIndex calls this "sentence window retrieval"; the concept is the same — retrieve small, generate large.

This is one of those architectural patterns that looks over-engineered until you see what it does to answer quality on dense technical documents. The gains are large enough to justify the added pipeline complexity in most production systems.

Metadata Is Part of the Chunk

A chunk without metadata is a sentence without a speaker. When you retrieve it, you don't know if it's from a 2019 policy document or a 2024 amendment. You don't know if it's from the terms of service or the FAQ. You don't know the document's authority level.

Imagine a team building a RAG system over a large enterprise knowledge base — hundreds of policy documents spanning several years. They skip the doc_date field to keep the pipeline simple. Six months later, users are getting answers grounded in superseded policies because the retriever has no way to filter by recency. The fix requires re-chunking and re-indexing the entire corpus. That's the metadata tax, paid late.

Every chunk should carry at minimum:

  • source_id: document identifier
  • section: heading path (e.g., "3.2 > Termination Clauses")
  • doc_date: publication or last-modified date
  • content_type: prose / table / code / list
  • language: especially critical for multilingual corpora

This metadata serves two purposes. First, it enables filtered retrieval — "only search chunks from documents updated after 2023" — which is often the difference between a useful answer and a confidently wrong one. Second, it gives the LLM provenance context so it can hedge appropriately when sources conflict.

If your chunking pipeline strips metadata to keep things simple, you're paying the price at query time.

The Evaluation Loop Most Teams Skip

Chunking decisions compound. A wrong strategy locks in retrieval failures that no amount of prompt engineering fixes. The only way to catch it is to evaluate retrieval quality independently of generation quality.

The minimal evaluation setup:

  1. Build a small golden dataset — 50–100 question/answer pairs where you know which document section contains the answer
  2. Run retrieval only (no LLM) and measure chunk-level recall@k (does the correct chunk appear in the top k results?)
  3. Vary chunking strategy, size, and overlap; plot recall@5 across strategies
  4. Only after retrieval recall is acceptable, evaluate end-to-end answer quality

Most teams skip step 2 and only measure end-to-end. The problem is that end-to-end quality conflates retrieval failures with generation failures. You can't fix what you can't isolate.

The same principle applies to every stage boundary in an AI pipeline: measure at the boundary, not just at the output. If you're not instrumenting retrieval recall independently, you're flying blind — and you'll keep patching the wrong component.

The Chunking Decision Flowchart

Before touching your embedding model or retriever, run through this:

code
Is your content homogeneous prose with consistent density?
  → Yes: Fixed-size (256–512 tokens, 10% overlap). Done.
  → No ↓

Does your content have reliable structural markers (headings, tags, code blocks)?
  → Yes: Structure-aware chunking. Inject heading path as metadata.
  → No ↓

Is sentence-level granularity meaningful in your domain?
  → Yes: Semantic chunking with similarity-based merging.
  → No ↓

Do you have multiple distinct content types in the same corpus?
  → Yes: Agentic routing with per-type strategy.
  → Start here only if you have the team to maintain it.

If you're retrieving small chunks but generation quality is low, add parent-child chunking before touching anything else.

If retrieval recall is below 70% at k=5 on your golden set, the problem is almost always chunking or metadata filtering, not the embedding model.

What to Actually Do

  1. Audit your current chunk distribution. Plot token length across your indexed chunks. If you see a bimodal distribution or a long tail of very short chunks, your strategy is wrong for your data.

  2. Build a 50-question golden retrieval dataset this week. Measure chunk recall@5. Do this before adding any new RAG features — you need a baseline.

  3. Add five metadata fields to every chunk. Source ID, section path, doc date, content type, language. This costs one engineering day and pays off on every filtered query.

  4. Implement deduplication at retrieval time. Cosine similarity threshold of 0.92 on the retrieved set. Five lines of code, measurable impact on generation coherence.

  5. If you're on a heterogeneous document corpus, prototype parent-child chunking on your highest-traffic query category first. Measure recall delta before rolling it out corpus-wide.

Chunking is unglamorous. It doesn't have a model card or a benchmark leaderboard. But it's the variable with the highest leverage in most RAG pipelines — and the one most teams configure once, file under "done," and never revisit.

Fix the chunks. Everything downstream gets better.

Working on something like this? I take on a few fractional-CTO and AI engagements at a time.

The AI CTO playbook

Get my AI playbooks — straight to your inbox

Practical notes on shipping production AI, scaling teams, and the calls a CTO actually has to make. A few times a month. No spam, no fluff.