RAGEngineering Hub
Chapter 4 · concept 1 of 6 · show all
  1. 1What RAG is, and why
    1. 1.1Why models need outside knowledge, and what hallucination means
    2. 1.2What RAG is: anatomy of a request
    3. 1.3RAG vs fine tuning vs long context and prompt caching
    4. 1.4Use cases and the quality, latency and cost triangle
    5. Chapter overview
  2. 2Tokens, cost and your first RAG
    1. 2.1Tokens and context windows for RAG
    2. 2.2The RAG lifecycle and your first RAG in miniature
    3. Chapter overview
  3. 3Getting documents in
    1. 3.1Content sources and file formats
    2. 3.2Parsing, layout and OCR
    3. 3.3Tables, images and mixed content
    4. 3.4Cleaning, normalization and quality gates
    5. 3.5Metadata, document identity and versioning
    6. 3.6Updates, deletes, reindexing and ingestion pipelines
    7. Chapter overview
  4. 4Chunking
    1. 4.1Why we chunk, and the main chunking strategies
    2. 4.2Structure aware chunking and tables
    3. 4.3Chunk size, overlap and token based sizing
    4. 4.4Contextual chunking and late chunking
    5. 4.5Parent child and multi representation indexing
    6. 4.6Chunk metadata, provenance and the chunking debugging playbook
    7. Chapter overview
  5. 5Embeddings
    1. 5.1Embeddings and similarity from first principles
    2. 5.2What embeddings cannot do
    3. 5.3Choosing an embedding model: size, cost, language
    4. 5.4Embedding model migration and index rebuilds
    5. Chapter overview
  6. 6Vector stores and scores
    1. 6.1Vector databases, ANN indexes and choosing a store
    2. 6.2Score calibration and thresholds
    3. 6.3Late interaction and multivector retrieval
    4. Chapter overview
  7. 7Finding the right chunks
    1. 7.1Query understanding, rewriting and expansion
    2. 7.2Dense retrieval and keyword search with BM25
    3. 7.3Hybrid retrieval and rank fusion
    4. 7.4topK, no good match, and metadata filtering
    5. 7.5Routing, diversity, deduplication and caching
    6. 7.6Reranking: two stage, cross encoder and LLM rerankers
    7. Chapter overview
  8. 8When retrieval fails
    1. 8.1Retrieval failure analysis playbook
    2. Chapter overview
  9. 9Building the answer
    1. 9.1Context selection, packing and citations
    2. 9.2Grounding prompts and abstention
    3. 9.3Lost in the middle and context management
    4. 9.4Conflicts, staleness and freshness rules
    5. 9.5Hallucination checks and answer verification
    6. 9.6Conversational RAG, answer UX and RAG APIs
    7. Chapter overview
  10. 10Measuring
    1. 10.1Why measure, and what to measure
    2. 10.2Building a golden dataset
    3. 10.3Retrieval metrics: precision, recall, MRR, nDCG
    4. 10.4Faithfulness and answer correctness
    5. 10.5LLM as judge, and judging the judge
    6. 10.6Offline and online evals, regression tests and slices
    7. Chapter overview
  11. 11Safety and operations
    1. 11.1Prompt injection and poisoned documents
    2. 11.2Access control, privacy and governance
    3. 11.3Index lifecycle operations
    4. 11.4Observability and tracing
    5. 11.5Latency, cost, caching and model routing
    6. 11.6Reliability, degraded modes and launch readiness
    7. Chapter overview
  12. 12Scale and advanced RAG
    1. 12.1Scaling, multi tenancy and enterprise architecture
    2. 12.2Choosing an architecture: naive, advanced, agentic, graph
    3. 12.3Agentic RAG and RAG as an MCP tool
    4. 12.4GraphRAG, structured data and multimodal RAG
    5. Chapter overview

Why we chunk, and the main chunking strategies

Why documents are cut into pieces before search, and how fixed, recursive and heading-based splitting differ on the same corpus.

A real question

Why not search whole documents? Why cut them into pieces at all?

Because the embedding model only reads the start of a long text, one vector cannot stand for many topics, and the model would have to read the whole file to answer one line.

The problem

Why this matters

Embed each document as one unit. Nothing gets cut, so nothing gets lost.
The belief

Embed each document as one unit. Nothing gets cut, so nothing gets lost.

The embedding model still cuts: it reads only the first 512 tokens. In our 1,922 token FiberBox manual, everything after that is never embedded.
What really happens

