Your retrieval-augmented generation prototype passes the demo questions, then fails on ordinary user queries. It gives half an answer on pricing exceptions, skips a step in a CLI setup flow, or quotes an old policy from last year. So you open the ingest script, swap the chunk size from one guessed number to another, re-index everything, and hope the next run feels better.
In a RAG pipeline, documents are split into chunks before they are embedded and stored in a search index, and at query time the system retrieves the top matches and passes them to a language model with the question. Tuning that splitting step by guesswork wastes hours because different failures come from opposite causes. Work backward from a small set of real mistakes instead.
Collect a small failure set first
Start with 10 to 20 real questions where the prototype already failed or gave an incomplete answer. For each question, write down the expected answer and the exact passage in your source documents that supports it.
Next, write a plain inspection script that runs each question through retrieval only, before the language model sees the prompt. Print the query, the expected source passage, and every retrieved chunk in rank order with its chunk ID, full text, and metadata. Keep the chunk text untruncated so you can see the exact boundaries the index returned.
This log replaces guesswork with direct inspection. A few pages of terminal output will tell you more about your RAG chunking than any rule of thumb.
Ask one question of every retrieved set
Before touching any chunking parameter, read the retrieved chunks for a failed query and ask one test question: could a human who sees only these passages answer the question accurately?
If yes, stop blaming chunking. Retrieval already handed the model what it needed, so the bug sits in your prompt, model choice, or generation behavior. If no, inspect what is missing or broken in those passages and match it to one of the patterns below.
Seven failure patterns and the matching fix
The answer sits next door in a missed neighbor chunk
You see the start of the right topic in a retrieved chunk, while the second half of the sentence or an exception clause sits in the adjacent chunk that never made the top results. Token windows frequently cut a sentence or separate a rule from its condition.
Fix this by splitting on document structure first, such as headings and paragraphs, so related sentences stay together. Then add a small chunk overlap, repeating the end of one chunk at the start of the next so a boundary sentence still appears whole in at least one chunk.
The answer is buried inside a giant section
The right passage is in the retrieved text, surrounded by several paragraphs of unrelated background, caveats, or other subtopics. Either the model overlooks the key sentence in the middle of the noise, or a few huge chunks consume your prompt budget before other relevant passages fit.
Fix this by capping section size and splitting oversized sections into smaller chunks. When chunks are tighter and focused on one point, you can retrieve a cleaner set without filling the prompt with dead weight.
The chunk is a fragment with no context
A retrieved chunk reads cleanly at the sentence level yet makes no sense alone, such as "This does not apply to enterprise plans." Without the earlier paragraph or section heading, neither a human reader nor a language model knows what "this" refers to.
Fix this by keeping chunks aligned with headings and paragraphs, or by prepending the document title and heading path to each chunk before embedding and prompting. That small piece of context anchors pronouns and short rules back to their subject.
A table, code block, or list got sliced down the middle
Half of a Markdown table shows up without its column headers, a fenced code snippet cuts off before the flags that matter, or step four of a five-step procedure appears without steps one through three. Token-count and character-count windows split right through formatted blocks.
Fix this with structure-aware rules that keep tables, fenced code blocks, and short lists intact as atomic units whenever they fit within your size cap. Scanned PDFs often lack reliable headings or block markers, so expect extra cleanup there before chunking.
An old version of the document won the search
Your inspection log shows a coherent, well-formatted chunk that happens to come from last quarter's guide or a deprecated API page. When documents update without removing or replacing their older indexed pieces, stale chunks remain a factual error waiting to surface.
Fix this in your ingestion lifecycle rather than your splitter. When a source file changes, re-chunk and re-embed it, write the new chunks under predictable IDs, and retire or delete the old chunk IDs from the index.
Near-duplicate chunks crowd out the rest of the results
Three of the top five retrieved passages say almost the same thing because wide overlap repeated whole paragraphs across consecutive chunks. That wastes slots in the result set and pushes distinct supporting passages out of the prompt.
Fix this by dialing back chunk overlap, adjusting chunk size, or adding a lightweight deduplication step after retrieval so overlapping siblings from the same section do not dominate the top results. Overlap always increases the tokens you embed and store, so keep it as small as your boundary tests allow.
Raising top-k makes answers worse
When answers look incomplete, the instinctive move is to fetch more chunks. Past a point, feeding more passages into the prompt degrades quality: instruction following slips and recall drops as the prompt grows longer and more cluttered, a problem known as context rot that can show up even under the advertised context limit.
Fix this by retrieving fewer, tighter chunks instead of piling on extra results.
A minimal structure-first splitter in Python
Once your failure log points toward boundary cuts and oversized sections, a structure-first pass is straightforward to prototype with Python's standard library. The example below splits Markdown on headings outside fenced code blocks, records each section's heading path as metadata, and caps long sections by word count with a small overlap.
import re
def chunk_markdown(text, max_words=180, overlap=25):
sections, stack, buf, fence = [], [], [], False
for line in text.splitlines():
if line.lstrip().startswith("`" * 3):
fence = not fence
if not fence and (m := re.match(r"^(#{1,6})\s+(.*)$", line)):
if buf:
sections.append((" > ".join(stack), " ".join(buf).strip()))
buf = []
stack = stack[: len(m.group(1)) - 1] + [m.group(2).strip()]
else:
buf.append(line)
if buf:
sections.append((" > ".join(stack), " ".join(buf).strip()))
chunks, step = [], max(1, max_words - overlap)
for path, body in sections:
words = body.split()
for i in range(0, len(words), step):
chunks.append({"heading_path": path, "text": " ".join(words[i : i + max_words])})
if i + max_words >= len(words):
break
return chunks
Word count is only a rough stand-in here so the sketch stays self-contained. Because embedding models and LLMs enforce token limits and bill by tokens, production code should measure chunk size and overlap using the actual tokenizer of your embedding model. Teams usually start with a size that fits comfortably inside that embedding model's limit, then adjust chunk size and overlap based on what their retrieval failure log shows. Even the classic 2020 RAG paper (arXiv:2005.11401) split Wikipedia articles into non-overlapping 100-word passages for a 21-million-passage research setup, which was an experimental choice rather than a production template.
Metadata that pays off while debugging
Good metadata makes the inspection script much easier to read and keeps bad data out of answers. At minimum, store these fields alongside every chunk:
- A stable chunk ID tied to the source document and position so updates can overwrite or delete old entries cleanly
- Source ID or URL, document title, and heading path so you can verify context at a glance
- Page number for paginated files and an updated timestamp for tracking stale content
- Permission labels enforced as hard filters inside the search index, never left to prompt instructions alone
What chunking cannot fix
Even careful RAG chunking hits hard walls. No splitting strategy can rescue a weak embedding model that misses domain terminology, an unindexed document, an index without permission filters, or a language model that ignores retrieved text and answers from memory. Good chunking reduces retrieval mistakes, though it does not eliminate hallucinations.
For a full reference on token, semantic, and structure-based splitting options, read Lotook's method-by-method guide to chunking documents for RAG on Lotook. Keep your 10-to-20-question failure script as a permanent regression check and re-run it after every splitter change.
Comments
Join the discussionNo comments yet. Be the first to comment.