Founders

How to Set Chunk Boundaries So a Fact Survives Whole

Antonio Evans8 min read

A document page is split along dashed lines that run between question blocks, not through them.
Key points

Key points

  1. 1Treat each question and its complete answer as the smallest unit you can retrieve, so no chunk returns half a number, a half date or a half condition.
  2. 2Draw split points before question lines and after the last sentence of an answer, never inside numbers, units, date ranges or if-then clauses.
  3. 3Use overlap that repeats the question line (not the answer tail) so a fragment that matches the question still carries its answer.
  4. 4Validate with a small labeled set of real queries, checking whether every number, date and condition in the expected answer appears intact in at least one retrieved chunk.

Why fixed-size windows break facts

Fixed-length chunking splits text at a character or token count and ignores sentence and idea structure, which is fast but can cut facts mid-sentence and orphan ideas in the Meilisearch chunking guide. A window that ends on "the refund is 40" and resumes on "percent for annual plans" has produced two chunks, neither of which states the policy. Retrieval can still rank the first chunk highly because it contains the query words, and the generator then sees a truncated number.

The same failure mode hits dates, thresholds and conditions. "Approved only if the request arrives within 14" is a different fact from "days of the invoice date." A model asked about the policy may fill the gap from neighboring chunks, from its training data, or from nothing, which is exactly the hallucination pattern that retrieval is meant to suppress as SUSE describes in its hallucination guidance.

The core insight is that chunking is not a text-window problem. It is a decision about what counts as one piece of evidence. NVIDIA's chunking experiments found that the best strategy varied by dataset and query type, and that page-level chunking gave the highest average end-to-end accuracy with the lowest variance across the datasets tested per NVIDIA's technical blog. Structure won over raw size. Question boundaries are the same idea applied to FAQ, support and policy content.

Step 1: identify the atomic unit

Before you touch a splitter, find the smallest thing a user would ask about and expect a full answer to. In a support knowledge base, a help center, a policy manual or a product FAQ, that unit is almost always one question paired with its complete answer. Splitting below that unit means a retrieved chunk cannot answer anything on its own; splitting above it means the embedding blurs two or three topics together.

A practical definition you can hand to an annotator:

  • Atomic unit: one question line plus every sentence, list item, table row and caveat that the answer needs in order to be true.
  • Not atomic: a heading alone, a paragraph mid-answer, a table row without its column header, a footnote without its referent.

Multi-part answers are where this gets interesting. A question like "What are the refund conditions for annual plans?" may answer with a duration, a percentage, an approval step and two exclusions. All of that belongs to one unit. If you split the exclusions into a separate chunk, the retrieved evidence states a refund is available without stating when it is not, and the generator can answer with confidence in the wrong direction.

Keep the question line attached to the answer in the stored text, not just in metadata. When a chunk contains its own question, its embedding carries both the user's phrasing and the answer's content, which improves matching on the exact words users type.

Step 2: choose split points around the unit

Once the unit is defined, the splitter's job is simple: cut immediately before each question, and never anywhere else except where a single unit exceeds your maximum chunk size. That turns the problem from "where do I cut text" into "how do I recognize a question start."

Typical question-start signals in documentation:

SignalExampleSplit before it?
Heading ending in a question mark## How long does a refund take?Yes
Bold question label**Can I cancel after renewal?**Yes
Q prefix in a Q and A listQ: What counts as a business day?Yes
FAQ schema fieldquestion in FAQPage JSON-LDYes
Answer continuationYes, provided the account is in good standing.No

When your source has a structured FAQ format, parse the question and answer fields directly instead of splitting on punctuation. That removes the guesswork. Where the content is prose with embedded questions, use the heading and question-mark signals to place boundaries and then walk backward to attach any lead-in sentence that the question depends on, such as "The following rules apply to all annual plans."

Here is a minimal splitter that respects those rules. It cuts only at question markers and keeps each unit whole:

import re

QUESTION_START = re.compile(
    r"(?=^#{2,6}\s.*\?\s*$)|(?=^\*\*.+\?\*\*\s*$)|(?=^Q:\s)",
    re.MULTILINE,
)

def split_on_questions(text: str, max_chars: int = 1800) -> list[str]:
    units = [u.strip() for u in QUESTION_START.split(text) if u.strip()]
    chunks = []
    for unit in units:
        if len(unit) <= max_chars:
            chunks.append(unit)
            continue
        # Oversized unit: split on sentence ends, then repack,
        # never leaving a sentence fragment in a chunk.
        sentences = re.split(r"(?<=[.!?])\s+(?=[A-Z0-9])", unit)
        current = ""
        for s in sentences:
            if len(current) + len(s) + 1 > max_chars and current:
                chunks.append(current.strip())
                current = s
            else:
                current = f"{current} {s}".strip()
        if current:
            chunks.append(current)
    return chunks

