DS DevShelfHub Projects · AI tools
Tutorials / LangChain / Text Splitters
LangChain Intermediate · 9 min read Page 12 of 20

Text Splitters in LangChain: Chunking Strategies for RAG

By DevShelfHub

Break large documents into chunks that fit within an LLM's context window while preserving meaning. The right splitter and chunk size have a major impact on RAG retrieval quality.

Series progress12 / 20
LangChain text splitters chunking strategies for RAG — recursive, character, token, and Markdown splitting

Why Split Documents?

LLMs have a fixed context window (e.g. 128 k tokens for GPT-4o). Embedding models also have limits — typically 512 to 8 192 tokens. Documents longer than these limits must be split into chunks before indexing.

chunk_size

Maximum characters (or tokens) per chunk. Controls granularity.

chunk_overlap

Characters repeated between adjacent chunks. Preserves context at boundaries.

separators

Ordered list of split points. Tries larger separators first.

Golden rule: chunks should be large enough to contain a complete thought, but small enough that the embedding captures one topic. Start with chunk_size=1000 and chunk_overlap=200, then tune based on your retrieval quality.

RecursiveCharacterTextSplitter

The recommended default for most use cases. It tries a list of separators in order (\n\n, \n, , "") and recurses until chunks are small enough.

python
from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    length_function=len,            # character count
    add_start_index=True,           # adds 'start_index' to metadata
)

# Split raw text
text = "LangChain is a framework for building LLM-powered applications..."
chunks = splitter.split_text(text)
print(len(chunks))  # list[str]

# Split Documents (preserves metadata)
from langchain_community.document_loaders import PyPDFLoader
pages = PyPDFLoader("paper.pdf").load()
docs = splitter.split_documents(pages)
print(docs[0].metadata)  # {'source': 'paper.pdf', 'page': 0, 'start_index': 0}

CharacterTextSplitter

Splits on a single separator only. Simpler than recursive, but can produce uneven chunks if the separator is rare.

python
from langchain_text_splitters import CharacterTextSplitter

splitter = CharacterTextSplitter(
    separator="\n\n",   # only split on double newlines
    chunk_size=1000,
    chunk_overlap=100,
)
chunks = splitter.split_text(long_text)

Use this when your text has a clear, consistent structure — like paragraphs separated by blank lines.

TokenTextSplitter

Splits by token count rather than character count. Essential when your embedding model or LLM has a token limit (not a character limit).

python
# pip3 install tiktoken
from langchain_text_splitters import TokenTextSplitter

splitter = TokenTextSplitter(
    encoding_name="cl100k_base",  # same tokenizer as GPT-4 / text-embedding-3
    chunk_size=512,               # tokens per chunk
    chunk_overlap=50,
)
chunks = splitter.split_text(text)

# Alternatively, use model name directly
splitter = TokenTextSplitter.from_tiktoken_encoder(
    model_name="gpt-4o",
    chunk_size=512,
    chunk_overlap=50,
)

When to use: When you're embedding with text-embedding-3-small (8 192 token limit) or passing chunks directly to an LLM with a tight context window. Token count is more reliable than character count for limit enforcement.

MarkdownHeaderTextSplitter

Splits Markdown documents on header boundaries and adds header text to each chunk's metadata. Ideal for documentation sites and wikis.

python
from langchain_text_splitters import MarkdownHeaderTextSplitter

headers_to_split_on = [
    ("#", "h1"),
    ("##", "h2"),
    ("###", "h3"),
]

splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
docs = splitter.split_text(markdown_text)

# Each doc contains header info in metadata
# metadata: {'h1': 'Introduction', 'h2': 'Installation'}
# page_content: the section text under that header
print(docs[0].metadata)

# Chain with recursive splitter for large sections
from langchain_text_splitters import RecursiveCharacterTextSplitter
secondary = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
final_docs = secondary.split_documents(docs)

Splitter Comparison

Splitter Best For Split Unit
RecursiveCharacterTextSplitterGeneral text (default choice)Characters
CharacterTextSplitterStructured paragraphsCharacters
TokenTextSplitterLLM / embedding token limitsTokens
MarkdownHeaderTextSplitterMarkdown docs / wikisHeaders
HTMLHeaderTextSplitterHTML pagesHTML tags
SemanticChunkerMeaning-based splitsEmbedding similarity

Full Load → Split Pipeline

Splitting sits between loading and embedding in a RAG pipeline. Start by reading source files with Document Loaders, split the resulting documents here, then turn each chunk into a vector with Embeddings before storing them for retrieval.

python
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

# 1. Load
loader = PyPDFLoader("handbook.pdf")
pages = loader.load()
print(f"Loaded {len(pages)} pages")

# 2. Split
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    add_start_index=True,
)
chunks = splitter.split_documents(pages)
print(f"Split into {len(chunks)} chunks")

# 3. Embed and store
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(chunks, embeddings)
print("Indexed successfully")

LangChain Text Splitters FAQ

What chunk size should I use for text splitting in RAG?

Start with a chunk_size of 1000 characters and a chunk_overlap of 200, then tune from there. Chunks that are too large dilute the embedding so precise facts get lost; chunks that are too small lose surrounding context. A good chunk holds one complete thought while staying focused on a single topic so the embedding stays sharp.

What is chunk overlap and why does it matter?

chunk_overlap is the number of characters or tokens repeated between adjacent chunks. It prevents an answer from being split across a chunk boundary and lost, because the overlapping window keeps shared context in both neighbouring chunks. A common starting point is 10 to 20 percent of chunk_size, such as 200 overlap for a 1000 character chunk.

What is the difference between RecursiveCharacterTextSplitter and CharacterTextSplitter?

RecursiveCharacterTextSplitter tries an ordered list of separators (paragraphs, then lines, then spaces, then characters) and recurses until each chunk fits, so it rarely cuts mid-sentence and is the recommended default. CharacterTextSplitter splits on a single separator only, which is simpler but can produce very uneven chunks when that separator is rare in the text.

Should I split text by tokens or by characters?

Split by characters with RecursiveCharacterTextSplitter for general prose because it is fast and predictable. Split by tokens with TokenTextSplitter when you must respect a model token limit, such as an embedding model with an 8192 token window or an LLM with a tight context window, since token count enforces those limits far more reliably than character count.

How do I split code or Markdown documents in LangChain?

For Markdown use MarkdownHeaderTextSplitter to split on header boundaries and store each heading in metadata, then optionally re-split large sections with RecursiveCharacterTextSplitter. For source code, use RecursiveCharacterTextSplitter.from_language with the right Language enum so it splits on function and class boundaries instead of arbitrary characters, which keeps code blocks intact.

Why is chunking so important for RAG retrieval quality?

Retrieval matches the query embedding against chunk embeddings, so the way you chunk directly controls what the model can find. Well sized, topically focused chunks produce sharp embeddings and precise retrieval, while oversized or fragmented chunks blur the signal and surface irrelevant passages. Tuning chunk_size, chunk_overlap, and the splitter is often the highest leverage fix for poor RAG accuracy.

Quick jump: API Reference