Docling for RAG

From document to retrieval-ready chunks: which chunker to pick, how to size chunks with the right tokenizer, how tables and citations survive, which framework integration fits, and which vector store to pair — checked against the official chunking concepts and serialization reference.

1
Choose your path

Which RAG path fits you?

Your situationPathWhy
Already on LangChainlangchain-docling DoclingLoaderOfficial loader; DOC_CHUNKS mode chunks natively, MARKDOWN mode + header splitter as fallback.
Already on LlamaIndexDoclingReader + Docling Node ParserReader loads lossless JSON or lossy Markdown; parser turns Docling format into Nodes.
Already on Haystackdocling-haystack converterDocling as a Haystack converter component.
Framework-free / custom storeHybridChunker directlyFull control: chunk, contextualize, embed, upsert anywhere (Qdrant, Milvus, Chroma, Pinecone…).
Just need chunk files fastCLI --to chunksNo Python: hybrid or hierarchical chunks straight from the terminal.
Mass ingestion at scaleData Prep Kit + DoclingChunk + tokenize pipelines built for large corpora.
2
Pipeline

Where Docling sits in RAG

A RAG pipeline has six stages; Docling owns the first three — the ones that decide answer quality before any embedding exists:

  • Parse — layout, reading order, tables, formulas, pictures → one structured DoclingDocument.
  • Serialize / chunk — structure-aware chunks with headings, captions and provenance (or a Markdown/HTML/JSON export for post-splitting).
  • Enrich textcontextualize() prepends heading context so each chunk stands alone.
  • Embed → store → retrieve → generate — your embedding model, vector store and framework; Docling-agnostic.

There are two chunking philosophies: export to Markdown and post-split (e.g. LangChain’s MarkdownHeaderTextSplitter), or chunk natively on the DoclingDocument. Native chunking preserves tables, headings and provenance that Markdown post-splitting silently drops — prefer it unless you have a reason not to.

3
Convert

Convert: keep the DoclingDocument

docling convert report.pdf --to md

For a programmatic pipeline, convert in Python and keep the document object — chunkers operate on it, not on files:

from docling.document_converter import DocumentConverter

converter = DocumentConverter()
result = converter.convert("report.pdf")
document = result.document
4
Export

Choose your export: Markdown, JSON, HTML, DocTags

Exports are shorthands over serializers (MarkdownDocSerializer etc.). For RAG the choice matters mostly for tables:

ExportTable spansRAG use
export_to_markdown()Flattened — no span syntax; spanned cells render emptyDefault text path; fine unless merged header cells matter.
export_to_html()Preserved — native rowspan/colspanBest text export when merged cells carry meaning.
export_to_dict() / JSONPreserved losslessly — full TableData incl. spansLossless path (LlamaIndex reader JSON mode); heaviest payloads.
DocTags / Docling LanguagePreserved — OTSL continuation tokensCompact structure-preserving format for Docling-native tooling.

Serializers are customizable: subclass BaseTableSerializer and friends for full control (see the advanced chunking & serialization example).

!If merged header cells decide answers, prefer HTML or JSON over Markdown — or enable table-header repetition at chunk time (step 7).
5
Compare

Chunker comparison

ChunkerStrategyPick when
HybridChunker (default)Hierarchical structure + tokenizer-aware split-when-oversized + merge-when-undersizedDefault for RAG. Balanced, heading-aware, table-aware.
HierarchicalChunkerOne chunk per document element; merges list items (opt-out via merge_list_items)Fine-grained, element-level chunks with full metadata.
LineBasedTokenChunkerKeeps line boundaries; splits a line only if it alone exceeds the limitTables, code, logs, lists — anything line-structured.
TrivialChunkerMinimal baseline chunkingDebugging and benchmarking, not production retrieval.
Markdown post-splittingExport to Markdown, then split (e.g. MarkdownHeaderTextSplitter)Only when a framework pipeline demands Markdown input.

All native chunkers implement BaseChunker (chunk() → chunk stream, contextualize() → enriched text), so LlamaIndex-style integrations accept any built-in, custom or third-party chunker through the same interface.

6
The default

HybridChunker in depth (the default)

from docling.chunking import HybridChunker

chunker = HybridChunker()
chunks = list(chunker.chunk(document))
print(len(chunks), chunks[0].text[:120])

How it works: start from hierarchical chunks, then one pass splits only oversized chunks (token-aware) and another merges only undersized successive peers with the same headings & captions. Key parameters:

  • tokenizer — align to your embedding model’s tokenizer (section 9). Default derives limits from the tokenizer.
  • max_tokens — cap per chunk; the enriched (contextualized) form is what should fit.
  • merge_peers=True — merge undersized peers; set False to keep splits strict.
  • repeat_table_header=True — every table chunk starts with the header row (section 7).
  • omit_header_on_overflow=False — drop the header for rows that fit without it but overflow with it (wide tables, strict budgets).
  • serializer_provider — e.g. Markdown tables via ChunkingSerializerProvider for chunk text shape control.

Embed the contextualized text, not chunk.text:

for chunk in chunker.chunk(document):
    enriched = chunker.contextualize(chunk)  # headings prepended — embed THIS
    vector = embed(enriched)
Embed the contextualized text, not chunk.text alone — the prepended headings are what make chunks retrievable.

Two operational notes: the transformers “Token indices sequence length …” warning during chunking is a documented false alarm (see the official FAQ), and set TOKENIZERS_PARALLELISM=false to silence fork warnings in servers.

7
Tables

Tables in RAG: headers, spans, formats

  • Repeat headers (repeat_table_header=True, default): each chunk of a split table starts with the header row, so every chunk is self-describing for the embedding model.
  • Overflow escape (omit_header_on_overflow=True): for wide tables, rows that fit without the header but overflow with it skip the header — token efficiency without breaking row integrity.
  • Merged cells: Markdown flattens spans; if merged headers matter, chunk from HTML/JSON serialization or a custom table serializer instead.
  • Line-based alternative: LineBasedTokenChunker with a repeated prefix keeps CSV-like rows intact and supports the same overflow logic via omit_prefix_on_overflow.
docling convert data.csv --to md

Worked demonstrations: hybrid chunking (incl. header repetition on a wide CSV), line-based chunking, table extraction.

8
Citations

Metadata & citations (DocMeta)

Every chunk carries dl_meta — keep it in your vector payload; it is what turns “an answer” into “an answer with a source”:

FieldContainsUse for
headingsHeading path, e.g. ["3.2 AI models"]Section labels, contextual prefixes, filters.
originMimetype, filename, binary hashDocument identity, dedup, source links.
doc_itemsSelf refs, labels, provenance: page_no, bbox, charspanPage citations, bounding-box highlighting, visual grounding.

Provenance includes page numbers and bounding boxes per item — enough to cite “page 3” or draw the source region (see visual grounding).

Never drop metadata to “save space”: without it, citations are impossible.
9
Sizing

Tokenizers: match your embedding model

TokenizerInstallNotes
HuggingFace (HuggingFaceTokenizer)pip install "docling-core[chunking]"Default path. max_tokens optional — derived from the tokenizer. Example: sentence-transformers/all-MiniLM-L6-v2 (also the CLI default).
OpenAI / tiktoken (OpenAITokenizer)pip install "docling-core[chunking-openai]"Requires explicit max_tokens (context window, e.g. 128 * 1024 for gpt-4o).
from transformers import AutoTokenizer
from docling_core.transforms.chunker.tokenizer.huggingface import HuggingFaceTokenizer
from docling.chunking import HybridChunker

EMBED_MODEL_ID = "sentence-transformers/all-MiniLM-L6-v2"
tokenizer = HuggingFaceTokenizer(
    tokenizer=AutoTokenizer.from_pretrained(EMBED_MODEL_ID),
    max_tokens=512,
)
chunker = HybridChunker(tokenizer=tokenizer, merge_peers=True)

At corpus scale, the Data Prep Kit chunk+tokenize pipeline applies the same principle in batch.

!The rule is absolute: size chunks with the tokenizer your embedding model uses. Mismatched tokenizers silently mis-size chunks and break merge/split decisions.
10
Frameworks

Framework integrations

LangChain — langchain-docling

pip install langchain-docling langchain-huggingface langchain_milvus
from transformers import AutoTokenizer
from docling_core.transforms.chunker.tokenizer.huggingface import HuggingFaceTokenizer
from docling.chunking import HybridChunker
from langchain_docling import DoclingLoader
from langchain_docling.loader import ExportType

tokenizer = HuggingFaceTokenizer(
    tokenizer=AutoTokenizer.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
)
loader = DoclingLoader(
    file_path=["https://arxiv.org/pdf/2408.09869"],
    export_type=ExportType.DOC_CHUNKS,  # native chunks (default)
    chunker=HybridChunker(tokenizer=tokenizer),
)
docs = loader.load()  # one LangChain Document per chunk, dl_meta in metadata

