Chunking: Getting the First Cut Right A developer's guide to RAG chunking argues that the document-splitting step is the most commonly rushed and most damaging part of a retrieval pipeline, since errors there propagate downstream into embeddings and retrieval. It compares three approaches — fixed-size, recursive, and semantic chunking — with code samples for each, noting that semantic chunking uses embedding cosine distances and a percentile threshold to cut where topics shift rather than at arbitrary character counts. Let's start with an inconvenient truth: your carefully built RAG system, the one you're quietly proud of, might be sabotaging itself at step one. Not at retrieval. Not at generation. At the very first thing that happens to your document, the cut . Chunking sounds boring. It's the "take a big document, slice it into smaller pieces" step, and it's usually the part we rush through to get to the "fun" stuff like embeddings and vector databases. But here's the thing nobody tells loudly enough: get this step wrong, and everything downstream is trying to fix a problem that got created on page one. A 40-page PDF is a lot to ask an embedding model to capture in a single vector. Embedding models have limits, and even without those limits, cramming a whole document into one vector means everything specific gets averaged into mush. So documents get broken down into smaller, digestible segments called chunks, which then get embedded and stored so a retrieval system can search and pull back only the relevant piece instead of the whole haystack. In short: chunking exists because "smaller and specific" beats "big and vague" when you're trying to find a needle. The argument isn't about whether to chunk, rather it's about how . If you've read literally any RAG tutorial, you've met these three: Fixed-size chunking : the "just cut every N characters" approach. Fast, simple, and about as thoughtful as slicing a birthday cake with your eyes closed. If a sentence happens to cross the 512-token limit, well... it doesn't get much of a say in where the cut lands. js import { CharacterTextSplitter } from "@langchain/textsplitters"; const splitter = new CharacterTextSplitter { separator: "", // no separator to respect, just slice chunkSize: 512, chunkOverlap: 50, } ; const documents = await splitter.createDocuments documentText ; Recursive chunking : a slightly more polite cousin. It tries to split along natural boundaries first like paragraphs, then sentences, then lines and only falling back to a hard cut if it absolutely has to. Think of it as tearing a loaf of bread along the lines where it naturally wants to break, instead of taking a knife to it wherever you feel like. js import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters"; const splitter = new RecursiveCharacterTextSplitter { chunkSize: 100, chunkOverlap: 20, separators: "\n\n", "\n", " ", "" , // tried in this order } ; const documents = await splitter.createDocuments documentText ; Semantic chunking : the fancy one. Instead of counting characters, it uses a model to notice where the topic shifts, and cuts there instead, so each chunk stays thematically whole rather than just structurally intact. python import OpenAI from "openai"; const openai = new OpenAI ; function cosineDistance a: number , b: number : number { const dot = a.reduce sum, v, i = sum + v b i , 0 ; const magA = Math.sqrt a.reduce sum, v = sum + v v, 0 ; const magB = Math.sqrt b.reduce sum, v = sum + v v, 0 ; return 1 - dot / magA magB ; } function percentile values: number , p: number : number { const sorted = ...values .sort a, b = a - b ; const idx = Math.floor p / 100 sorted.length - 1 ; return sorted idx ; } async function semanticChunk sentences: string , percentileThreshold = 95 : Promise