VerifyDoc
The trust layer for document → structured-JSON extraction. Wrap any extractor — get back JSON where every field carries a calibrated confidence, a source grounding (page + bbox / char span), and an accept/review decision tuned to your error budget.
Above: a real pipeline run (scripts/make_demo_gif.py). The extractor returned $1,432.50; the page says $1,234.50. Grounding support drops to 0.78, the field misses the accept threshold, and the reviewer is pointed at the exact source region. The other three fields are auto-accepted.
The problem
Modern document parsers read pages at 96%+ benchmark accuracy — and still emit fluent, plausible, silently-wrong values ($42.50 → $45.20) with no reliable per-field signal telling you which values to trust. Commercial APIs (Box, Azure, Textract) sell field-level confidence as a closed feature. No popular open-source parser leads with it. (full USP audit)
VerifyDoc doesn't compete with the parsers — it layers on top of any of them:
document + schema ─► ingest ─► extractor adapter ─► confidence ─► calibration
(any model) signals (fit on cal split)
─► grounding ─► abstention policy ─► verified JSON + review UI
(bbox/span) (target risk α)
At a chosen operating point, VerifyDoc auto-accepts as many fields as possible while holding the error rate among accepted fields below your target (e.g. ≤ 2%) — everything else is routed to review with its source location attached, so a human verifies in seconds instead of eyeballing every field.
Quickstart
pip install verifydoc # core (text pipelines + eval harness)
pip install 'verifydoc[pdf]' # + PDF/image ingestion
from verifydoc import verify
# ready-to-run sample lives in examples/
result = verify("examples/invoice.txt", schema="examples/invoice_schema.json")
for f in result.fields:
print(f"{f.path:12} = {f.value!r:24} conf={f.confidence:.2f} {f.decision}")
if f.grounding:
print(f" └─ page {f.grounding.page}, bbox {f.grounding.bbox}")
verifydoc extract examples/invoice.txt --schema examples/invoice_schema.json --threshold 0.8
streamlit run ui/streamlit_app.py # review UI: green/red fields + click-through to source
See examples/ for the full runnable walk-through.
For AI agents (MCP)
Give any MCP-capable agent (Claude Desktop, IDEs, custom agents) a trust layer for reading documents — so it acts on confident, grounded fields and escalates the rest instead of hallucinating forward:
pip install 'verifydoc[mcp]'
verifydoc-mcp # stdio MCP server exposing verify_extraction()
Every field the agent extracts comes back with confidence + grounding + accept/review. See docs/MCP.md for the one-line client config.
Schemas are plain JSON Schema, with each leaf optionally declaring how it is scored (the executable-schema pattern):
{
"type": "object",
"properties": {
"invoice_id": {"type": "string"},
"vendor": {"type": "string", "x-scoring": "semantic"},
"total": {"type": "number", "x-numeric-tol": 0.01}
}
}
What's inside
| Layer | Modules | Status |
|---|---|---|
| Adapters (all model code isolated here) | mock · text-search · RapidOCR · PaddleOCR · dots.ocr · Docling/MinerU output · API-VLM (OpenAI/Anthropic) | ✅ |
| Confidence signals | token-prob · verbalized · consensus (k-sample voting) · grounding-based · combined | ✅ |
| Calibrators (fit on a dedicated split, never test) | temperature · Platt · isotonic · histogram · split conformal · grounding-conditioned (Mondrian) conformal (novel — recovers coverage a pooled threshold forfeits) | ✅ |
| Grounding | value → page/bbox/char-span attachment with support scores | ✅ |
| Policy | empirical & conformal accept thresholds for a target selective risk | ✅ |
| Eval harness / VerifyDocBench scorer | Field-F1 · exact · CER/WER · ANLS · TEDS/TEDS-Struct · GriTS · omission vs hallucination · ECE/Adaptive-ECE/MCE/Brier/NLL/TCE · RC/AURC/E-AURC/Coverage@Risk/AUROC/AUPR/FPR@95 · box IoU/span-F1/grounding-conditioned correctness · bootstrap CIs + paired tests | ✅ |
Every metric implements the exact definition in PROJECT.md §5 with a hand-computed numeric regression test (201 tests, eval/ coverage 98%).
Results on real documents
Two independent real OCR extractors (RapidOCR and PaddleOCR) on real CORD receipts and FUNSD forms, scored by the harness (full tables + reading):
| Confidence signal | ranks errors? | CORD AUROC (RapidOCR / PaddleOCR) |
|---|---|---|
| learned combiner | ✅ best | 0.89 / 0.84 |
| grounding | ✅ strong | 0.82 / 0.74 |
| token-probability | ~ moderate | 0.69 / 0.68 |
| verbalized / consensus | ✗ uninformative | 0.50 / 0.50 |
- Grounding is a real trust signal: grounded fields are 84–85% correct vs ~1% for ungrounded (gap ≈ +0.84; box accuracy @IoU 0.5 = 0.75–0.78).
- The abstention layer is honest: with a weak field-extractor the base error rate is high, so conformal abstention at a 2–5% budget correctly refuses to auto-accept — you report selective risk, not headline accuracy.
- The synthetic slice (strong extractor) shows the other end: Coverage@2% ≈ 1.0.
The thesis holds on real data: grounding + a learned fusion rank errors; self-reported and single-sample-consensus confidence do not.
The benchmark
make results # regenerates every table/figure in paper/generated from configs/
The harness runs signals × calibrators × the full metric suite with a
document-level calibration split (disjointness asserted in code), bootstrap
CIs, and a conformal-guarantee row. It ships a deterministic synthetic
slice (runs in CI) plus CORD and FUNSD loaders with gold source boxes;
extractor: dispatches to any adapter (rapidocr, paddleocr-vl, …) and
dataset: to any slice. See the GPU runbook to
reproduce the real-model rows. Core claims (grounding beats verbalized;
conformal holds its guarantee) are also CI-enforced as unit tests, not
just stated.
Why not just use the parser's own score?
Because it doesn't exist (Docling/MinerU/Marker), or it's a raw recognition
score that was never calibrated against field-level correctness
(PaddleOCR/dots.ocr). See docs/USP.md for the audit, and the
reliability diagrams in paper/generated/ for what "calibrated" actually
buys you.
Roadmap
- v0.1 — library + CLI + harness + synthetic benchmark slice + UI
- v0.2 — CORD + FUNSD real slices with gold boxes; learned combiner; 1000× faster grounder
- v0.3 — real-model results (RapidOCR + PaddleOCR on CORD/FUNSD)
- v0.4 — vendor-neutral API-VLM extractor (OpenAI/Anthropic) with k-sample consensus; compilable paper with auto-generated tables
- v0.5 — novel method (grounding-conditioned conformal, +0.50 coverage at fixed risk) + MCP server (agent trust layer) + real frontier-VLM results
- v0.6 — method validated on real data at scale (FUNSD 24%→71% coverage at 2% risk); inter-annotator-agreement tooling (
verifydoc iaa); numeric-aware grounding - dots.ocr via vllm; SROIE / DocILE / XFUND slices; human-labeled correctness at scale
- Paper submission (contributions welcome)
Documentation
- How it works — the pipeline, the abstention idea, why grounding is a trust signal
- Related work & positioning — how VerifyDoc compares to Beyond Logprobs, Cleanlab TLM, conformal factuality, and the commercial IDP stack
- Real-model results — RapidOCR + PaddleOCR numbers and reading
- GPU runbook — reproduce the real-extractor rows
- USP audit · Paper draft · Full spec
Development
git clone https://github.com/bhaskargurram-ai/verifydoc && cd verifydoc
uv venv .venv && uv pip install -e ".[dev]"
make test lint typecheck # all green before any PR (CI enforces)
make results # regenerate benchmark tables + LaTeX
make paper # compile the paper (needs a LaTeX toolchain)
Contributions welcome — see the issues tagged good-first-issue. All model-specific code goes in verifydoc/adapters/; a new extractor is one file.
Citation
@software{verifydoc2026,
author = {Gurram, Bhaskar},
title = {VerifyDoc: Calibrated, Abstaining, Grounded Document Extraction},
year = {2026},
url = {https://github.com/bhaskargurram-ai/verifydoc}
}
Apache-2.0.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file verifydoc-0.7.0.tar.gz.
File metadata
- Download URL: verifydoc-0.7.0.tar.gz
- Upload date:
- Size: 915.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d2133bf5ebf4e21be2675ac94e2e21a18faa4cc087be4cf22a400236bc42c8b
|
|
| MD5 |
decdf422588a97ec4622b86a1d62256c
|
|
| BLAKE2b-256 |
236f0bc251fb27810a811b47180119cdde73518d12fcee77262f5d210ee36535
|
Provenance
The following attestation bundles were made for verifydoc-0.7.0.tar.gz:
Publisher:
release.yml on bhaskargurram-ai/verifydoc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
verifydoc-0.7.0.tar.gz -
Subject digest:
6d2133bf5ebf4e21be2675ac94e2e21a18faa4cc087be4cf22a400236bc42c8b - Sigstore transparency entry: 2219408542
- Sigstore integration time:
-
Permalink:
bhaskargurram-ai/verifydoc@24dd1f05f035039c3146752199f97ba52daaa981 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/bhaskargurram-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@24dd1f05f035039c3146752199f97ba52daaa981 -
Trigger Event:
push
-
Statement type:
File details
Details for the file verifydoc-0.7.0-py3-none-any.whl.
File metadata
- Download URL: verifydoc-0.7.0-py3-none-any.whl
- Upload date:
- Size: 81.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
274abe3e1ea8d4f83ea1f6f98697b78285e3d41f249913d806f23905f28754d1
|
|
| MD5 |
cecee2835eaee666ace8d61733c5f7ea
|
|
| BLAKE2b-256 |
4d2b0587514d338f656b6c6a4ef96daa0b0221e2b413607dfa7054c2d8057d9d
|
Provenance
The following attestation bundles were made for verifydoc-0.7.0-py3-none-any.whl:
Publisher:
release.yml on bhaskargurram-ai/verifydoc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
verifydoc-0.7.0-py3-none-any.whl -
Subject digest:
274abe3e1ea8d4f83ea1f6f98697b78285e3d41f249913d806f23905f28754d1 - Sigstore transparency entry: 2219408993
- Sigstore integration time:
-
Permalink:
bhaskargurram-ai/verifydoc@24dd1f05f035039c3146752199f97ba52daaa981 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/bhaskargurram-ai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@24dd1f05f035039c3146752199f97ba52daaa981 -
Trigger Event:
push
-
Statement type: