Semantic & Sliding-Window Chunking
Introduction
The last lesson's three methods — fixed, recursive, sentence — all share a blind spot: they use the document's structure (counts, separators, sentence breaks) as a proxy for meaning. When structure and meaning line up, that's cheap and effective. When they don't — a topic shifts mid-paragraph, or your text has no usable headings at all — structure stops being a good proxy.
This lesson covers two ideas that go further, plus one of the most valuable honest truths in all of RAG:
- Semantic chunking — split by meaning itself, detecting topic boundaries from embeddings.
- Sliding-window & sentence-window — decouple what you embed from what you return.
- The truth most courses won't tell you: embedding-based semantic chunking is overrated. Peer-reviewed benchmarks show it usually ties or loses to plain recursive on real documents. You'll learn exactly when it's worth the cost — and when it isn't.
Three of these terms are constantly conflated. The single most useful thing you'll leave with is being able to keep them straight — so let's map them.
The Map: Three Ideas People Constantly Confuse
Before the mechanics, lock in the distinctions — this clarity is the lesson:
- Semantic (embedding-based) chunking — an index-time strategy that decides where to cut from content: a sharp drop in embedding similarity between consecutive sentences marks a topic boundary.
- Sliding-window chunking — just fixed-size with overlap, restated: cut every K tokens, advance the window by a stride S, so overlap = K − S. (Do not confuse this with sliding-window attention, an unrelated Transformer architecture trick.)
- Sentence-window retrieval — a retrieval-time technique, not a chunking method: you embed one sentence (for precision) but return a window of neighboring sentences (for context). It decouples retrieval granularity from generation granularity.
And here's the framing that ties the whole section together: these techniques layer, they don't compete. Where you cut (chunking) → how the vector is pooled (late chunking, L94) → what text is added to the chunk (contextual retrieval, L94) → what's sent to the LLM (parent-child / sentence-window, L93). A serious pipeline can use several at once.

Semantic Chunking: Cut Where the Meaning Shifts
The first principle is intuitive: consecutive sentences about the same topic have similar embeddings; a topic change shows up as a sudden drop in similarity. Semantic chunking turns that observation into boundaries, in four steps:
- Split the text into sentences.
- Embed each sentence — optionally a small window of neighbors together (a denoising trick; see
buffer_size). - Measure the cosine distance (
1 − cosine similarity) between each consecutive pair. - Cut wherever that distance exceeds a threshold — i.e., the meaning jumped.
Both major libraries implement exactly this:
# LangChain — embedding-based semantic chunking (note: lives in langchain-experimental)
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
chunker = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile", # also: standard_deviation | interquartile | gradient
breakpoint_threshold_amount=95, # cut where distance > the 95th-percentile distance
buffer_size=1, # embed a 3-sentence window (denoising), not one lone sentence
)
chunks = chunker.split_text(text)
# ⚠️ No max-size cap by default: a long coherent passage becomes ONE oversized chunk.
# Add a floor with min_chunk_size; add a ceiling by post-splitting with RecursiveCharacterTextSplitter.
# LlamaIndex — same 4 steps, percentile method only
from llama_index.core.node_parser import SemanticSplitterNodeParser
parser = SemanticSplitterNodeParser(
buffer_size=1, breakpoint_percentile_threshold=95, embed_model=embed_model)The promise: chunks that each hold one coherent idea, regardless of where paragraph breaks happen to fall. Whether that promise pays off in practice is the crucial question we'll get to — but first, watch the mechanism work.
See It: The Semantic Breakpoint Detector
Below, each sentence shows its similarity to the next one. Drag the stay-together threshold: neighbors less similar than the threshold become a breakpoint (a topic shift) and start a new chunk. Start low — everything is one chunk; raise it and watch breakpoints appear first at the biggest meaning shifts, then start over-splitting within a topic:

