Document is text plus metadata. Before you can embed and store them, long documents need to be split into chunks small enough for the embedder’s context window and small enough that retrieval surfaces relevant pieces, not whole files.
What a document is
Document loaders produce them; splitters transform them.
Loading documents
TheDocumentLoader trait is one async method that returns a stream of Document. Built-in loaders are feature-gated:
Implement
DocumentLoader for sources that aren’t in the box (databases, APIs, S3 buckets):
Splitters
All splitters implementTextSplitter:
split returns chunks for one doc; split_all is a convenient wrapper for many.
Pick a splitter
chunk_size, overlap, and a separators list.
How chunks land in retrieval
Each chunk inherits its parent’s metadata, plus a position-in-parent marker. So when you retrieve a chunk, you also know:- Which document it came from (via metadata, especially if you set
with_id). - Roughly where in that document it sits.
- Any custom metadata you attached.
Tuning chunk size
Two competing forces:- Smaller chunks = sharper retrieval (the right idea, not surrounding noise) but less context for the LLM to reason from.
- Larger chunks = more context but worse signal-to-noise — the embedding averages over a lot of unrelated text.
Validate with retrieval evals on your own corpus — there’s no universal right answer.
How it works
- Splitting is lossless. No characters disappear; overlap means neighboring chunks share a window.
- Document
idis preserved through splits. Each chunk gets its own derived id (so you can de-duplicate later) but knows its parent. - Splitters don’t know about embedders. That decoupling means you can switch embedders without re-splitting.
See also
Embeddings and vector stores
Turn chunks into vectors, store, search.
Indexing pipeline
Keep your store in sync with the source of truth.
Patterns → Code Q&A
A worked end-to-end RAG over a Rust codebase.