Skip to main content

any-to-bench

Convert any exam materials — photos of exam papers, exam PDFs, solution PDFs/photos, official answer keys, scoring rubrics — into a machine-gradable benchmark.

Give it everything you have for one exam; it produces an exam bundle: the structured exam, a strict answer-sheet JSON Schema for any LLM harness, and a grading spec. Fixed-answer questions grade deterministically; open-ended questions are graded by multimodal LLM judges with rubrics extracted from your materials. All common paper-exam question types are supported, including nested sub-questions, figures, tables, and LaTeX math.

Install

Requires Python ≥ 3.12.

uv tool install any-to-bench   # or: pip install any-to-bench
a2b --help

Set the API keys for the providers you use as environment variables (or in a .env file in your working directory): OPENAI_API_KEY, GOOGLE_API_KEY, HF_TOKEN, ... Google models can also run through Vertex AI on a service account — google-cloud:gemini-3.7-flash with GOOGLE_APPLICATION_CREDENTIALS set; see docs/models.md.

For development, clone the repo and:

uv sync
cp .env.example .env   # fill in the API keys for the providers you use

Usage

# 1. Ingest: any mix of PDFs and photos for ONE exam -> a bundle
a2b ingest exam.pdf answer-key.jpg rubric.pdf -o out/bundle --model openai:gpt-5.6-sol

# Add one public corpus shared by every question (not sent to the ingest model)
a2b ingest questions.pdf answer-key.pdf --resources ./corpus \
    -o out/retrieval-bundle --model codex:gpt-5.6-sol

# 2. Check the bundle
a2b validate out/bundle

# 3. Have an LLM take the exam (any provider — this is the benchmark part)
a2b solve out/bundle --model google:gemini-3.7-flash -o out/answers.json

# 4. Grade the answer sheet
a2b grade out/bundle out/answers.json -o out/report.json
# override judge model(s): --judge-model openai:gpt-5.6-sol --judge-model agy:gemini-3.7-flash-high

# Or benchmark several models at once: solve + grade each, compare in one table
a2b bench out/bundle -o out/bench \
    --model openai:gpt-5.6-terra --model google:gemini-3.7-flash

# Share bundles via Hugging Face datasets (viewer-friendly, byte-faithful round trip)
a2b upload out/bundle user/my-exams --name matha
a2b download user/my-exams --name matha -o local/bundle

# Publish what you measured, so it accumulates into a leaderboard
a2b results publish out/bench user/my-results --source-repo user/my-exams

a2b is a shorthand alias for any-to-bench — every command works with both. In a cloned repo without installing, prefix commands with uv run (e.g. uv run a2b ...).

Ingest, solve, and judge models are independent. Use a codex:, claude:, or agy: model string (e.g. codex:gpt-5.6-sol, claude:opus, agy:gemini-3.7-flash-high) to run a phase agentically via that CLI instead of direct LLM calls — same commands, same outputs. All commands accept --effort and report token usage.

Existing document- or repository-based questions can be packaged as resource-backed retrieval benchmarks. Agentic takers receive the complete original corpus; direct LLMs get bounded read-only search/list/read tools over strict UTF-8 text. Actual file/byte exposure and optional, score-neutral citation checks are retained through benchmark and publication.

Example dataset

JacobLinCool/taiwan-exams is a corpus built with this tool: 182 Taiwanese national exams, 7,772 questions, ingested from the official papers, answer keys, and marking schemes with codex:gpt-5.6-sol (cap with claude:claude-opus-5). One subset per exam, named <exam>-<year>-<subject>, where the year is the ROC year the exam is named for (113–115 = 2024–2026):

Prefix Exam Subsets
gsat 學測 General Scholastic Ability Test 21 — three years × 7 subjects
ast 分科測驗 Advanced Subjects Test 23 — three years, every subject
tve 統測 vocational Unified Entrance Examination 120 — three years × 5 common + 35 group papers
cap 會考 Comprehensive Assessment Program, end of junior high 18 — three years × 6 papers

英語(聽力)is the one paper left out: its questions are spoken and published as audio, and a bundle carries text and images.

The dataset viewer shows the extracted questions with their figures embedded; <subset>/bundle/ holds the bundle itself. Benchmark against any of them:

a2b download JacobLinCool/taiwan-exams --name gsat-115-math-a -o bundle
a2b solve bundle --model google:gemini-3.7-flash -o answers.json
a2b grade bundle answers.json -o report.json

Published results

JacobLinCool/taiwan-exams-results holds the first scores against that corpus: nineteen taker configurations over all 21 papers of the 115 year, 1,748 points apiece — four agentic models (codex:gpt-5.6-sol, codex:gpt-5.6-luna, claude:claude-opus-5, claude:claude-sonnet-5) at each of low, medium, high and xhigh, plus google-cloud:gemini-3.7-flash through Vertex AI at low, medium and high. Sixteen of them also sat the 40 papers of the 115 統測 — all four agentic models at all four efforts — so those sixteen rows cover 61 papers and 5,748 points.

Over the 21 papers every configuration sat:

Configuration Score Rule-graded Solve output tokens
codex:gpt-5.6-sol xhigh 99.4% 99.2% 211k frontier
codex:gpt-5.6-sol medium 99.1% 99.2% 150k frontier
codex:gpt-5.6-luna xhigh 98.1% 98.2% 343k
claude:claude-opus-5 high 96.8% 97.4% 471k
google-cloud:gemini-3.7-flash high 95.2% 95.6% 696k
codex:gpt-5.6-sol low 95.0% 95.2% 102k frontier
claude:claude-sonnet-5 xhigh 94.3% 94.8% 1,385k
codex:gpt-5.6-luna low 78.8% 80.0% 99k frontier