Two honest clarifications the widget makes concrete — and that trip up most people:
- The real threshold is relative, not absolute. Production tools (LangChain/LlamaIndex
percentile) cut at a percentile of this document's own distance distribution — so a perfectly uniform, single-topic document still gets split at its 95th-percentile gap, whether or not a real boundary exists. The widget uses a fixed cutoff for clarity; the mechanism is identical. - Higher percentile → fewer, larger chunks (counterintuitive): raising the bar means only the very biggest jumps qualify as boundaries.
buffer_sizedenoises — it doesn't peek ahead. It embeds a sentence together with its neighbors (defaultbuffer_size=1→ a 3-sentence window) because lone-sentence vectors are noisy. Bigger buffer → smoother distance curve → fewer, coarser cuts.
The Knobs and the Traps
If you do use semantic chunking, these are the settings that decide everything — and the failure modes that bite:
Threshold types (LangChain offers four; LlamaIndex only percentile):
percentile(default 95) — cut at the 95th-percentile distance. Simple, but cuts a fixed fraction whether or not boundaries exist.standard_deviation(default 3) — cut atmean + 3·std. Often more predictable than percentile on real text (distances are roughly positively-skewed-normal).interquartile(default 1.5) — robust to outliers.gradient(default 95) — built for documents where adjacent sentences are uniformly highly similar (legal, medical): no single raw distance ever crosses a threshold, so percentile/std find no spikes — but a relative jump in the rate of change still flags the boundary. It's anomaly detection on the derivative.
The traps:
- No native max size. It splits only on breakpoints, so a long coherent passage becomes one oversized chunk — and isolated jumps become micro-fragments. Add
min_chunk_sizefor a floor and post-split withRecursiveCharacterTextSplitterfor a ceiling. - The threshold is brittle — corpus-, model-, and domain-specific. There's no universal value; you must tune on your data and eval.
- It's only as good as your sentence splitter. Garbage sentence boundaries (naive
.?!on "Dr.", "U.S.", decimals, code) → noisy embeddings → meaningless breakpoints. - It lives in
langchain-experimental— not version-pinned; treat the defaults as a moving target.
The Honest Truth: Is Semantic Chunking Worth It?
Here's what sets a real practitioner apart from a tutorial-follower. Semantic chunking sounds obviously better — so most courses oversell it. The peer-reviewed evidence says it's usually not worth the cost.
NAACL 2025 — "Is Semantic Chunking Worth the Computational Cost?" (Vectara-led; 15 datasets, 3 tasks) found:
- Semantic wins only on stitched corpora — short, unrelated documents artificially concatenated to manufacture hard topic boundaries (Miracl* 81.9% vs fixed-size 69.5%). That is not what a single coherent document looks like.
- On natural documents, fixed-size ties or wins (HotpotQA 90.6% vs 87.4%; MSMARCO 93.6% vs 92.2%).
- End-to-end answer quality is identical — BERTScore matched across methods.
- The embedding model swamps the chunker: a better model gave +7.44% F1 — larger than any gap between chunking strategies. The paper's verdict: "fixed-size chunking remains a more efficient and reliable choice for practical RAG."
Chroma's Evaluating Chunking study agrees: a classic embedding-based semantic chunker scored 83.6% recall — below plain RecursiveCharacterTextSplitter's 88.1%. The thing that actually moved recall most? Chunk size and avoiding bad defaults, not semantic-vs-recursive.
But "semantic chunking" is an overloaded term — and everything above indicts embedding-similarity breakpoint chunking specifically (the cheap method LangChain/LlamaIndex ship and that the interactive demonstrates). LLM-judged and cluster-based chunkers are different, pricier beasts: in that same Chroma study, an LLM chunker (GPT-4o) scored the best recall of all — 91.9%, beating recursive's 88.1% — and a cluster-based chunker had the best precision. So the "overrated" label targets the embedding-breakpoint method you reach for by default — not every technique with "semantic" in its name.
And the cost is real: semantic chunking must embed every sentence at index time before placing a single boundary — one production report measured it ~4× slower to index than a hierarchical approach.
Decision rule. Default to recursive (~512 tokens, 10–20% overlap) or sentence-window retrieval. Reach for semantic chunking only when all three hold: (a) your corpus is unstructured and heterogeneous (no headings, mixed prose/tables/code), (b) your own eval (recall / IoU / RAGAS) shows recursive underperforming, and (c) the index-time cost is acceptable. Spend your effort budget on higher-ROI levers first: a better embedding model, metadata enrichment, contextual retrieval, late chunking, reranking. Semantic chunking is near the bottom of that list.
Sliding-Window & Sentence-Window Retrieval
Sliding-window chunking is just overlap, named differently. Slide a window of size K across the text, advancing by a stride S each step; the shared region between neighbors is the overlap = K − S. S = K → no overlap (plain fixed chunking); S < K → overlapping chunks. "Use a sliding window" and "add overlap" are the same decision — and, as the last lesson noted, overlap isn't always a free win (a Jan-2026 study found it added no measurable benefit while inflating the index by 1/(1−overlap)). Measure, don't assume.
Sentence-window retrieval is the genuinely clever idea here — and it quietly escapes the Goldilocks tradeoff from L90 (small chunks = precise but context-starved; large chunks = full context but a diluted vector). The move: embed and match on a single sentence (maximal precision), but return a window of the surrounding sentences to the LLM (full context). You get a sharp, undiluted vector and enough context to answer — because retrieval granularity and generation granularity no longer have to be the same number.
from llama_index.core.node_parser import SentenceWindowNodeParser
from llama_index.core.postprocessor import MetadataReplacementPostProcessor
# INDEX: embed each sentence, but stash a window of 3 sentences on EACH side (7 total) in metadata.
parser = SentenceWindowNodeParser.from_defaults(window_size=3) # code default = 3 (the prose docs say 5 — wrong)
# QUERY: match the precise single sentence, then SWAP IN its full window before the LLM sees it.
query_engine = index.as_query_engine(
similarity_top_k=2,
node_postprocessors=[MetadataReplacementPostProcessor(target_metadata_key="window")],
)
# ⚠️ Forget the postprocessor and the LLM gets only the lone matched sentence — the entire benefit vanishes silently.This is the retrieval-time cousin of the index-time sliding window, and a direct preview of parent-child / small-to-big retrieval (the next lesson), which generalizes the idea from "a window of sentences" to "the whole parent section" — both escape that same size tradeoff. One caution the docs get wrong: the code default is window_size=3 (3 sentences each side = a 7-sentence window), not the "5 either side" the prose claims — and if you forget the MetadataReplacementPostProcessor, the LLM silently receives only the lone matched sentence and the entire benefit disappears.
🧪 Try It Yourself
Drive the breakpoint detector, then decide like an engineer:
- In the widget, set the threshold very low, then very high. At which end do you get one giant chunk, and at which do you get one chunk per sentence? Find the value that yields exactly the three real topics.
- Your corpus is 10,000 well-structured Markdown docs with clear headings. A teammate wants to switch from recursive to semantic chunking "because it's smarter." What do you say — and what do you ask them to produce first?
- You're building QA over messy mixed-format Notion exports (prose + tables + bullets, no heading hierarchy) and recursive's separators have nothing to grab. Is this a reasonable case for semantic chunking? What must you still add to it?
- Small chunks retrieve precisely but the LLM keeps giving incomplete answers for lack of context. Name the technique that fixes this without changing your chunk size.
→ (1) Low threshold → one giant chunk (no gap is 'big enough' to cut); high → over-split toward one-per-sentence; the mid value cuts only at the genuine topic shifts. (2) Push back: on structured natural docs, recursive ties or beats semantic (NAACL 2025) at a fraction of the index cost — ask them to run a retrieval eval (recall/IoU) proving recursive underperforms before paying for it. (3) Yes — unstructured and heterogeneous is semantic chunking's actual niche; but still add a min/max size cap (post-split oversized chunks) and validate on retrieval metrics, not on prettier boundaries. (4) Sentence-window retrieval (or parent-child) — embed the small sentence, return the surrounding window; precision and context, no size change.
Mental-Model Corrections
- "Sliding window vs overlap" is a false choice.
overlap = K − S; the sliding window is the procedure that produces the overlap. Same decision. - Sentence-window retrieval ≠ sliding-window chunking. Sentence-window decouples what you embed (1 sentence) from what you return (an N-sentence window) at retrieval time; sliding-window chunking embeds and returns the same overlapping object.
breakpoint_percentile_thresholdis not a fixed cosine cutoff. It's a percentile of this document's own distances — a uniform doc still gets split.- Higher distance percentile → fewer, larger chunks — counterintuitive but key.
buffer_sizedenoises the embedding; it is not a peek-ahead for where to cut.- Embedding-based semantic chunking is usually not worth it. It mostly ties/loses to recursive on natural docs and costs ~4× more to index; the embedding model matters far more. It's a niche tool (unstructured + heterogeneous), not a default.
- "Semantic chunking" is overloaded. Embedding-breakpoint chunking (overrated) is not the same as LLM-judged or cluster-based chunking — those can actually win (Chroma's top-recall chunker was the expensive LLM one, beating recursive). The "overrated" verdict is about the embedding-breakpoint method only.
- Don't confuse sliding-window chunking with sliding-window attention (a Transformer architecture concept).
- Overlap isn't always beneficial — it can just inflate the index with near-duplicates. Measure it.
Key Takeaways
- Semantic chunking cuts where meaning shifts — embed sentences, take cosine distance between neighbors, break where it spikes; the threshold is a percentile of the doc's own distances (not a fixed cutoff), and higher percentile = fewer, larger chunks.
- The embedding-breakpoint method is overrated. Peer-reviewed (NAACL 2025) and Chroma benchmarks show it ties or loses to recursive on natural documents, costs ~4× more to index, and is swamped by embedding-model quality (+7.44% F1). Default to recursive ~512 / 10–20% overlap; reach for it only when your corpus is unstructured and heterogeneous and your eval proves it. (LLM-judged / cluster-based chunkers are a different, pricier story — they can win.)
- Sliding-window = fixed-size + overlap (
overlap = K − S); overlap isn't always a win — measure it. Don't confuse it with sliding-window attention. - Sentence-window retrieval escapes the size tradeoff: embed one sentence (precise), return a window (context) — retrieval granularity ≠ generation granularity. Needs the metadata-replacement step or the benefit vanishes.
- Spend your budget on higher-ROI levers first (embeddings, metadata, contextual retrieval, late chunking, reranking).
- Next: parent-child / small-to-big retrieval — the full generalization of "match small, return big."