RAG chunking playground
Drop in text, Markdown, HTML or PDF and chunk it five ways — fixed token windows with overlap, sentences, LangChain's recursive splitter, Markdown headings and semantic breakpoints — see every boundary and overlap on the text, then compare BM25, embedding and hybrid retrieval across the chunkers with recall@k and MRR against passages you mark.
Added
How to use it
How you cut documents into chunks decides what a retrieval-augmented system can find: too small and an answer is split across pieces, too big and every hit drags a page of noise into the prompt. This page chunks the same documents several ways, draws the boundaries on the text, and measures retrieval for each chunker against answers you mark. The text stays in your browser; only the libraries and the embedding model are downloaded.
How to use it
Documents. Start with the sample corpus — a Markdown runbook, a plain-text essay on lighthouse
optics and an HTML FAQ, with eleven questions whose answers are already marked — or open, drop or
paste your own .txt, .md, .html and .pdf files. HTML is turned into text with its headings
as Markdown # lines (so the Markdown chunker can use them) and list items as `- ` lines; PDF text
is extracted page by page with pdf.js (loaded from cdnjs when
you open a PDF). Your documents, queries and marks are remembered in this browser; Export JSON
saves the whole project and Import JSON brings it back.
Chunks. Set the chunk size and overlap and what they are measured in: characters (code points,
as Python’s len), words, tokens (≈ cl100k, see below) or exact cl100k_base tokens. Then pick the
chunkers to compare:
| Chunker | What it does |
|---|---|
Fixed windows |
Windows of exactly size units, each starting size − overlap units after the last — LangChain’s |
Sentences |
Whole sentences packed up to the size, carrying the last n sentences (default 1) into the next chunk. A sentence longer than the size becomes a chunk of its own. |
Recursive (LangChain) |
LangChain’s |
Markdown headings |
One chunk per section at headings up to the chosen level (code fences are skipped), with the heading path ( |
Semantic breakpoints |
Greg Kamradt’s method, as LangChain’s |
The text shows each chunk in its own colour with its number at its start; striped text is in two chunks (the overlap) and grey text in none (whitespace the splitter dropped, or a heading-only Markdown section). Tap a chunk to read it on its own. The table compares every chunker: how many chunks, their mean, median and range of lengths, how much text is duplicated by overlap, and how much is covered. The histogram shows the selected chunker’s lengths against the size; for the semantic chunker a chart shows the distances between sentences, the threshold and where it cut. Full screen (or F) gives the text the whole screen.
Retrieval. Each query is run against the chunks of every chunker with
-
BM25 — Elasticsearch’s scoring (k1 = 1.2, b = 0.75,
englishorstandardanalyzer) from the site’s search engine model; -
Embeddings — cosine similarity between all-MiniLM-L6-v2 vectors of the query and each chunk, computed in your browser (the model is downloaded once, about 23 MB);
-
Hybrid — the two rankings fused with reciprocal rank fusion, score = Σ 1 / (k + rank), k = 60 (Cormack, Clarke & Büttcher 2009).
To judge a query, select it, select the answer in the document text and press Mark selection relevant; a query can have several passages, in several documents. Because chunkers cut the same text differently, the scores are measured on those passages, not on chunk ids:
-
a chunk answers a passage when it covers at least half of the passage’s characters (the A chunk answers a passage if it covers control: any overlap, at least half, or all of it);
-
Recall@k is the share of a query’s passages answered by some chunk in the top k;
-
MRR is the mean of 1 / rank of the first chunk that answers any passage (over the top 100);
-
Context@k is the length of the top k chunks — what you would paste into the prompt;
-
Precision is the share of those characters that lie inside marked passages.
The table shows the means over the judged queries (the best recall in each column in green); tap a chunker to see its top k for the selected query, ticked when they answer, and underlined in the text with their ranks.
Tokens. The ≈ tokens unit runs cl100k_base’s own pre-tokeniser regex (from OpenAI’s tiktoken) and counts each piece of up to nine characters (a word with its leading space, a run of punctuation, up to three digits) as one token and longer pieces as one per nine characters. It is a rule of thumb: on the three sample documents and LangChain’s example text it lands within 5% of tiktoken’s count (−2% to +5%), but code, numbers-heavy text and other languages can be far off. Choose tokens (cl100k exact) to download the real vocabulary (1.1 MB from jsDelivr, as in the tokenizer explorer) and count exactly; the fixed windows then cut at real token boundaries.
Limits
-
The sentence splitter is a set of rules (stops followed by a capital, common abbreviations, list items and headings), not a trained model; it will mis-split some text. LangChain’s
SemanticChunkersplits sentences with a simpler regex, so its chunks can differ slightly. -
all-MiniLM-L6-v2 reads at most 256 word pieces; longer chunks are embedded from their start only, which handicaps big chunks in the embedding columns. It is a small English model, not the model your system uses.
-
BM25 statistics are computed per chunker over that chunker’s chunks, so idf differs between chunkers — as it would in separate indexes.
-
Recall is about whether one chunk covers enough of a passage; an answer spread over two chunks that each hold less than the threshold does not count.
-
HTML conversion is a simple tag-to-text pass (no CSS, no layout); PDF text comes out in the order pdf.js reports it, which for multi-column pages may be wrong. Scanned PDFs have no text.
-
Large corpora (several MB) work but re-chunk on every settings change; embedding thousands of chunks on the CPU takes minutes.
Checked against
The tests (tests/tools/rag-chunking.test.js) check the recursive splitter against LangChain’s
own test suite (test_iterative_text_splitter: chunk size 10, overlap 1, 18 expected chunks) and
the first chunks of its documentation example (the 2022 State of the Union with
chunk_size=100, chunk_overlap=20), the merge step against CharacterTextSplitter’s
`"foo bar baz 123" example, the token windows against split_text_on_tokens, exact token counts
against tiktoken’s cl100k_base ids, and percentiles against NumPy’s linear interpolation.
Sources: LangChain langchain-text-splitters (RecursiveCharacterTextSplitter,
CharacterTextSplitter, TokenTextSplitter, MarkdownHeaderTextSplitter) and
langchain_experimental.text_splitter.SemanticChunker; G. Kamradt, “The 5 levels of text
splitting for retrieval” (2024); S. Robertson & H. Zaragoza, “The probabilistic relevance framework:
BM25 and beyond” (2009); G. Cormack, C. Clarke & S. Büttcher, “Reciprocal rank fusion outperforms
Condorcet and individual rank learning methods” (SIGIR 2009); N. Reimers & I. Gurevych,
“Sentence-BERT” (2019) for all-MiniLM-L6-v2.
Linking to an example
A link opens the tool with the given settings: /tools/rag-chunking/?<parameters>#<tab>, where
the tab is docs, chunks or retrieve. Share link builds one from what is on screen; your
own documents are never put in a link (a link to them opens with the recipient’s documents).
| Parameter | Meaning | Example |
|---|---|---|
|
a preset (below); other parameters in the same link override it |
|
|
|
|
|
chunk size and overlap |
|
|
|
|
|
chunkers to compare, comma separated: |
|
|
sentences carried into the next sentence chunk |
|
|
Markdown: deepest heading level, split long sections again, index the heading path |
|
|
recursive separators as a JSON list |
|
|
semantic breakpoint rule ( |
|
|
top k, and the share of a passage a chunk must cover (0, 0.5, 1) |
|
|
BM25 analyzer ( |
|
|
embeddings on ( |
|
|
the chunker, document (0-based) and query shown |
|
Presets for example=:
| Name | Tab | Shows |
|---|---|---|
|
retrieve |
The sample corpus with four chunkers at 400 characters (open) |
|
retrieve |
120-character chunks and a passage must be covered whole: recall falls apart (open) |
|
retrieve |
1,500-character chunks: recall is easy, but every answer costs far more context (open) |
|
chunks |
60-token fixed windows with no overlap cut sentences mid-word (open) |
|
chunks |
128-token windows with 16 tokens of overlap (open) |
|
retrieve |
Markdown sections with their heading path indexed against the recursive splitter (open) |
|
chunks |
LangChain’s documentation example, |
|
chunks |
Semantic breakpoints at the 80th percentile of the distances on the lighthouse essay (downloads the model) (open) |
For example, ?example=sample&size=250&overlap=0&k=1#retrieve
asks how often the single best 250-character chunk holds the answer.
Feedback is a gift. Something to improve, a feature to add, or a bug? Tell me.
Runs entirely in your browser — network requests go only to the services named on this page.