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, ...
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
# 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 codex:gpt-5.6-sol
# 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
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: or claude: model
string (e.g. codex:gpt-5.6-sol, claude:opus) 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.
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
- The exam bundle — output format, question model, validation
- How ingestion works — the LLM-mode extraction pipeline
- Agentic mode —
codex:/claude:models, workspaces, the fix loop - Grading semantics — deterministic rules and LLM judges
- Benchmarking — the
benchmodel matrix and its metrics - Publishing — sharing bundles as Hugging Face datasets
- Models, effort, usage — model strings,
--effort, token reporting
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 (any_to_bench.agentic.runner.run_codex / run_claude), so the
entire ingest → solve → grade pipeline runs end-to-end in every mode without network
access or either 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
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 any_to_bench-0.1.1.tar.gz.
File metadata
- Download URL: any_to_bench-0.1.1.tar.gz
- Upload date:
- Size: 69.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da2a888247c25a16e69c312c337d0bb1d7166525084b7e4fb20ee32843a1a1d3
|
|
| MD5 |
ed0de0133198ac3b37a3f77b88bf3aaa
|
|
| BLAKE2b-256 |
70c41413866baafa5faf3c4c824093a95f5f052e1df24c8c87858a6318d43093
|
Provenance
The following attestation bundles were made for any_to_bench-0.1.1.tar.gz:
Publisher:
release.yml on JacobLinCool/any-to-bench
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
any_to_bench-0.1.1.tar.gz -
Subject digest:
da2a888247c25a16e69c312c337d0bb1d7166525084b7e4fb20ee32843a1a1d3 - Sigstore transparency entry: 2467819789
- Sigstore integration time:
-
Permalink:
JacobLinCool/any-to-bench@0a133fbdd7120915f8d3fe9a47f65c40e3069a79 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/JacobLinCool
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0a133fbdd7120915f8d3fe9a47f65c40e3069a79 -
Trigger Event:
push
-
Statement type:
File details
Details for the file any_to_bench-0.1.1-py3-none-any.whl.
File metadata
- Download URL: any_to_bench-0.1.1-py3-none-any.whl
- Upload date:
- Size: 89.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
926ada578dfdd24760639f637ec75b0a4d288bd3cd2245c994ac683ebf902629
|
|
| MD5 |
de45c30c6314697c11d8a01ce1697c65
|
|
| BLAKE2b-256 |
8e048416a3b3a0e92fb4f914179f52ca2f6714d3961b6d199dff5fd2b0e60c98
|
Provenance
The following attestation bundles were made for any_to_bench-0.1.1-py3-none-any.whl:
Publisher:
release.yml on JacobLinCool/any-to-bench
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
any_to_bench-0.1.1-py3-none-any.whl -
Subject digest:
926ada578dfdd24760639f637ec75b0a4d288bd3cd2245c994ac683ebf902629 - Sigstore transparency entry: 2467819954
- Sigstore integration time:
-
Permalink:
JacobLinCool/any-to-bench@0a133fbdd7120915f8d3fe9a47f65c40e3069a79 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/JacobLinCool
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0a133fbdd7120915f8d3fe9a47f65c40e3069a79 -
Trigger Event:
push
-
Statement type: