Model-agnostic evaluation for user-defined operations over PDF and image data.
Project description
OCR Extraction & Understanding Evaluation (visual_evals)
An evaluation pipeline for OCR extraction and downstream manipulation of document-derived data.
Abstract
Systems that read documents rarely stop at optical character recognition. A production pipeline typically extracts structured values from a PDF or image, then manipulates them through one or more downstream operations— recomputing totals, classifying instruments, comparing against a base table, summarizing, or routing. Errors compound across these stages, and a failure observed at the end of the pipeline is not necessarily caused by the stage where it surfaces.
visual_evals is a research-oriented, model-agnostic harness for evaluating
such multi-stage systems end to end. It follows the useful part of the Ragas
pattern—test cases, pluggable metrics, and inspectable reports—but makes three
distinctions that matter for document pipelines:
-
Three levels of reference authority. Every reference carries one of three authority levels, and the harness treats them differently:
evidence— a raw source (PDF, image, OCR/Markdown) that can support or contradict a prediction but is not assumed infallible;generated— a provisional, AI-generated ground truth synthesized from the evidence when no human reference exists (see Generating provisional ground truth); agreement among models is a reliability signal, not proof of accuracy;ground_truth— an authoritative human or trusted-system reference.
A
generatedreference is never silently treated as authoritative: it is promoted toground_truthonly through an explicit, signed human approval step. Any mixture of the three can appear in a single test case. -
Deterministic where possible, judged where necessary. Exact metrics run wherever structured ground truth exists; a visual LLM judge handles images, PDFs, layout, semantic equivalence, and rubric-based criteria.
-
Dependency-aware failure attribution. Each stage records its upstream dependencies, so the harness can localize where a pipeline failed rather than only reporting that it failed.
The library evaluates recorded inputs and outputs; it does not dictate or execute the system under test. Domain policy—what each operation is and how its correctness is decided—stays in the calling application.
Research design goals
| Goal | Mechanism |
|---|---|
| Attribute failures across compounding stages | depends_on graph + failure_context |
| Avoid treating lossy OCR/Markdown as truth | evidence/ground-truth authority levels |
| Reduce single-judge bias | model-balanced JudgePanelMetric (PoLL-style) |
| Keep judgments auditable | versioned specs, SHA-256 prompt fingerprints, retained dissent |
| Separate the prompt-under-test from the correctness rule | OperationSpec vs. independently reviewed EvaluationSpec |
The voting and panel design draw on published findings on self-consistency, panels of diverse evaluators, and documented LLM-judge biases; see Research-backed multi-judge voting for citations and the important caveats on what agreement does and does not prove.
Core contract
┌─ PDF / image ─────────── evidence
prediction JSON ─┬───────┼─ OCR / Markdown ─────── evidence
│ ├─ human notes ────────── ground_truth
│ └─ JSON / CSV base table ground_truth
▼
deterministic metrics + visual LLM judge
▼
JSON report + Markdown summary
- Evidence can support or contradict a prediction, but is not assumed infallible.
- Ground truth is an authoritative human or trusted-system reference.
- References are interchangeable and can be mixed in one test case.
- Exact metrics run where structured ground truth exists.
- The visual judge handles images, PDFs, layouts, semantic equivalence and rubrics.
The extraction → manipulation pipeline
Evaluation does not stop after OCR or extraction. The unit of study is the whole chain from source document to final decision:
document ──▶ OCR / extraction ──▶ manipulation A ──▶ manipulation B ──▶ ... ──▶ decision
│ │ │
chosen metric chosen metric chosen metric
An operation can be extraction, calculation, comparison, validation,
summarization, routing, or anything else the caller defines (LLM prompt). operation is a
free-form string, not an enum or a library-controlled taxonomy. The library
evaluates recorded inputs and outputs; it does not dictate or execute the
system being tested.
Each EvaluationCase represents one stage and contains:
action_instruction: the exact editable prompt or rules given to the action;input_data: the material received from an earlier stage;prediction: this stage's output;references: any mixture of evidence and authoritative ground truth;rubric: the caller's success rule for this operation;judge_instruction: optional stage-specific guidance for the evaluator;operation: a free-form identifier used to select metrics; anddepends_on: upstream stage IDs.
PipelineEvaluator selects metrics in this order: exact stage ID, operation,
then default metrics. An operation can use a deterministic built-in metric, an
arbitrary Python function through FunctionMetric, an LLM judge, or several of
these together. This keeps domain policy in the application using the library.
The caller defines both sides of the contract:
operationandrubricsay what the system was supposed to do.input_dataandpredictionrecord what went in and what came out.- A metric says how correctness is decided. Use code when the rule is exact (such as arithmetic), and an LLM judge when the rule needs semantic or visual judgment.
Dependency-aware failure diagnosis
Every stage report includes its own verdict, the verdicts of all upstream
stages, and a failure_context:
current_stage: this stage failed while its upstream stages passed;upstream: this stage passed on the input it received, but an upstream stage failed;upstream_or_combined: both this stage and an upstream stage failed, so the result must not be blamed on this stage alone;indeterminate: an uncertain or non-applicable result prevents attribution.
This is careful failure localization, not automatic proof of causality. If both extraction and a later action fail, rerun the action with corrected extraction input. If it then passes, the extraction error caused the downstream failure; if it still fails, the action also has an independent problem.
Stage checkpointing
Long multi-model evaluations can checkpoint after every completed stage:
report = evaluator.evaluate(
pipeline,
checkpoint_dir="eval_runs/my-case/checkpoints",
checkpoint_context={
"judge_provider": "anthropic",
"judge_model": "claude-opus-4-8",
},
)
Each report is written atomically. A rerun loads completed stages instead of
calling their metrics or judges again. A checkpoint is reused only when the
serialized stage case and checkpoint_context have the same fingerprint, so
include every external setting that can change the result, especially judge
provider, model, temperature, and prompt version.
Installation
The project is a standard src-layout
Python package built with Hatchling. Install it into
your environment as an editable dependency:
pip install -e .
The import package is visual_evals; the distribution is
ocr-extraction-understanding-eval:
import visual_evals
The package does not create, load or require a .env file. Secret configuration
belongs to the application using the library.
For development and to run the test suite:
pip install -e ".[dev]"
pytest -q
Optional extras:
pip install -e ".[pdf]"— PyMuPDF4LLM Markdown conversion for search/debug.
Building and distributing
Because packaging metadata lives entirely in pyproject.toml, a wheel and
source distribution can be produced with the standard build frontend:
pip install build
python -m build # writes dist/*.whl and dist/*.tar.gz
The resulting wheel is installable anywhere with pip install dist/*.whl, and
publishable to a package index with twine upload dist/* once you own the name.
Direct PDF evaluation quick start
Keep the original PDF as primary evidence. The system output is compared directly with the PDF; it is not evaluated against a lossy Markdown replacement:
from visual_evals import (
EvaluationCase,
Evaluator,
LLMJudgeMetric,
ModelJudge,
OpenAIResponsesAdapter,
artifact_from_path,
)
source = artifact_from_path("statement.pdf", authority="evidence")
case = EvaluationCase(
case_id="statement-001",
operation="statement_extraction",
prediction=actual_system_output,
# Optional. Omit this entirely if the producer is unknown.
producer={
"provider": "anthropic",
"model": "claude-sonnet-your-version",
},
references=[source],
rubric=(
"Every extracted value must match the visible PDF and remain associated "
"with the correct label, row, column, and page."
),
)
adapter = OpenAIResponsesAdapter(model="gpt-4o-mini")
report = Evaluator(
[LLMJudgeMetric(ModelJudge(adapter))]
).evaluate(case)
print(report.verdict)
print(adapter.total_usage.model_dump())
Producer provenance is optional. If omitted, reports show unknown and make no
assumption about judge independence. If supplied, judges are labelled
same_model, same_provider, or independent. Producer-related votes remain
visible, but a panel with no independent judge requires human review.
Set OPENAI_API_KEY and ANTHROPIC_API_KEY in the process that runs this
code. The library never parses .env; .env is git-ignored only to reduce the
risk of accidentally committing a locally created secret file.
The runnable two-model example evaluates a JSON prediction directly against the PDF:
python examples/evaluate_pdf_directly.py statement.pdf prediction.json
Markdown conversion remains available as an optional local derivative via PyMuPDF4LLM. It is useful for search and debugging but never replaces the raw PDF:
pip install -e ".[pdf]"
python examples/pdf_to_markdown.py statement.pdf
See the full usage guide for provider selection, voting, ground truth, single-vs-panel batch reports, token semantics, and Windows commands.
Python API
This example evaluates extraction and a subsequent calculation. The custom metric independently recomputes the total from the stage input:
from visual_evals import (
EvaluationCase,
ExactStructuredMetric,
FunctionMetric,
PipelineCase,
PipelineEvaluator,
artifact_from_path,
artifact_from_value,
)
extraction = EvaluationCase(
case_id="document-001",
stage_id="extract",
operation="parse_line_items",
prediction={
"line_items": [
{"quantity": 2, "unit_price": 4.25},
{"quantity": 1, "unit_price": 3.00},
]
},
references=[
artifact_from_path("invoice.png", authority="evidence"),
artifact_from_value(
{
"line_items": [
{"quantity": 2, "unit_price": 4.25},
{"quantity": 1, "unit_price": 3.00},
]
},
authority="ground_truth",
artifact_id="human-transcription",
),
],
)
calculation = EvaluationCase(
case_id="document-001",
stage_id="calculate-total",
operation="sum_quantity_times_unit_price",
input_data=extraction.prediction,
prediction={"total": 11.50},
depends_on=["extract"],
rubric="Total equals the sum of quantity multiplied by unit price.",
)
def total_matches(stage):
expected = sum(
row["quantity"] * row["unit_price"]
for row in stage.input_data["line_items"]
)
return stage.prediction["total"] == expected
pipeline = PipelineCase(
pipeline_id="document-processing",
stages=[extraction, calculation],
)
report = PipelineEvaluator(
operation_metrics={
"parse_line_items": [ExactStructuredMetric()],
"sum_quantity_times_unit_price": [
FunctionMetric(
"recalculate_total",
total_matches,
score_name="calculation_accuracy",
)
],
}
).evaluate(pipeline)
Custom action and judge instructions
The downstream action prompt is part of the system under test, not a source of
truth. Define it once in an OperationSpec, alongside a separate,
independently reviewed EvaluationSpec. Every case that names the operation
reuses that configuration:
from visual_evals import (
EvaluationCase,
EvaluationSpec,
OperationRegistry,
OperationSpec,
build_judge_request,
)
vehicle_operation = OperationSpec(
spec_id="investment-vehicle",
operation="vehicle_classification",
version="2026-07-23",
# This is the real production prompt being tested. It may be imperfect.
action_instruction="Determine the investment vehicle.",
# These rules are reviewed independently; they define correctness.
evaluation_spec=EvaluationSpec(
spec_id="investment-vehicle-correctness",
version="3",
status="approved",
objective="Choose the vehicle using the approved investment taxonomy.",
criteria=[
"Use fund for pooled capital managed as one portfolio.",
"Use equity only for direct ownership in a company.",
"Use unknown when the evidence is insufficient.",
],
allowed_outputs=["fund", "equity", "bond", "unknown"],
),
)
vehicle_stage = EvaluationCase(
case_id="statement-001",
stage_id="classify-vehicle",
operation="vehicle_classification",
input_data=extraction.prediction,
prediction={"vehicle": "fund"},
depends_on=["extract"],
)
# Evaluator and PipelineEvaluator accept operation_specs=[vehicle_operation].
# Resolve manually only when you want to inspect the prompt before evaluation.
resolved_stage = OperationRegistry([vehicle_operation]).resolve(vehicle_stage)
# Inspect exactly what any judge provider will receive before calling a model.
request = build_judge_request(resolved_stage)
print(request.task["action_instruction"])
print(request.task["evaluation_spec"])
print(request.instructions)
action_instruction records what the action model was asked to perform; the
library never treats it as truth and does not execute the action.
EvaluationSpec.status="approved" records that the correctness rules were
reviewed; draft specifications must still be calibrated.
judge_instruction can add stage-specific evaluation guidance. Its default
mode is append, which keeps the protected evidence and response-contract
rules. Replacing that base prompt requires both
judge_instruction_mode="replace" and
allow_unsafe_judge_override=True; reports display a warning until the
replacement is calibrated against human-labelled cases.
Reports include the complete instructions and evaluation criteria, their versions, and separate SHA-256 fingerprints. This makes prompt changes visible without asking users to paste the same configuration into every test case.
Model-agnostic judges
The scoring layer depends on the Judge protocol, not on an OpenAI class. Every
provider receives the same JudgeRequest:
instructions: the shared evaluator prompt;task: operation, input, prediction claims, rubric, and reference inventory;output_schema: the JSON Schema forJudgeOutput; andreferences: text, structured data, image, PDF, or file artifacts.
There is one ModelJudge. OpenAI, Claude, and local models differ only in the
adapter passed to it. OpenAI and Anthropic have ready-to-use transport
adapters:
from visual_evals import (
AnthropicMessagesAdapter,
LLMJudgeMetric,
ModelJudge,
OpenAIResponsesAdapter,
)
openai_metric = LLMJudgeMetric(
ModelJudge(OpenAIResponsesAdapter(api_key=application_secret))
)
anthropic_metric = LLMJudgeMetric(
ModelJudge(AnthropicMessagesAdapter(api_key=anthropic_secret))
)
Any Ollama, vLLM, Transformers, or other model client can be wrapped by the same judge:
from visual_evals import LLMJudgeMetric, ModelJudge
def call_model(request):
# This adapter owns the provider-specific SDK or local inference call.
# Attach request.references when the model supports images or PDFs.
result = my_model_adapter.generate_json(
instructions=request.instructions,
task=request.task,
schema=request.output_schema,
references=request.references,
)
return result # JudgeOutput, a matching dict, or matching JSON text
metric = LLMJudgeMetric(
ModelJudge(call_model),
name="claude-sonnet-judge", # or "ollama-judge", "vllm-judge", etc.
)
The evaluation cases, evidence semantics, dependency diagnosis, metric
calculation, and reports are identical across providers. Only call_model
changes. A stage can also use deterministic metrics and multiple judges
together. Give each judge metric a distinct name when comparing models in one
report.
If api_key is omitted, the built-in adapters read OPENAI_API_KEY or
ANTHROPIC_API_KEY from the caller's existing process environment. The
library never reads an environment file. Both adapters expose last_usage,
usage_records, total_usage, and reset_usage().
Research-backed multi-judge voting
JudgePanelMetric supports both different model providers and repeated samples
at different temperatures:
from visual_evals import (
JudgePanelMetric,
JudgeVariant,
ModelJudge,
OpenAIResponsesAdapter,
)
panel = JudgePanelMetric(
[
JudgeVariant(
name="openai-low-temp",
judge=ModelJudge(
OpenAIResponsesAdapter(
model=openai_model_id,
temperature=0.0,
api_key=openai_secret,
)
),
provider="openai",
model=openai_model_id,
temperature=0.0,
),
JudgeVariant(
name="openai-higher-temp",
judge=ModelJudge(
OpenAIResponsesAdapter(
model=openai_model_id,
temperature=0.8,
api_key=openai_secret,
)
),
provider="openai",
model=openai_model_id,
temperature=0.8,
),
JudgeVariant(
name="claude",
judge=ModelJudge(call_claude_at_temperature_0),
provider="anthropic",
model=claude_model_id,
temperature=0.0,
),
JudgeVariant(
name="local",
judge=ModelJudge(call_local_model),
provider="local",
model=local_model_id,
temperature=0.2,
),
],
vote_rule="majority", # or "unanimous"
review_on_any_disagreement=True, # retain dissent for human review
)
Voting is model-balanced:
- Repeated runs or temperature variants of the same provider/model form one self-consistency group.
- Each distinct provider/model group contributes one final vote.
- A majority or unanimous rule selects the panel verdict.
- Ties, uncertainty, and—by default—any minority dissent enter the human-review queue.
This prevents five temperature samples from one model outvoting one Claude and one local-model judge. Reports preserve raw-sample agreement, model-group agreement, every variant's provider/model/temperature, the winning vote, and dissenting evidence.
The design follows two related but different research findings:
- Wang et al.'s ICLR self-consistency work found that sampling multiple reasoning paths and selecting the consistent answer improved several reasoning benchmarks. This supports repeated stochastic samples, but is not direct proof that temperature voting makes an LLM judge accurate. Google Research paper
- Verga et al.'s Panel of LLM evaluators (PoLL) found that panels of diverse model families outperformed a single large judge across their tested evaluation settings and reduced intra-model bias. PoLL paper
- Zheng et al. documented position, verbosity, self-enhancement, and reasoning limitations in LLM judges, supporting calibration and retained human review. MT-Bench/Chatbot Arena paper
The OpenAI Responses API documents temperature values from 0 to 2 and advises
changing temperature or top_p, not both.
Official Responses API reference
Other providers and model families may expose different sampling controls.
Voting agreement remains a reliability signal, not accuracy. Validate the panel against human-labelled cases before using it for production gating.
Generating provisional ground truth when none exists
When the only material you have is an image or a set of pages—and no human has
transcribed them—you do not have ground truth yet. You have evidence. This
section describes how to generate a provisional, clearly-labelled
authority="generated" reference from that evidence, and how to promote it to
authoritative ground_truth only through explicit human approval.
How generation works, end to end:
- Supply the evidence and the extraction contract. Give
GroundTruthPaneltwo or more independent model providers, anextraction_instruction, and theexpected_fieldsyou want read out. - Each provider reads the source blind. Every provider sees only the
source images and the extraction instruction—never the system prediction
and never another model's output. Each independently emits candidate
claims (
path,expected_value, citedreference_id, visualevidence, and aconfidence). - Claims are reconciled field by field. For each field the panel takes a
normalized vote across providers. Fields that meet
minimum_agreementbecome consensus rows; anything below it—including a field one model omitted entirely—becomes adisagreementand setsrequires_human_review=True. - A provisional reference is assembled. The consensus rows are packaged
into a single
ReferenceArtifactwithauthority="generated". It is safe to evaluate against immediately, but any score is agreement with a provisional AI reference, not verified accuracy. - (Optional) reduce the review queue automatically. Run
GroundTruthAdjudicatorfor a blind, targeted second read of only the disputed paths, orresolve_ground_truth_union(...)to accept non-conflicting one-sided claims. Both keepauthority="generated". - Promote to ground truth via signed human approval. A generated reference
becomes authoritative only through
approve_generated_reference(...)(or the file-basedwrite_ground_truth_review→approved_reference_from_reviewflow), which recordsapproved_byand refuses to approve while any disagreement is unresolved.
Terminology: what the panel produces is generated ground truth (
authority="generated"), not evidence. Evidence is the raw source you feed in; the generated reference is a hypothesis about what that evidence says. Only human approval turns it intoauthority="ground_truth".
Each provider sees only the source images and extraction instructions—not the system prediction—and independently generates candidate reference facts:
from visual_evals import (
EvaluationCase,
Evaluator,
ExactStructuredMetric,
GroundTruthPanel,
GroundTruthRouter,
OpenAIResponsesAdapter,
approve_generated_reference,
artifact_from_path,
)
source_images = [
artifact_from_path(
"page-001.png",
authority="evidence",
artifact_id="page-001",
metadata={"page": 1},
),
artifact_from_path(
"page-002.png",
authority="evidence",
artifact_id="page-002",
metadata={"page": 2},
),
]
panel = GroundTruthPanel(
{
"openai": OpenAIResponsesAdapter(api_key=openai_secret),
"claude": call_claude,
"local-model": call_local_model,
},
extraction_instruction="Extract currency, amount, and account name.",
expected_fields=["$.currency", "$.amount", "$.account_name"],
minimum_agreement=1.0, # Any disagreement creates a review item.
)
router = GroundTruthRouter(image_panel=panel)
prepared = router.prepare(
case_id="statement-001",
references=source_images,
)
generation = prepared.image_report
# Agreed claims can be used provisionally, but are not called ground truth.
case = EvaluationCase(
case_id="statement-001",
operation="statement_extraction",
prediction=actual_extraction,
references=[*source_images, generation.provisional_reference],
)
report = Evaluator([ExactStructuredMetric()]).evaluate(case)
# If providers disagreed, a human supplies the resolved path/value rows.
approved_reference = approve_generated_reference(
generation.provisional_reference,
approved_by="reviewer@example.com",
resolved_content=human_resolved_rows,
)
Routing is based on the complete input set:
all PDF/ODF/ODT documents -> preserve original source evidence
all images -> preserve sources + optional model consensus
explicit markdown mode -> add a local supplementary derivative
mixed/unsupported inputs -> explicit error; prepare separately
GroundTruthRouter uses document_mode="preserve" by default. Optional
Markdown derivatives have authority="generated" and never replace the raw
document. They are unscored or low-confidence until evaluated and therefore
remain reviewable.
The generation report contains:
- every provider's independently generated candidate claims;
- fields on which the models agreed;
- each disputed field and candidate value;
- expected fields omitted by every provider;
- cited image IDs, page metadata, and visual evidence; and
requires_human_review=Truewhenever candidates differ or no model produced a usable claim.
The consensus artifact has authority="generated". Evaluation against it is
labelled agreement with a provisional AI-generated reference, not accuracy.
It can be promoted to authority="ground_truth" only through
approve_generated_reference; unresolved disagreements require human-supplied
resolved content. Even unanimous models can share errors, so human calibration
is still recommended.
For a persistent review workflow, use write_ground_truth_review before the
tested pipeline runs. A reviewer edits the generated claims, resolves each
review_items entry with decision="set" or "omit", then sets
status="approved" and approved_by. Load the result with
approved_reference_from_review; it rejects unresolved or unsigned files.
Freeze ground truth for the complete batch before generating predictions.
GroundTruthAdjudicator can reduce the manual queue with a second blind
source-reading pass. It shows each provider only the disputed paths, never the
first-pass candidate values. The default requires unanimity; callers can set
minimum_agreement for a majority-vote panel. Votes below the configured
threshold remain human-review items.
For unattended experiments where omission by one generator should not remove a
field found by another, call resolve_ground_truth_union(generation). It
accepts non-conflicting present values, leaves conflicting present values
unresolved, and keeps the reference authority as generated. Its scores remain
provisional agreement signals rather than verified accuracy.
CLI
The CLI uses the caller's existing OPENAI_API_KEY or ANTHROPIC_API_KEY
process environment. With --judge auto, OpenAI is preferred when both exist,
then Anthropic; without either key, only deterministic metrics run. A model can
be supplied through --model or VISUAL_EVAL_MODEL, and OpenAI reasoning
effort through --reasoning-effort or
VISUAL_EVAL_REASONING_EFFORT.
Evaluate directly against the statement:
python -m visual_evals.cli \
--prediction examples/output.json \
--evidence examples/source.pdf \
--output reports/example-visual-eval.json \
--markdown reports/example-visual-eval.md
Add a human-maintained base table:
python -m visual_evals.cli \
--prediction examples/output.json \
--evidence examples/source.pdf \
--ground-truth examples/truth.csv \
--output reports/example-grounded-eval.json
Use an image and human notes instead:
python -m visual_evals.cli \
--prediction output.json \
--evidence statement-page.jpg \
--ground-truth main-points.md \
--output reports/image-eval.json
Run deterministic checks only:
python -m visual_evals.cli \
--prediction output.json \
--ground-truth truth.csv \
--judge none \
--output reports/exact.json
Ground truth can use any format
Ground truth means “authoritative reference”; it does not require a special table shape. You can supply:
- an ordinary CSV/TSV table with any columns;
- free-form
.mdor.txtreview notes; - JSON;
- a PDF, image, or document supported by the selected model adapter; or
- several references in different formats.
The LLM judge reads the reference and compares its meaning with the extraction. This supports cases where a reviewer provides only the important facts:
topic,main_point
vehicle,Pooled investment fund
risk,Moderate risk
currency,USD
That semantic comparison is probabilistic. It should be reported as an LLM judgment against authoritative material, not deterministic field accuracy.
For deterministic partial field metrics, optionally use explicit paths:
path,expected
$.heading,Safety inspection
$.status_text,Needs review
$.items[0].code,A-12
$.items[0].quantity,3
field_path is accepted instead of path; expected_value or value is
accepted instead of expected.
Reference comparison modes are:
auto: explicit path/value tables and JSON can use exact metrics; ordinary tables and notes use semantic comparison;semantic: force LLM comparison even for structured JSON; andexact: explicitly flatten structured data for deterministic comparison.
reference = artifact_from_path(
"review_notes.csv",
authority="ground_truth",
comparison_mode="semantic",
)
Exact metrics skip references that are not exact-compatible rather than producing misleading missing-field scores.
Interpreting scores
field_accuracy: correct authoritative fields / all annotated fields.claim_precision: supported predictions / judge-decided predictions.reference_recall: matched reference claims / judge-decided reference claims.claim_verification_coverage: fraction of claims the judge could decide.f1: harmonic mean of claim precision and reference recall.
With source evidence only, recall is an LLM-estimated inventory—not true accuracy. With arbitrary notes or tables marked as ground truth, the reference is authoritative but the LLM's interpretation remains probabilistic. Calibrate the judge on human labels and report judge/human agreement before scaling to a large test set.
Project details
Release history Release notifications | RSS feed
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 ocr_extraction_understanding_eval-0.1.0.tar.gz.
File metadata
- Download URL: ocr_extraction_understanding_eval-0.1.0.tar.gz
- Upload date:
- Size: 75.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
22262b91fe9c8938bdc367ecd2cd08c8729b581975922177c32b67466b89de24
|
|
| MD5 |
4348dec181c17b8502f8f0b884127260
|
|
| BLAKE2b-256 |
2694949645d501ee5e7e126bd41ba796f1d689cf9d55a570015c03924a562123
|
Provenance
The following attestation bundles were made for ocr_extraction_understanding_eval-0.1.0.tar.gz:
Publisher:
publish.yml on ajiayi-debug/OCR_extraction_understanding_eval
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ocr_extraction_understanding_eval-0.1.0.tar.gz -
Subject digest:
22262b91fe9c8938bdc367ecd2cd08c8729b581975922177c32b67466b89de24 - Sigstore transparency entry: 2255514360
- Sigstore integration time:
-
Permalink:
ajiayi-debug/OCR_extraction_understanding_eval@4f900656bc25c8ed5c94038cb9c7d170a831dd20 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ajiayi-debug
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f900656bc25c8ed5c94038cb9c7d170a831dd20 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ocr_extraction_understanding_eval-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ocr_extraction_understanding_eval-0.1.0-py3-none-any.whl
- Upload date:
- Size: 56.2 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 |
cb78b8787c31ee7579ecce66415a16f8ddcf0d2191181c6bafadf1e0892420ff
|
|
| MD5 |
3c5e96fd5ba032bd1191d193ac66d7fb
|
|
| BLAKE2b-256 |
d2ff1c7eaaa06866ce6ef43be79d33198d9cb5e53d2f4d1bc8d92e3904167dfb
|
Provenance
The following attestation bundles were made for ocr_extraction_understanding_eval-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on ajiayi-debug/OCR_extraction_understanding_eval
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ocr_extraction_understanding_eval-0.1.0-py3-none-any.whl -
Subject digest:
cb78b8787c31ee7579ecce66415a16f8ddcf0d2191181c6bafadf1e0892420ff - Sigstore transparency entry: 2255514377
- Sigstore integration time:
-
Permalink:
ajiayi-debug/OCR_extraction_understanding_eval@4f900656bc25c8ed5c94038cb9c7d170a831dd20 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ajiayi-debug
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f900656bc25c8ed5c94038cb9c7d170a831dd20 -
Trigger Event:
release
-
Statement type: