Datagrunt 5.1: Schema-Driven PDF Extraction with Consensus & Grounding
Datagrunt 5.1.0 introduces schema-driven PDF rationalization (rationalize()). While Datagrunt 5.0 established a fast, single-engine PDFium document parser, 5.1 provides a robust, production-grade layer to extract strongly-typed Pydantic records from unformatted or semi-structured PDF documents.
Extracting structured data from enterprise PDFs with large language models often suffers from hallucinations, missing records, or ungrounded values. rationalize() addresses these challenges directly through cross-family model consensus, verbatim quote grounding, cross-page entity folding, and deterministic Polars/Arrow exports.
Quick Start
Install the [rationalize] extra alongside [pdf]:
uv pip install "datagrunt[pdf,rationalize]"Define your target Pydantic schema using the lightweight Key annotation, and call rationalize() over any PDFDocument or PDF path:
from decimal import Decimal
from typing import Annotated
from pydantic import BaseModel
from datagrunt import Key, PDFReader, rationalize
class LineItem(BaseModel):
part_no: Annotated[str, Key]
qty: int
unit_price: Decimal
total: Decimal
class Invoice(BaseModel):
invoice_id: Annotated[str, Key("Invoice #")]
vendor_name: str
invoice_date: str
items: list[LineItem] = []
# Load PDF document
doc = PDFReader("invoices/purchase_order.pdf").document
# Rationalize against multi-provider models
result = rationalize(
doc,
schema=Invoice,
prompt="Extract invoice header details and all listed line items.",
models=("openai:gpt-5.4", "anthropic:claude-sonnet-4-6"),
min_families=2, # Require agreement across >= 2 distinct model families
cache_dir=".dg-cache", # Atomic on-disk response cache
)
# Export consensus records directly to a Polars DataFrame
df = result.to_dataframe(flatten="items")
# Review quarantine or disputed items
if result.needs_review:
print(f"Items needing manual review: {len(result.needs_review)}")Core Capabilities of Datagrunt 5.1
1. Zero-Dependency Schema Definition (Key)
The Key marker identifies entity key fields on Pydantic models. Importantly, Key is imported directly from datagrunt without requiring pydantic or httpx to be installed:
from typing import Annotated
from datagrunt import Key
# Pure marker annotation without heavy dependencies
customer_id: Annotated[str, Key("Customer ID / Account Number")]You can pass optional label aliases to Key(...) to provide provider models with hints on how the entity identifier appears in source documents.
2. Cross-Family Model Consensus (min_families)
Relying on a single AI provider model for data extraction exposes pipelines to silent errors. Datagrunt 5.1 routes prompts across multiple LLM families and verifies their results against each other:
- Supported Endpoints:
openai:,anthropic:,gemini:,ollama:, andopenai-compatible:<model>@<url>. - Google Cloud Endpoints: Native authentication via
GoogleCloudAuthwithGeminiProvider.google_cloud(...),AnthropicProvider.google_cloud(...), andOpenAICompatibleProvider.google_cloud(...). - Consensus Threshold: By default,
min_families=2requires that a strict majority of models from at least two separate provider families agree on every field before a record reaches"consensus". - Field Unanimity: Pass
must_agree=("vendor_name", "invoice_id")to require 100% unanimity on critical header fields.
3. Verbatim Quote and Key Grounding
Every extracted record must prove its source:
- Pre-Filtering: Elements flagged with
hidden_reasons(invisible text, off-page elements, background matches) are stripped before building wire requests, protecting models against hidden prompt injection or noisy layers. - Wire Escaping: Bidi controls, invisible tags, and ASCII control characters are safely sanitized in wire envelopes.
- Citation Verification: Supporting models cite
source_element_idsand providegrounding_quotes. Datagrunt verifies quotes against visible page text (exact substring matches ondigital/hybridpages, token containment $\ge 0.85$ onvision/OCRpages). - Key Grounding: Root entity
Keyvalues are matched against visible cited elements.
Records that fail grounding or consensus are placed in result.needs_review or result.quarantined rather than silently dirtying downstream tables.
4. Cross-Page Entity Folding & Deterministic Exports
PDF tables and entity records often split across page breaks. rationalize() automatically folds records across page boundaries based on root entity keys, stitching partial lists and attributes together.
RationalizedResult provides deterministic exports:
# Polars DataFrame with exploded child lists
df = result.to_dataframe(flatten="items")
# Arrow Table export
arrow_table = result.to_arrow_table()
# Python dictionary list
dicts = result.to_dicts()5. Atomic Response Cache (cache_dir)
Prompt tuning and pipeline debugging can be expensive. By setting cache_dir=".dg-cache", Datagrunt computes canonical SHA-256 hashes over schema definitions and PDF document contents. Provider responses are cached atomically on disk, eliminating duplicate API calls during local testing.
Summary of Updated & Created Files
README.md&pyproject.toml— Version bumped to5.1.0, added[rationalize]extra.src/datagrunt/rationalization/— Package implementation for schema markers, provider adapters, consensus engine, quote grounding, entity folding, and caching.datagrunt-site/content/docs/_index.md— Updated stable release notice to v5.1.0 and added[rationalize]installation & quickstart.datagrunt-site/content/docs/guides/pdf-parsing.md— Added dedicated section onrationalize()schema-driven extraction.datagrunt-site/content/blog/2026/november/datagrunt-5-1-0-schema-driven-pdf-rationalization.md— Published release blog post.
Upgrading
Upgrade Datagrunt and install the new rationalization extra:
uv pip install --upgrade "datagrunt[pdf,rationalize]"