Eight of the nineteen; the dataset card ranks all of them. Effort is not the whole story, and neither is spending: four rows are all that survive on the cost/score frontier, and codex:gpt-5.6-sol holds three of them. Its dial barely moves the rule-graded column — 99.2%, 99.0%, 99.2% at medium, high and xhigh — so what separates its top three rows is the judged half, and that half is only as good as the judge model named in the entry.

On the 40 papers of the 統測 — 4,000 points, 1,844 questions, only 9 of them judged — codex:gpt-5.6-luna scores 84.8%, 91.9%, 96.4% and 98.2% at low, medium, high and xhigh. The dial is flatter there than on 會考/學測/分科 (13.4 points of range against 19.3), because 37 of the 40 papers are multiple choice throughout and a low-effort reader already does well on them. The dials differ by an order of magnitude in slope: luna covers 13.4 points, claude: claude-sonnet-5 2.1 (93.8% → 95.9%), claude:claude-opus-5 0.9 (97.3% → 98.2%) — opus starts where luna finishes. codex:gpt-5.6-sol is done before its dial begins: 97.1% at low, 99.9% at medium, and 4000/4000 at xhigh — every one of the 1,835 rule-graded questions and all 80 judged points, on 393k output tokens, less than luna's xhigh spends to finish 1.8 points behind. Every point on the cost/score frontier but the cheapest is a sol row; the other eleven configurations all spend more for less.

google-cloud:gemini-3.7-flash is the first taker here that is a plain API model rather than a coding agent, and the difference shows in a way worth naming: it loses 41, 34 and 24 points at low, medium and high to answering fill-in-blank questions with its own blank ids instead of the schema's, in the maths papers, even after the harness retries with the error. Following the answer schema is part of sitting the exam, so those points stay lost — but the gap is a formatting failure, not arithmetic, and the entries say so.

Rule-graded points are scored by program, so that column compares across any two rows; judged points depend on the judge model, which is named per entry. One run per paper, so there is no error bar — read small gaps as unresolved.

a2b bench bundle -o out --model your:model --effort high
a2b results publish out user/your-results --source-repo JacobLinCool/taiwan-exams

Browse it as a leaderboard — pick the papers, decide whether judged questions count, and compare cost against score. See docs/results.md for the layout.

Design principles

The three phases have deliberately asymmetric goals:

  • Ingest: spend freely, be exact. A bundle is a dataset — built once, reused by everyone who ever benchmarks against it. Extraction accuracy is worth almost any model cost and wall time; this is why ingestion supports the expensive agentic mode, gap-repair rounds, and validate-and-fix loops. Intelligence spent here is amortized across every future run.
  • Solve: no constraints. The taker is the thing being measured — anything from a cheap LLM call to a full agent belongs here.
  • Grade: require as little intelligence as possible. The same answer sheet must earn the same score every time. Fixed-answer questions grade as pure scripts — zero model calls, bit-for-bit reproducible. Where an LLM judge is unavoidable (open-ended questions), it is constrained, not creative: precise rubrics with defined point levels, reference answers, and level snapping mean the judge follows the rubric mechanically instead of improvising — so even a non-frontier judge model grades accurately and consistently.

Put differently: ingest converts intelligence into structure (keys, rubrics, schemas) exactly once, so that grading needs almost none, forever.

Documentation

Development

uv run pytest -q        # fully offline — model requests are forbidden in tests
uv run ruff check .
uv run ruff format .    # CI enforces this with --check

The test suite fakes the LLM layer (any_to_bench.llm.build_agent) and the agentic subprocess layer (run_codex / run_claude / run_agy), so the entire ingest → solve → grade pipeline runs end-to-end in every mode without network access or any CLI binary installed.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

any_to_bench-0.1.2.tar.gz (102.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

any_to_bench-0.1.2-py3-none-any.whl (126.7 kB view details)

Uploaded Python 3

File details

Details for the file any_to_bench-0.1.2.tar.gz.

File metadata

  • Download URL: any_to_bench-0.1.2.tar.gz
  • Upload date:
  • Size: 102.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for any_to_bench-0.1.2.tar.gz
Algorithm Hash digest
SHA256 8f8af62044b30d6bc5ae0135ba6bc7ae168104481fa9648b56ab392d10d2219a
MD5 5d998705a601747e0cf3731bb65e434f
BLAKE2b-256 2f5a60c47e69df20a4c0023d1311be5e2f4e372d3c672dd678f9c4d90ffd4161

See more details on using hashes here.

Provenance

The following attestation bundles were made for any_to_bench-0.1.2.tar.gz:

Publisher: release.yml on JacobLinCool/any-to-bench

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file any_to_bench-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: any_to_bench-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 126.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for any_to_bench-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a0d8a08b7b1bcbf1a8cc066f9d8d605ca7bfe56fc08ea7fd0ee00c21471109ee
MD5 29d71b58fd67f94ed2678e7e85403cea
BLAKE2b-256 67e46ea44482fa64348f5cbf02c68711e27b0feeab0e59328b3d2f8f03225149

See more details on using hashes here.

Provenance

The following attestation bundles were made for any_to_bench-0.1.2-py3-none-any.whl:

Publisher: release.yml on JacobLinCool/any-to-bench

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page