Document Extraction Strategies#
docling-graph offers three values for extraction_contract — "direct", "dense", and "auto" (default) — that control how the pipeline sends document content to the LLM for entity extraction. The choice affects latency, token cost, and accuracy for documents of different sizes.
Extraction Contracts#
Auto (default)#
"auto" makes a per-document decision after the document's actual size is known . It resolves to direct when both conditions hold:
- The estimated input + output budget fits the model's context window.
- The document is small enough that a single response can represent it (input within
DIRECT_OVERFLOW_RATIO = 1.0× output character capacity).
Otherwise it resolves to dense. If chunking is disabled, direct is always used regardless of document size . The decision is logged per document. The in-code rationale explains the motivation: a 36k-character paper run with direct scored node-F1 0.04 vs 0.14 with dense .
Token estimation uses a conservative CHARS_PER_TOKEN = 4 .
Direct#
"direct" performs best-effort extraction in a single LLM call over the full (or chunked) document . It suits small documents where everything fits within the model's context window.
An optional gleaning pass (gleaning_enabled=True, default) runs after the primary call: it shows the LLM what was already extracted and asks "what did you miss?", then merges any additional entities into the result . The llm_input_format defaults to "doclang-geo" (DocLang XML with geometry) when direct is resolved .
Dense#
"dense" uses a two-phase skeleton → fill orchestration designed for large or complex documents :
- Phase 1 (skeleton): Chunks the document into batches (default
dense_skeleton_batch_tokens=2048) and runs entity-discovery prompts over each batch. Nodes are tracked with batch-local integer handles (ifor self,pfor parent) to minimize output tokens. If the model truncates a batch's output, the batch is recursively split in half (up to 4 levels) . - Phase 2 (fill): Populates entity attributes. Each fill call is scoped to the document regions where the skeleton node was observed (
dense_fill_context="scoped", default), keeping token cost proportional to entities being filled — not to document length . Up todense_fill_nodes_cap=5node instances are packed per fill call .
After Phase 1, a deduplication step reconciles aliases (dense_dedupe="standard" by default runs one LLM reconciliation call to collapse same-entity aliases; "aggressive" also merges near-identical strings from OCR noise) .
The llm_input_format defaults to "doclang" (DocLang XML without geometry) when dense is resolved .
The entry point is run_dense_orchestrator(), which returns a root result, run stats, and a provenance ledger.
Quick Reference: When to Use Each#
| Contract | Best for | Key trade-off |
|---|---|---|
auto (default) | Most workloads | Adds a size-check step; logs decision per document |
direct | Short docs (notes, invoices, short articles) | May silently self-ration on large docs; gleaning adds one extra call |
dense | Long/complex docs (papers, reports, books) | Higher token cost; two-phase overhead; better recall |
Reusing Pre-Parsed DoclingDocument Objects#
Docling conversion (OCR, layout analysis, segmentation) is the most expensive step in the pipeline. docling-graph lets you skip it on subsequent runs by reusing an already-parsed document.
Workflow:
- Run the pipeline with
export_docling_json=True(default). This writes theDoclingDocumentas a JSON file alongside other outputs . - In a subsequent run, set
sourceto the exported.jsonpath and provide a new template. InputTypeDetectorrecognizes the JSON asInputType.DOCLING_DOCUMENTby inspecting for fields like"schema_name","pages", and"main_text".- The
DoclingDocumentHandlerloads the JSON viaDoclingDocument.from_dict()and stores it incontext.docling_document;context.normalized_sourceis set toNone. ExtractionStagedetects the pre-loaded document and routes directly to_extract_from_docling_document()with the new template — no conversion models are invoked .
DocLang files (.dclg, .dclg.xml, .dclx) follow the same bypass path via DoclangInputHandler .
This pattern is the recommended way to experiment with multiple templates on the same document without paying the conversion cost each time. The exported JSON from a first run becomes the reusable artifact for all subsequent extractions .
Key Configuration Fields (PipelineConfig)#
| Field | Default | Description |
|---|---|---|
extraction_contract | "auto" | "direct", "dense", or "auto" |
llm_input_format | "auto" | Serialization sent to LLM; "auto" pairs format to resolved contract |
gleaning_enabled | True | Second-pass recall improvement for direct |
dense_skeleton_batch_tokens | 2048 | Max tokens per Phase 1 skeleton batch |
dense_fill_nodes_cap | 5 | Max nodes per Phase 2 fill call |
dense_fill_context | "scoped" | "scoped" (regions where node was observed) or "full" (whole doc) |
dense_dedupe | "standard" | Alias deduplication intensity after Phase 1 |
export_docling_json | True | Export parsed document as JSON for later reuse |