conceptRetrieval y Conocimiento~3 min de lecturaActualizado 2026-06-10#rag#chunking#preprocessing
Esta nota todavía no está traducida, así que se muestra la fuente en inglés.

Chunking that respects structure

Mental model: the chunk is the unit of retrieval, so chunking sets a hard ceiling on RAG quality — a retriever can only return what chunking produced, and a great reranker cannot resurrect an answer that was split across two chunks. It is the most under-invested, highest-leverage stage of the pipeline.

The size trade-off, concretely

  • Too small (≲150 tokens) — facts get severed from their referents ("it improves throughput by 40%" — what does?); you need more chunks per answer, so recall must be higher everywhere.
  • Too large (≳1,500 tokens) — one matching sentence is diluted by a wall of off-topic text, so embedding similarity drops; retrieved chunks waste context budget and bury the answer (lost in the middle).

There is no universal optimum. Defensible 2026 starting points: 400–800 tokens for prose, with 10–20% overlap; whole functions/classes for code; row-groups with repeated headers for tables. Anthropic's Contextual Retrieval experiments used "a few hundred tokens" per chunk. Treat size as a tunable hyperparameter and measure recall@k across 2–3 candidate sizes — an afternoon of work that outperforms any blog's recommendation.

The strategy ladder

Strategy What it does When
Fixed-size + overlap split every N tokens never as final choice; only as baseline
Recursive split on \n\n\n → sentence → token, biggest unit that fits good default for plain prose
Structure-aware split on headings/sections/code blocks; never cut mid-table or mid-function the production default
Semantic embed sentences, cut where adjacent similarity drops rarely worth it (below)
Late chunking embed full doc with a long-context embedder, then pool per chunk long-context embedding models only

On semantic chunking: Qu et al. 2024 (arXiv:2410.13070) found its gains over simple splitting are inconsistent and often fail to justify the compute — fix structure-aware splitting and chunk context first. Late chunking (Günther et al., Jina 2024, arXiv:2409.04701) attacks the same problem (chunks losing document context) at embedding time instead of text time.

Structure-aware splitting in TypeScript

Markdown-aware splitter that keeps heading context attached to every chunk — the part that matters more than the exact token count:

type Chunk = { heading: string; text: string; ordinal: number };

export function chunkMarkdown(doc: string, maxTokens = 700): Chunk[] {
  // crude but adequate token estimate for splitting purposes (~4 chars/token)
  const tokens = (s: string) => Math.ceil(s.length / 4);
  const sections = doc.split(/^(?=#{1,3} )/m); // split BEFORE each heading, keep it
  const chunks: Chunk[] = [];
  let ordinal = 0;

  for (const section of sections) {
    const heading = section.match(/^#{1,3} (.*)$/m)?.[1] ?? "";
    if (tokens(section) <= maxTokens) {
      if (section.trim()) chunks.push({ heading, text: section.trim(), ordinal: ordinal++ });
      continue;
    }
    // section too big: split on blank lines between blocks
    const blocks = section.split(/\n\n+/);
    let buf = "";
    for (const block of blocks) {
      if (tokens(buf + block) > maxTokens && buf) {
        chunks.push({ heading, text: buf.trim(), ordinal: ordinal++ });
        buf = "";
      }
      buf += block + "\n\n";
    }
    if (buf.trim()) chunks.push({ heading, text: buf.trim(), ordinal: ordinal++ });
  }
  return chunks;
}

(Production caveat: a regex splitter like this will cut inside fenced code blocks — use a Markdown AST parser such as remark to treat fences as atomic blocks.)

Store heading and ordinal with each chunk (schema): the heading gives the chunk cheap context and a citation label; the ordinal lets you fetch neighboring chunks at answer time ("small-to-big" retrieval: match on small chunks, hand the LLM the surrounding window).

Add context to chunks

A standalone chunk often loses what it refers to. Two proven fixes:

  • Cheap and always-on: prepend Document: <title> > <section heading> to the text you embed (not necessarily to the text you display).
  • Contextual retrieval (Anthropic 2024): generate a 50–100-token, chunk-specific context with a small model and prepend it before embedding and before BM25 indexing. Cuts top-20 retrieval failures 35% alone, 49% combined with contextual BM25 (one-time cost ≈ $1.02 per million document tokens with prompt caching). Implementation in advanced patterns.

Failure modes

  • Mid-structure cuts — half a table, half a code block, a step list split at step 3. These chunks are unretrievable garbage. Structure-aware splitting exists to prevent exactly this.
  • Boundary-straddling facts — overlap mitigates but doesn't cure; if evals show misses on multi-paragraph answers, retrieve neighbors via ordinal instead of growing chunk size.
  • One config for heterogeneous corpora — FAQs, API references, and long-form policies need different chunkers. Route by document type.
  • Re-chunking churn — changing chunking invalidates the whole index and your eval baselines. Version the chunker config alongside the index; re-run the eval set before/after.

In practice

When retrieval quality disappoints, read 20 retrieved chunks before touching anything else. Mid-sentence cuts, missing referents, and headerless table fragments are visible to the naked eye — and no embedding model upgrade fixes them. Chunk inspection is the highest-ROI debugging step in all of RAG (failure modes).

Connects to: embeddings · contextual retrieval · failure modes · first-pass schema

Sources