Two details matter more than the code. First, the sentence split uses a lookbehind on terminal punctuation and a lookahead on an uppercase letter or digit, which reduces (but does not eliminate) cuts inside abbreviations. Second, the oversized-unit path keeps at least one full sentence per chunk, so even a very long answer never returns a bare fragment.

Step 3: protect numbers, dates and conditions

The sentence splitter above still has a weakness: a sentence can be longer than your limit, and some "sentences" contain decimal numbers, version-like tokens or abbreviations that look like sentence ends. Add a guard pass that forbids cuts inside protected spans.

Span typePattern that must not be cutWhy
Decimal and thousands1,024, 3.5A cut yields two wrong numbers
Currency$40, USD 250Loses the unit or the symbol
Percent40%, 12.5 percentPercentage becomes a bare figure
Date rangeMarch 1 to March 31, Q1 to Q3Range collapses to one endpoint
Time windowwithin 14 days, no later than 5 p.m.Condition loses its trigger
Conditional clauseif ... then ..., unless ...Stated rule becomes unconditional

A simple enforcement rule: after splitting, reject any chunk whose first or last token matches a number, currency symbol, month name, unit word, or dangling connective (and, or, unless, except, if). Move those boundary tokens to the neighboring chunk and re-check. This is mechanical and catches most half-facts before they ever reach an embedding.

For conditions specifically, prefer a split that keeps the whole if ... then ... construction in one unit even if it runs a little over your target length. Chunk size is a soft budget. A condition split across two chunks is a hard error. The Meilisearch guide makes the same point in its evaluation advice: chunks should be self-contained facts that are not cut off in a way that changes them per its context preservation measure.

Step 4: use overlap that repeats the question, not the answer tail

Sliding-window overlap is the default fix for boundary loss, and it is the wrong one here. Repeating the last 50 tokens of an answer into the next chunk duplicates half a fact if the boundary fell inside it, and it makes the second chunk's embedding drift toward content it does not own. Meilisearch lists overlapping as a valid umbrella strategy but pairs it with the caveat that the chunk must still hold one coherent fact per the same guide.

For question-boundary chunking, repeat the question line at the top of every continuation chunk of a long answer. The question is the retrieval key, so carrying it forward keeps continuation chunks matchable without duplicating answer content. If an answer spans three chunks, all three start with the same question, and the numbers appear exactly once each.

Pair this with metadata that lets you reassemble rather than duplicate:

  • doc_id and unit_id identify the question-answer pair.
  • part and part_count show whether the unit was split and where this chunk sits.
  • has_question_line records whether the chunk starts with the question.
  • protected_spans can cache the character offsets of numbers, dates and conditions you refused to cut.

At query time, if two chunks share a unit_id, the generator can see they are parts of one answer. That is cheaper and more accurate than hoping overlap covered the gap.

Step 5: validate with sample queries

You cannot know the boundaries are right by reading the splitter. You need a small query set with known-answer checks. Build 20 to 50 real questions your users ask, and for each one record the exact strings the answer must contain: the number, the date range, the condition. Then run retrieval and check the text, not the model's answer.

A validation set in that shape looks like this. The must_contain list is the automated check; the must_not_lead_with list catches fragments:

cases = [
    {
        "query": "Can an enterprise customer cancel after renewal and get money back?",
        "must_contain": ["30 days", "prorated", "annual plans"],
        "must_not_lead_with": ["percent", "days of"],
    },
    {
        "query": "How long does a refund take once approved?",
        "must_contain": ["5 to 10 business days"],
        "must_not_lead_with": ["business"],
    },
]

Score each case on three counts. Hit rate: did any retrieved chunk contain all required strings? Intactness: did the top-ranked chunk contain them without the answer being split across that chunk and its neighbor? Grounding: does the generated answer cite a chunk that actually holds the fact? This mirrors the evaluation dimensions used in legal QA chunking research, which measured answer correctness, answer groundedness and quality of retrieved chunks against a constructed question-answer set per the IEEE conference paper. The paper also compared naive token-level, hierarchical sentence-level and recursive chunking on the same corpus, which is the right experimental shape: hold the model constant and change only the boundaries.

