Skip to content

Chunking

Chunking splits documents into retrievable segments. The right chunk size balances context (too small loses meaning) and precision (too large retrieves irrelevant text). This is the most underrated step in RAG — bad chunking breaks retrieval.

Table of Contents

Installation / Setup

pip install langchain langchain-text-splitters    # or: uv add langchain-text-splitters

Core Syntax

Chunking strategies

Strategy How it works Best for
Fixed-size Split every N characters/tokens Simple, predictable
Recursive character Split on \n\n → \n → . → General purpose (best default)
Semantic Split when meaning changes High quality, slower
Document-aware Respect headers, code blocks, tables Structured documents
Markdown-aware Split on markdown headers Markdown files
Code-aware Split on function/class boundaries Code files
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,        # max characters per chunk
    chunk_overlap=200,      # overlap between chunks
    separators=["\n\n", "\n", ". ", " ", ""],   # split priority
    length_function=len,    # or use tiktoken for token counting
)

chunks = splitter.split_text(document)

Token-based splitting

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.callbacks import get_openai_callback

# split by tokens (more accurate for LLM context windows)
splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="cl100k_base",
    chunk_size=512,         # tokens, not characters
    chunk_overlap=50,
)

Markdown-aware splitting

from langchain.text_splitter import MarkdownTextSplitter

splitter = MarkdownTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
)
# respects ## headers as split points

Code-aware splitting

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_text_splitters import Language

splitter = RecursiveCharacterTextSplitter.from_language(
    language=Language.PYTHON,
    chunk_size=1000,
    chunk_overlap=200,
)
# splits on function/class boundaries

Common Operations

Chunk size decision table

Use case Chunk size (tokens) Overlap Why
Q&A over docs 500–1000 100–200 Balance context and precision
Summarization 1000–2000 200 Need more context per chunk
Code retrieval 200–500 50 Functions should stay together
Legal/contracts 500–800 100 Preserve clause boundaries
Chat messages 100–300 20 Short, focused context

Overlap rules

chunk_overlap should be 10–20% of chunk_size

chunk_size=1000 → chunk_overlap=100–200
chunk_size=500  → chunk_overlap=50–100
chunk_size=250  → chunk_overlap=25–50

Overlap prevents: - Important context split across chunk boundaries - Losing the "connection" between adjacent chunks

Document-type chunking

# PDF with sections
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

docs = PyPDFLoader("paper.pdf").load()
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000, chunk_overlap=200,
    separators=["\n\n\n", "\n\n", "\n", ". "],  # respect double newlines
)
chunks = splitter.split_documents(docs)
# Markdown with headers
from langchain.text_splitter import MarkdownTextSplitter

splitter = MarkdownTextSplitter(chunk_size=1500, chunk_overlap=200)
chunks = splitter.split_text(markdown_text)

Practical Examples

Example 1 — Compare chunk sizes

from langchain.text_splitter import RecursiveCharacterTextSplitter

text = open("document.md").read()
for size in [256, 512, 1000, 2000]:
    splitter = RecursiveCharacterTextSplitter(chunk_size=size, chunk_overlap=size//10)
    chunks = splitter.split_text(text)
    print(f"chunk_size={size}: {len(chunks)} chunks, avg {sum(len(c) for c in chunks)//len(chunks)} chars")

Example 2 — Chunk with metadata preservation

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = splitter.split_documents(documents)

# add chunk index metadata
for i, chunk in enumerate(chunks):
    chunk.metadata["chunk_index"] = i
    chunk.metadata["total_chunks"] = len(chunks)

Example 3 — Semantic chunking

# semantic chunking splits when meaning changes
# (requires an embedding model)
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings

splitter = SemanticChunker(
    OpenAIEmbeddings(),
    breakpoint_threshold_type="percentile",
    breakpoint_percentile_threshold=85,
)
chunks = splitter.split_text(document)

Common Patterns

Pattern: hierarchical chunking

# keep parent document for context, retrieve smaller chunks
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain.text_splitter import RecursiveCharacterTextSplitter

parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=200)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=InMemoryStore(),
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)
retriever.add_documents(documents)
# retrieves small chunks but returns full parent documents

Pattern: test chunking quality

def evaluate_chunks(chunks, sample_queries):
    """Check if chunks contain answerable information."""
    for q in sample_queries:
        found = False
        for c in chunks:
            if any(word in c.page_content.lower() for word in q.lower().split()):
                found = True
                break
        print(f"{'✓' if found else '✗'} {q}")

Best Practices

  • Recursive character splitter is the best default — handles most document types.
  • Chunk size 500–1000 tokens is the sweet spot for most RAG systems.
  • Overlap 10–20% prevents context loss at boundaries.
  • Test with real queries — chunk quality is measured by retrieval success.
  • Use document-aware splitting when structure matters (headers, code, tables).
  • Token-based splitting is more accurate than character-based for LLM context.
  • Metadata preservation — keep source, section, and page info in each chunk.
  • Hierarchical chunking — retrieve small chunks, return full context.

Common Mistakes

Mistake Fix
chunk_size too large (>2000 tokens) Reduces retrieval precision — use 500–1000
chunk_size too small (<200 tokens) Loses context — chunks become meaningless
No overlap Add 10–20% overlap to prevent context loss
Ignoring document structure Use Markdown/code-aware splitters
Same chunk_size for all documents Tune per document type
Not testing with real queries Validate chunk quality against sample questions

Troubleshooting

Symptom Cause / Fix
Chunks too small Increase chunk_size
Chunks cut mid-sentence Use document-aware splitter
Poor retrieval quality Chunk size may be wrong — test different sizes
Too many chunks Increase chunk_size or reduce document count
Memory issues Process documents in batches

Official Documentation

Last Updated

2026-09-06 — Initial version.