TL;DR: On the public Kleister-NDA benchmark (arXiv 2105.05796 — real SEC EDGAR NDAs, a deterministic 40-doc set), our credential-free rule extractor scores field-F1 0.523 out of the box and 0.697 after one round of targeted rule improvements, with source-attribution accuracy 1.0 in both rounds — every extracted value is a verbatim span of the block it cites. Separately, switching structured LLM output from client-side TOOLS-mode function calling to server-side schema-constrained decoding cut schema-validation failures from 12/40 (30%) to 0/40 on an identical rig. Every number in this article is a point-in-time measurement (2026-07-06), committed to the repo in content/kleister_results.toml and injected at build time — not recomputed on each site build — and reproducible with the commands at the end.
Our NDA vertical first shipped with an author-written synthetic golden set, scoring field-F1 0.981. That number is real but proves the wrong thing: it demonstrates pipeline correctness — extraction, attribution, and metrics all work end-to-end for a new domain — not real-world accuracy, because the rules were tuned on the very documents they were scored against.
So we attacked it with a third-party public benchmark. Kleister-NDA is a set of real NDAs filed with the SEC, labeled by the benchmark's authors, not by us. The same extractor that scored 0.981 on synthetic documents scored 0.523 on real ones. That gap — 0.981 vs 0.523 — is the honest picture, and it is exactly why extraction demos on vendor-authored data should not be trusted, including ours.
Kleister-NDA labels four fields. Field-F1 on labeled docs ("initial" = rules as tuned on the synthetic set; "improved" = after targeted fixes; the 40-doc set is built deterministically from train + dev-0 — the hidden test-A gold is never used):
| field | labeled docs | initial F1 | improved F1 |
|---|---|---|---|
| party | 40 | 0.025 | 0.550 |
| effective_date | 29 | 0.345 | 0.759 |
| term | 14 | 0.286 | 0.714 |
| governing_law | 40 | 0.925 | 0.925 (unchanged) |
Blended field-F1: 0.523 → 0.697. Source-attribution accuracy: 1.0 in both rounds.
Three targeted fixes, each aimed at a measured failure mode, each canonicalized on both sides (the same helper canonicalizes gold and extraction, so formatting differences never count as errors) and unit-tested:
governing_law was already at 0.925 and was left untouched. Two regression guards held throughout: the synthetic NDA eval stayed at 0.981, and the generic CUAD contract baseline is byte-identical — the NDA rules only reuse shared helpers; there is no core fork.
Every extracted field carries a source_block_id, and the metric verifies the extracted value actually appears in the text of that block (for the multi-valued party field, every extracted entity must appear in the cited block). The rule finders only ever emit spans of the block they matched, so attribution holds by construction — accuracy stayed 1.0 even while field-F1 was 0.523. Getting the value wrong and citing it honestly is a recoverable failure mode; citing the wrong evidence is not.
A separate measurement, on the 40-doc CUAD contract set (not Kleister), same rig both runs — Lambda A100-40GB, Ollama, qwen2.5:32b-instruct @ 32K context:
# prefix on cited block ids.response_format=json_schema): 0/40 schema failures. Field-F1 0.661–0.672 (two runs; decoding is non-deterministic, treat ±0.01 as run noise), source-accuracy 0.744–0.756.Same model, same prompts, same documents. The 30% failure class was a transport problem, and grammar-constrained decoding removes it at the transport level. The remaining source-accuracy gap is wrong_span — the right value cited from the wrong block — which no output-format constraint can fix.
The Kleister-NDA repository states no explicit license, so its files are never committed here — point KLEISTER_DIR at your own checkout:
git clone https://github.com/applicaai/kleister-nda
git clone <repo> && cd contract-rag && uv sync --extra dev
KLEISTER_DIR=../kleister-nda uv run python -m contract_rag.verticals.nda.kleister # build the deterministic 40-doc set
KLEISTER_DIR=../kleister-nda uv run python -m contract_rag.verticals.nda.kleister --eval # field-F1 + source-accuracy
The set build is deterministic (train + dev-0, seeded selection), so you should reproduce field-F1 0.697 / source-accuracy 1.0 up to environment variance in the PDF parser. If your numbers differ materially, open an issue — that is the point of measuring in public.