Two modes: DOC_CHUNKS (default — one LangChain Document per native chunk, metadata preserved) and MARKDOWN (one Document per file; split downstream, e.g. with MarkdownHeaderTextSplitter on #/##/###). Full flow in the official LangChain RAG example (Milvus + Mixtral) and the LangChain guide.

LlamaIndex — reader + node parser

pip install llama-index-readers-docling llama-index-node-parser-docling
from llama_index.readers.docling import DoclingReader

reader = DoclingReader(export_type="json")  # lossless; "markdown" for lossy
documents = reader.load_data("report.pdf")

DoclingReader populates LlamaIndex Documents (lossless JSON or lossy Markdown); the Docling Node Parser turns Docling-format Documents into Nodes using its knowledge of the format. Any BaseChunker implementation plugs into the same interface. See the official LlamaIndex RAG example.

Haystack and the rest

pip install docling-haystack

Haystack ships Docling as a converter component (example, integration docs). Beyond the big three: txtai, Kotaemon, DocETL, Vectara, Semantica, Hector, haiku.rag, Bee, CrewAI, Langflow, Open WebUI, spaCy, NVIDIA, Granite cookbook RAG and Data Prep Kit all integrate Docling — browse the official integrations index.

11
Stores

Vector stores: worked examples

The store never affects Docling — chunks + embeddings upsert anywhere. Official worked examples exist for:

StoreExampleNotes
MilvusRAG with MilvusAlso the store in the LangChain example (local docling.db, FLAT index).
WeaviateRAG with WeaviateNative vector + hybrid search options.
QdrantRetrieval with QdrantRetrieval-focused recipe.
OpenSearchRAG with OpenSearchSearch + vector in one engine.
MongoDB + VoyageAIRAG with MongoDBAtlas Vector Search + VoyageAI embeddings.
Azure AI SearchRAG with Azure AI SearchManaged retrieval on Azure.
Chroma / Pinecone / othersNo official recipe; same pattern: embed contextualize(chunk), upsert text + DocMeta.
12
No Python

Chunks from the CLI (no Python)

docling convert report.pdf --to chunks --chunks-type hybrid
docling convert report.pdf --to chunks --chunks-type hybrid --chunks-max-tokens 512 --chunks-tokenizer sentence-transformers/all-MiniLM-L6-v2

--chunks-type accepts hybrid (default) or hierarchical; the tokenizer defaults to sentence-transformers/all-MiniLM-L6-v2. Remote conversion (docling convert-remote / docling-serve) supports the same chunk options server-side. See the command finder.

13
Checklist

Production ingestion checklist

  • Tokenizer == embedding model. Always. Re-check on every model swap.
  • Embed contextualize() output, store chunk.text alongside for display.
  • Keep DocMeta (headings, page_no, bbox, origin) in the payload for citations.
  • Repeat table headers; use HTML/JSON source for merged-cell tables.
  • Trim conversion cost: --no-ocr for digital PDFs, skip enrichment you don’t rank on, batch-convert and pre-download models (docling-tools models download --all).
  • Scale-out: Data Prep Kit chunk+tokenize for corpora; docling-serve /v1/convert/source with --to chunks behind a queue for services.
!PII first: detect and obfuscate PII before embedding (see the PII example) — you cannot un-embed a leak.
14
Fix

Troubleshooting RAG ingestion

  • Answers lack context / “orphan” chunks — you embedded chunk.text; switch to contextualize() and confirm headings appear.
  • Table answers are wrong — enable repeat_table_header; check merged cells (Markdown flattens spans → use HTML/JSON).
  • Transformers sequence-length warning — documented false alarm, safe to ignore (official FAQ).
  • Chunks too big/small for the embedding model — tokenizer mismatch; set the chunker tokenizer to the embedding model’s and tune max_tokens.
  • No citations possible — metadata dropped at upsert; persist dl_meta (page_no, headings, origin).
  • Ingestion too slow — see conversion is slow: disable unneeded OCR/enrichment, use GPU, batch.
15
FAQ

RAG FAQ

Which chunker should I start with?
HybridChunker with your embedding model’s tokenizer. It is the default for a reason: structure-aware, size-controlled, heading-enriched and table-aware. Only move to Hierarchical (element-level) or LineBased (line-structured content) for a specific need.
chunk.text or contextualize() — what do I embed?
Always chunker.contextualize(chunk) for embeddings. It prepends the heading path, which is what makes a chunk self-contained for retrieval. Keep chunk.text for display.
Do I need LangChain / LlamaIndex / Haystack at all?
No. They are conveniences: loaders, readers and converters around the same chunkers. A dozen lines of HybridChunker + your embedding call + any vector store is a complete pipeline.
Markdown export + text splitter vs native chunking?
Native chunking preserves tables, headings, provenance and spans that Markdown export flattens or drops. Use Markdown post-splitting only when a downstream component requires Markdown input.
How do I keep table structure in retrieval?
Repeat headers (repeat_table_header), mind overflow (omit_header_on_overflow), and source tables from HTML/JSON when merged cells matter. LineBasedTokenChunker is the specialist for row-oriented data.
How do answers cite pages?
Persist each chunk’s dl_meta (headings, page_no, bbox, origin) in the vector payload and return it with hits. Provenance supports page citations and even bounding-box highlighting.
Which vector store works with Docling?
Any. Docling produces chunks + metadata; stores only hold vectors. Official recipes cover Milvus, Weaviate, Qdrant, OpenSearch, MongoDB and Azure AI Search — Chroma, Pinecone and the rest follow the identical pattern.
How do I scale ingestion?
Batch conversion, pre-downloaded models, GPU for layout/OCR, no unneeded enrichment — plus Data Prep Kit chunk+tokenize pipelines for corpora or docling-serve chunk output behind a queue for services.

Verified with Docling v2.129.0 · Last checked 2026-09-22 · Official source