The embedding model still cuts: it reads only the first 512 tokens. In our 1,922 token FiberBox manual, everything after that is never embedded.

Why not the simpler way

The simple way is to embed each document as one unit. It is less code and nothing gets cut. It breaks in three places: an embedding model reads only a limited number of tokens, so a long manual is represented by its first pages; one vector has to stand for many topics at once; and the prompt has to carry the whole document to answer one line of it.

The idea

The idea in plain words

A chunk is the piece of text that search can return and the model can quote. Chunking is choosing where to cut. A good cut keeps one idea together; a bad cut splits the answer in half, so no single piece matches the question well.

It is like cutting a long recipe book into cards. One card per recipe is useful. Cutting every 20 lines puts half of one recipe and half of the next on the same card.

How it works

Step by step

  1. Cut every N tokens, wherever that lands. Simple, but it can cut a sentence or a table in half.
    1

    Fixed window

    Cut every N tokens, wherever that lands. Simple, but it can cut a sentence or a table in half.

  2. Try the biggest natural break first (blank lines), then lines, then sentences, until every piece fits.
    2

    Recursive

    Try the biggest natural break first (blank lines), then lines, then sentences, until every piece fits.

  3. Follow the document's own sections, and split a section further only when it is too long.
    3

    By headings

    Follow the document's own sections, and split a section further only when it is too long.

In detail

There are three main families. A fixed window cuts every N tokens, wherever that lands. Recursive splitting tries the biggest natural break first (blank lines between paragraphs), then lines, then sentences, until each piece fits the size limit. Structure-aware splitting follows the document's own headings, so each section becomes one chunk, and only sections that are too long are split further.

The same document of headings, paragraphs and a table cut three ways: a fixed window cuts every N tokens and can go through the table; recursive splitting cuts at paragraph breaks; splitting by headings makes one chunk per section.Fixed windowevery N tokenscut through the tableRecursiveparagraph, line, sentenceBy headingsone section per chunkheading · paragraph · paragraph · table · heading · paragraph · paragraph
A fixed window ignores the structure. Recursive and heading-based splitting move the cuts to places where the text already breaks.

A fourth family is semantic splitting: embed every sentence, and cut where the meaning shifts, that is, where one sentence is far from the next in embedding space. It sounds like the smartest option, but it needs one embedding per sentence just to decide the cuts, and the chunks come out very uneven in size. On our corpus it found fewer answers than recursive 512 (see My numbers).

Size and overlap are the next decisions; they get their own page (RAG-15). Newer ideas such as adding document context to each chunk, or embedding the whole document first and splitting the vectors later, come in RAG-16.

Try it · cut one real document

Loading chunks…

Fixed windows with no overlap, cut from our insurance claims handbook. Question: “Is there an excess for storm damage?”

The code

Try it in Python

import re

def recursive_split(text: str, max_tokens: int, count, seps=("\n\n", "\n", ". ", " ")) -> list[str]:
    """Split at the largest natural break that makes every piece fit."""
    if count(text) <= max_tokens:
        return [text]
    sep, *rest = seps
    parts, out, buf = text.split(sep), [], ""
    for p in parts:
        joined = f"{buf}{sep}{p}" if buf else p
        if count(joined) <= max_tokens:
            buf = joined
        else:
            if buf:
                out.append(buf)
            buf = p if count(p) <= max_tokens else ""
            if not buf:  # this piece alone is too big: split it at the next smaller break
                out.extend(recursive_split(p, max_tokens, count, tuple(rest)) if rest else [p])
    return out + ([buf] if buf else [])

def semantic_split(sentences: list[str], embed, pct: float = 95) -> list[str]:
    """Cut where the next sentence is far from this one in meaning (needs one embedding per sentence)."""
    import numpy as np
    v = np.array(embed(sentences))                      # normalised vectors
    dist = 1 - (v[:-1] * v[1:]).sum(axis=1)             # distance to the next sentence
    cut = np.percentile(dist, pct)
    chunks, buf = [], [sentences[0]]
    for s, d in zip(sentences[1:], dist):
        if d > cut:
            chunks.append(" ".join(buf)); buf = []
        buf.append(s)
    return chunks + [" ".join(buf)]

def split_by_headings(md: str) -> list[str]:
    return [s for s in re.split(r"(?m)^(?=#{1,6} )", md) if s.strip()]

See it

What we measured

Measured · evidence recall@3

25 Polder documents, 47 golden questions, bge-small-en-v1.5 vectors, vector search only, 512 token limit. A hit means a top 3 unit holds the whole answer passage.

Whole document as one unit (25 units)baseline0.915 (43 of 47)
Fixed window 512 (35 chunks)0.9150.894
By headings 512 (91 chunks)0.9150.894
Recursive 512 (34 chunks)0.9150.936
Semantic, cut above 90th percentile (64 chunks, 10 to 524 tokens)0.9150.872 (41 of 47)
Semantic, cut above 95th percentile (42 chunks)0.9150.851 (40 of 47)

Measured 2026-11-03 (precompute report); the whole-document row re-measured 2026-11-04 with a reproducible script, which gave 43 of 47 where an earlier one-off run had given 42. Three documents are longer than the 512 token input of the embedding model. Splitting by headings found one answer fewer than whole documents here because many sections are short, which gives 91 small chunks that compete for the top 3. Semantic rows measured 2026-11-04 (corpus/scripts/measure-semantic.mts): each sentence embedded with one neighbour on each side, a cut where the distance to the next sentence is above the document's 90th or 95th percentile, and also at the 512 token limit. It took 544 sentence embeddings to place the cuts. The 80th percentile gave 112 chunks and 40 of 47. One corpus and one embedding model, so treat this as a result for these documents, not a law.

Try it on a real example

A customer asks: “Should I reset the router when the power light is solid red?”

Index

FiberBox manual · 1,922 tokens

The warning “Do not reset a router that shows a solid red power light” starts at token 1,663. The embedding model reads only the first 512 tokens of the document.

The answer is in the file, but not in its vector. Search has nothing that points to it.

Choosing

Use it when, avoid it when

Use it when
  • Recursive splitting as the default for prose
  • Splitting by headings for manuals and policies with clear sections, then recursive inside long sections
Avoid it when
  • Whole documents as one vector: the model reads only the start, and the prompt carries everything
  • A fixed window on tables and code: it cuts rows and functions
  • Semantic splitting as a default without measuring: here it found fewer answers and needed an embedding per sentence

What it costs · More chunks mean more vectors to store and embed. Here: 25 whole documents, 34 to 35 chunks at 512 tokens, 91 chunks when split by headings.

In production

Look at your documents before choosing. Manuals, policies and handbooks with clear headings suit structure-aware splitting. Chat logs and transcripts have no headings, so recursive splitting by turns or sentences fits better. Keep the source, section path and position with each chunk; you need them for citations and for updates.

Then measure. A golden set with the exact answer passage per question shows which cuts lose answers, and the Chunk Lab shows whether each miss was a split or a ranking problem.

Watch out

Common mistakes

  • No chunking is safest because nothing is cut. The embedding model still cuts: it ignores everything after its input limit.
  • Structure-aware splitting always wins. On this corpus it found one answer fewer than whole documents, because short sections made many small chunks.
  • Semantic chunking is the advanced, better choice. Here it found 41 of 47 at best, against 44 for recursive 512, and cost 544 extra embeddings.
  • Chunking is a one-time setup. Every new document type is a reason to measure again.

Check yourself

Remember this

Remember these

  • A chunk is the piece that search can return and the model can quote.
  • Three main families: fixed window, recursive, and by headings. Semantic splitting is a fourth, and it did worse here.
  • Whole documents found 43 of 47 answers here; recursive 512 chunks found 44.
  • Pick the strategy per document type, then measure.

Say it in an interview

  1. 01Chunks are what search can return and the model can cite, so the cuts cap quality.
  2. 02Three families: fixed window, recursive by natural breaks, and structure by headings.
  3. 03On my corpus recursive 512 reached 0.936 evidence recall against 0.915 for whole documents.
  4. 04Whole documents fail quietly: the embedder reads only the first 512 tokens.
  5. 05Semantic chunking scored 41 of 47 against 44 for recursive here, so I measure it before I trust it.
  6. 06I pick the strategy per document type and check misses for splits in a chunk lab.
Try it yourself: Chunk LabChange the chunk strategy and size, and see how often the answer survives in one piece.

Quick quiz

Three questions

Question 1

Q1

Embedding each whole document as one unit found the answer for 43 of 47 questions. What is the main problem besides recall?

Question 2

Q2

Which strategy keeps a markdown table whole most reliably?

Question 3

Q3 · from RAG-05

In the RAG lifecycle, when does chunking happen?

Saved on this device. No account needed.