Document Chunking#
Docling's chunking system converts a DoclingDocument into a sequence of DocChunk objects, each bounded by a configurable token limit and enriched with provenance metadata. The pipeline is multi-stage: a structural pass follows the document hierarchy, a splitting pass enforces token constraints, and an optional merge pass consolidates undersized adjacent chunks. The primary entry point is HybridChunker, which orchestrates all three passes.
See also: PDF-to-RAG Pipeline (end-to-end workflow) and Document Chunk Metadata and Provenance (data model detail).
Pipeline Architecture#
DoclingDocument
│
▼
HierarchicalChunker ← Stage 1: document-structure split
(doc_chunk.py / hierarchical_chunker.py)
│ DocChunk[]
▼
_split_by_doc_items() ← Stage 2a: split oversize multi-item chunks
│
▼
_split_using_plain_text() ← Stage 2b: semchunk / LineBasedTokenChunker
│
▼
_merge_chunks_with_matching_metadata() ← Stage 3 (merge_peers=True)
│
▼
Iterator[DocChunk]
The full pipeline is wired in HybridChunker.chunk().
Stage 1 — HierarchicalChunker#
HierarchicalChunker walks the document tree using ChunkingDocSerializer to serialize each item to text, grouping content under the current heading context. It emits DocChunk objects at three points:
- Content chunks — the main path;
doc_itemsis populated fromser_res.spans,headingsfrom the current heading stack, andoriginfromdl_doc.origin. - Heading-only chunks — emitted when
always_emit_headings=Trueand a heading goes out of scope without body content. - Trailing headings — any unmatched headings remaining at document end.
The default serializer is ChunkingDocSerializer, which extends MarkdownDocSerializer and uses TripletTableSerializer for tables by default. A custom ChunkingSerializerProvider can substitute any BaseTableSerializer.
Stage 2 — Token-Aware Splitting#
After the hierarchical pass, HybridChunker applies two token-enforcement steps :
2a. Split by doc items#
_split_by_doc_items() uses a sliding window over doc_items in an oversize chunk, greedily expanding the window until adding one more item would exceed max_tokens. The result preserves headings and origin in each sub-chunk's DocMeta .
2b. Semantic / line-based splitting#
_split_using_plain_text() handles single items that are still too large. For tables (when repeat_table_header=True), it delegates to LineBasedTokenChunker which re-prefixes each body chunk with the header row. For all other text, it uses semchunk — a semantic text splitter — with the available token budget calculated by subtracting the heading/caption overhead from max_tokens.
Stage 3 — Peer Merging#
When merge_peers=True (the default), _merge_chunks_with_matching_metadata() consolidates consecutive undersized chunks that share the same headings into a single DocChunk, accumulating their doc_items and joining their text with self.delim. Merging stops when the token count would exceed max_tokens or the heading context changes .
Output: DocChunk / DocMeta#
Every emitted chunk is a DocChunk(text, meta: DocMeta). The DocMeta fields are:
| Field | Type | Notes |
|---|---|---|
doc_items | list[DocItem] | ≥ 1; source nodes covered by this chunk |
headings | Optional[list[str]] | Section headings in scope at chunk time |
origin | Optional[DocumentOrigin] | File-level metadata: filename, mimetype, binary_hash |
captions | Optional[list[str]] | Deprecated |
excluded_embed and excluded_llm class-level lists ensure that doc_items, schema_name, version, and origin are omitted when contextualize() builds the embedding string — only headings flows into the embedding payload by default .
For the full provenance chain (DocItem.prov → ProvenanceItem → page_no / bbox / charspan), see Document Chunk Metadata and Provenance.
HybridChunker Configuration#
HybridChunker is importable as from docling.chunking import HybridChunker or from docling_core.transforms.chunker.hybrid_chunker import HybridChunker . Requires pip install docling-core[chunking] .
| Parameter | Default | Effect |
|---|---|---|
tokenizer | HuggingFaceTokenizer("all-MiniLM-L6-v2") | Token counter; must match embedding model |
max_tokens | derived from tokenizer | Hard cap per chunk |
merge_peers | True | Enable Stage 3 merge pass |
repeat_table_header | True | Re-prefix each table sub-chunk with header row |
omit_header_on_overflow | False | Drop header when row + header exceeds budget |
serializer_provider | ChunkingSerializerProvider() | Plug in custom table serialization |
always_emit_headings | False | Emit heading-only chunks for empty sections |
Tokenizer options: HuggingFaceTokenizer (default) and OpenAITokenizer (requires [chunking-openai]) both implement BaseTokenizer (count_tokens, get_max_tokens, get_tokenizer) .
Table Serialization#
The default TripletTableSerializer emits flat "**Column**, row = value" strings that lose column-to-value bindings. For table-heavy documents, switch to MarkdownTableSerializer via a custom provider :
from docling_core.transforms.chunker.hierarchical_chunker import (
ChunkingDocSerializer, ChunkingSerializerProvider,
)
from docling_core.transforms.serializer.markdown import MarkdownParams, MarkdownTableSerializer
class MDTableSerializerProvider(ChunkingSerializerProvider):
def get_serializer(self, doc):
return ChunkingDocSerializer(
doc=doc,
table_serializer=MarkdownTableSerializer(),
params=MarkdownParams(compact_tables=True),
)
chunker = HybridChunker(
tokenizer=tokenizer,
repeat_table_header=True,
serializer_provider=MDTableSerializerProvider(),
)
See Table Export and Serialization for a fuller comparison.
Key Source Files#
| File | Purpose |
|---|---|
hybrid_chunker.py | HybridChunker — orchestrates all three stages |
hierarchical_chunker.py | HierarchicalChunker, ChunkingDocSerializer, ChunkingSerializerProvider |
doc_chunk.py | DocChunk, DocMeta data model |
base.py | BaseChunker, BaseChunk, BaseMeta, contextualize() |
line_chunker.py | LineBasedTokenChunker (table row splitting) |
tokenizer/huggingface.py | HuggingFaceTokenizer |
tokenizer/openai.py | OpenAITokenizer |