A worked example with numbers helps set expectations. Suppose you have 4,000 FAQ units averaging 600 characters, and a fixed-size splitter at 1,800 characters produces roughly 1,700 chunks. If 15 percent of boundaries land inside a unit, that is about 255 chunks with a cut fact. At a top-5 retrieval budget, a query hitting a damaged unit has a real chance of pulling the truncated half, because truncated chunks often score well on the question terms. Moving to question boundaries removes those 255 splits at the cost of some chunks being shorter or longer than target, which is a good trade for a fact-bearing corpus.

Fix failures at the unit, not the chunk. If a case fails intactness, the answer is almost always a multi-part answer that should have been one unit, or a condition that was written two paragraphs below its rule. You can also raise the retrieval count, but a larger candidate pool does not repair a split fact. It just gives the model more fragments to reconcile, and the arXiv work on question decomposition shows why: when the needed evidence is distributed, retrieval improves only if each piece is self-contained and then reranked, not if the pieces are halves of the same statement per the question decomposition paper.

Where this fits in a RAG pipeline

Question-boundary chunking sits in preprocessing, before embedding, and it interacts with two later stages. First, embeddings: a chunk that leads with its question has a strong lexical and semantic match to user phrasings, which matters because chunk quality directly affects retrieval accuracy per IBM's RAG overview. Second, reranking: once you retrieve a candidate pool, a reranker can prefer chunks where the full fact appears, but it cannot create the missing half. Better boundaries reduce the reranker's workload.

If your team is still assembling the pipeline itself, AWS's primer on retrieval-augmented generation walks through the retrieve-then-generate loop and where the knowledge base step sits per AWS. For a hands-on local build that shows text splitting in context, Daniel Bourke's from-scratch RAG tutorial covers preprocessing, chunking, embeddings and generation in one sequence per the tutorial. Watching a splitter misbehave on a real PDF is the fastest way to internalize why fixed windows fail.

One caution that applies throughout: do not carry over prompt-engineering habits that were tuned for plain chatbots. Aaron Tay's analysis of RAG prompting argues that techniques tested on chat models do not automatically transfer to retrieval systems, because the model is reasoning over retrieved context rather than free generation per his analysis. The same logic applies to chunking: a trick that made a chatbot answer better says nothing about where your evidence boundaries belong.

For teams that want one API key across embeddings and generation so chunking experiments do not require rewiring providers, Quanta Inference exposes text, image, video, audio and embedding models through one OpenAI-compatible, prepaid endpoint Quanta Inference. That keeps the variable under test as the boundary rule, not the vendor.

FAQ

FAQ

Should every chunk contain exactly one question?

Yes for fact-bearing corpora such as FAQs, policies and support docs. One question and its full answer is the smallest unit that can answer a user on its own. If a question has four conditions, all four belong in that chunk even if it grows past your target size.

What if a single answer is longer than my chunk limit?

Split it at sentence ends, keep at least one complete sentence per chunk, repeat the question line at the top of each part, and store unit_id and part metadata so the generator knows the parts belong together. Never split inside a number, date range or condition; move the whole protected span into one part.

Is overlap still useful with question-boundary chunking?

Use it only to repeat the question line, not the answer tail. Repeating answer text duplicates half-facts and dilutes the continuation chunk's embedding. Overlap on the question keeps continuation chunks matchable to user queries without copying content.

How many test queries do I need to trust the result?

Start with 20 to 50 real user questions, each labeled with the exact strings the answer must contain, such as a duration, a percentage and a condition. That is enough to catch systematic split failures, which tend to cluster around the same document patterns. Expand the set as new failure types appear.

Does chunk size matter less than I thought?

Chunk size matters, but NVIDIA's experiments found the best strategy and size varied by dataset and query type, with medium sizes generally beating both extremes per the NVIDIA chunking study. Treat size as a soft budget and boundary integrity as the hard constraint.

Sources

Sources

  1. 13 RAG chunking strategies for better retrieval
  2. Preventing AI Hallucinations with Effective User Prompts
  3. Finding the Best Chunking Strategy for Accurate AI ...
  4. Evaluating Retrieval Augmented Generation (RAG) ...
  5. Question Decomposition for Retrieval-Augmented ...
  6. What is RAG (Retrieval Augmented Generation)?
  7. What is RAG? - Retrieval-Augmented Generation AI ...
  8. Local Retrieval Augmented Generation (RAG) from Scratch ...
  9. Prompt engineering with Retrieval Augmented Generation ...
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles