Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Chapter 3: Chunking Long Documents

Every document in Chapters 1 and 2 was one sentence. That made the examples clean, but it quietly sidestepped something every real RAG system has to deal with: real documents are long. An employee handbook isn’t one sentence, it’s pages, with a vacation section, a sick leave section, an expense section, and more, all in one file.

So what happens if we just embed the whole handbook as a single vector, the same way we embedded single sentences in Chapter 2? Two problems show up immediately:

  1. Length limits. Real embedding models accept a fixed maximum amount of text (typically somewhere from a few hundred to a few thousand words, depending on the model), then either truncate or refuse anything longer. A long document may not even fit.

  2. Dilution. Even when it fits, averaging an entire multi-topic document into one vector blends the vacation section, the sick leave section, and the code of conduct into a single blurry point in space. A question about one specific detail has to compete against everything else in the document for that one vector’s “attention.”

Chapter 3 of 10 · Foundations

Where we left off: Chapter 2 fixed spelling-vs-meaning, but every document it compared was a single sentence.

What we fix now: Split long, multi-topic documents into smaller pieces before embedding them, so a question only has to compete against a focused chunk, not an entire document.

By the end, you can:

  • explain why embedding a whole multi-topic document as one vector causes dilution

  • build and compare three chunking strategies: whole-document, fixed-size, and sentence-aware

  • see how a badly placed chunk boundary can cut a fact in half, and fix it with overlap

The fix: chunk before you embed

The fix is the same shape as Chapter 2’s fix: change what gets compared. Instead of one vector per document, split each document into smaller pieces first, a chunk is just a contiguous slice of text, embed each chunk separately, and retrieve at the chunk level. The question now only has to compete against focused pieces of text, not entire documents.

The interesting part, and where real systems spend a lot of care, is how you cut a document into chunks. Cut carelessly and you can slice a fact in half right at a chunk boundary, which causes its own new failures. This chapter builds a few chunking strategies from scratch and shows exactly where each one helps and where it still breaks.

Source

The handbook still comes out on top here, but look at the margin: it’s not a landslide, even though the handbook contains the exact answer and the IT document has nothing to do with vacation days. Every irrelevant sentence in the handbook, sick leave, expense, working hours, code of conduct, pulls the whole-document vector a little further from “vacation,” diluting the one part that actually matters.

And even where this ranking happens to be correct, think about what you’d actually hand to a generation step: the entire six-section handbook, when the honest answer is one sentence. That’s wasted context for the model to sort through, and in a real system, wasted cost.

A large jump: the Vacation Policy section alone now scores well above the whole handbook’s 0.76, and well above the IT document too. More importantly, what you’d actually retrieve is two sentences about vacation days, not six sections of mostly-irrelevant policy. Splitting on blank lines worked well here because this handbook happens to have clean section breaks. Most real documents don’t hand you paragraph breaks that conveniently, so we need a strategy that doesn’t depend on getting lucky with formatting.

Before · whole document

Same question: “how many vacation days can be carried over into next year?”

Similarity score for the entire six-section handbook: 0.762. The right answer is buried somewhere inside it, alongside five sections that have nothing to do with the question.

After · chunked by section

Same question: “how many vacation days can be carried over into next year?”

Similarity score for the Vacation Policy section alone: 0.910, a clear jump above the whole document’s score. What gets retrieved now is two relevant sentences, not six sections.

Fixed-size chunking, and its obvious problem

The simplest general-purpose strategy: cut the text every N characters, no matter what’s there. It requires no assumptions about formatting, and it can slice a sentence, or a word, right down the middle. This chapter counts characters specifically to keep the mechanism visible; real RAG pipelines usually define chunk size in tokens instead, since tokens are the unit embedding models and LLM context windows actually operate on. Don’t assume a production chunk_size=500 means 500 characters, it almost always means tokens.

Look closely at chunk 0 and chunk 1: the word “vacation” is sliced into “vacatio” and “n” across the boundary. The similarity score barely notices, our small toy model is forgiving because other nearby words (“days,” “calendar,” “year”) still carry the meaning, but check the last line of output: no single chunk contains the complete fact “5 unused vacation days.” It’s split across two pieces. If a real system retrieves only the top-scoring chunk, whatever step reads it next, a person or a model, never sees the whole number in one place.

Same chunk size, same document, one new parameter: overlap=20. Now at least one chunk contains “5 unused vacation days” fully intact, because the 20 characters right at the danger zone get repeated in two neighboring chunks instead of split between them. Overlap doesn’t prevent every possible bad cut, but it makes them far less likely to cost you a fact entirely.

The size trade-off, and a middle ground

Chunk size is a genuine trade-off, not a setting with one correct answer:

  • Too small, and you get the boundary problem above: facts and even single words get sliced apart, and each chunk carries too little surrounding context to be useful on its own.

  • Too large, and you’re sliding back toward the very first demo in this chapter: a chunk that spans multiple topics dilutes back down toward whole-document behavior, just with extra steps.

A common middle ground: sentence-aware chunking. Never cut in the middle of a sentence; instead, group whole sentences together up to a target size. It respects the one boundary that almost always matters (the sentence), while still giving you control over roughly how big each chunk is.

No word gets cut in half anywhere in this list, and the score is close to the section-chunking result from earlier. It’s not perfect, look closely and you’ll find at least one chunk that quietly crosses from one policy into the next, since “group sentences up to a size budget” doesn’t actually know what a “topic” is, only where sentences end. That’s a real, still-open limitation, not a bug to fix in this chapter.

Quick check

Before moving on, answer for yourself (no peeking ahead):

  1. In the very first demo, the handbook still scored higher than the IT document, so the naive whole-document approach technically “worked.” What was still wrong with it, beyond the raw score?

Check your reasoning ▸

A strong answer names dilution specifically: even though the ranking was technically correct, the margin was thin (0.762 vs 0.572) for a document that contains the exact answer, and everything you’d actually hand to a generation step is six sections of mostly irrelevant text instead of the one relevant sentence.

  1. Look at the two chunks produced by chunk_size=63 with no overlap. Why does neither one contain the complete phrase “5 unused vacation days,” and what specifically does adding overlap=20 change about how the chunks are built to fix that?

Check your reasoning ▸

Neither chunk contains the full phrase because the character-count cutoff landed mid-fact, splitting the words right at the boundary with no regard for what they meant. Adding overlap=20 makes each new chunk start 20 characters before the previous one ended, so a fact sitting near a boundary gets a second chance to appear whole in at least one of the two overlapping chunks.

  1. In the “try it yourself” cell, push CHUNK_SIZE up to something large, larger than an entire section. What happens to the winning chunk’s score, and why does that start to resemble the very first demo in this chapter?

Check your reasoning ▸

As CHUNK_SIZE grows toward the length of an entire section, or the whole document, each chunk starts blending multiple topics together again, and the score should drift back down toward the diluted, less confident numbers from the naive whole-document demo, because a large enough chunk basically is the whole document.

  1. Sentence-aware chunking never cuts a word or sentence in half, but it can still merge two different policies into a single chunk. Why might that still cause a problem for retrieval, even though nothing is technically “broken” the way the fixed-size cut was?

Check your reasoning ▸

A good answer separates two different failure modes: fixed-size chunking corrupts a fact by cutting it mid-word, while a merged-policy chunk keeps every word intact but still dilutes the vector, the same underlying problem the whole-document approach had, just at a smaller scale, because the chunk’s vector now has to represent two unrelated topics instead of one.

  1. Chapter 2 pointed out that its toy embedding model has no vector at all for words it never saw during training. Does chunking make that specific problem better, worse, or unrelated? Explain.

Check your reasoning ▸

A strong answer recognizes these as unrelated problems solved at different stages: chunking controls how much text gets embedded together, which fixes dilution, but it does nothing about which words the embedding model actually knows. A chunk built entirely from out-of-vocabulary words would still fail exactly the way Chapter 2’s “furlough” example did; chunking doesn’t touch the vocabulary problem at all.


You can now: split long documents into focused chunks and choose a strategy deliberately, whole-document, fixed-size with overlap, or sentence-aware, based on what each one actually trades off.

But one problem remains: we’ve now built retrieval three different ways, word overlap, embeddings, chunking, and never once actually generated an answer with a real model. It’s time to actually call one.

Next → Chapter 4: Completing the Loop