Skip to content

Add scripts/export_openeval.py: optional EvalPort ResultSet export - #336

Open
adhabnr-ux wants to merge 2 commits into
TIGER-AI-Lab:mainfrom
adhabnr-ux:add-export-openeval-script
Open

Add scripts/export_openeval.py: optional EvalPort ResultSet export#336
adhabnr-ux wants to merge 2 commits into
TIGER-AI-Lab:mainfrom
adhabnr-ux:add-export-openeval-script

Conversation

@adhabnr-ux

Copy link
Copy Markdown

Resolves #322.

What this adds

An optional, additive script (scripts/export_openeval.py, + a thin scripts/export_openeval.sh wrapper matching scripts/rescore.sh's shape) that converts a batch's rescore-summary.json (enriched with each run's own run-meta.json) into an EvalPort ResultSet — a small open interchange format for portable LLM/agent evaluation results.

Per the discussion on #322: @Perry2004 confirmed this is the results side (complementing clawbench-harbor-adapt/clawbench-edgebench-adapt, which import test-case definitions the other direction) and suggested it land as "a simple script inside script/" rather than a new adapters/-style package or a core dependency — this PR follows that shape exactly. There's a longer story behind why this took a couple of tries to land in the right place, laid out honestly below.

No new dependency

The script does not require evalport-sdk to run — the conversion itself is pure stdlib (argparse/json/pathlib). If evalport-sdk happens to be installed, the script uses it to validate the ResultSet it produces against the real EvalPort JSON Schema before writing it out (and prints a clear error if validation fails); if it isn't installed, the script says so and skips that step. pyproject.toml's dependency list is untouched either way.

Grounded in the real, current source — not just docs/scoring.md

docs/scoring.md describes an idealized/older shape (judge_match/final_pass merged into one per-run record). I read the actual current code instead:

  • run-meta.json (make_run_meta() in src/clawbench/runner/run_support/metadata.py) carries test_case, instruction, model, harness, intercepted, result_category, failure_category, adjusted_eligible, duration_seconds — but not judge_match or final_pass.
  • The per-run judge verdict (judge_llm.json for the default "lenient" rubric, judge.json for "strict" — JUDGE_FILE in src/clawbench/eval/rescore.py) carries match/reason (src/clawbench/runner/judge_llm.py's judge_request()).
  • rescore-summary.json (aggregate_batch()) rolls a batch into n_total, n_intercepted, judge_model, rubrics, and a tasks[] list shaped {"task_id", "test_case", "intercepted", "match_<rubric>", "reason_<rubric>"}. tasks[] rows don't carry instruction/model/harness — only each run's own run-meta.json does, so the script walks the batch dir for those to enrich each Result.

Result.passed is intercepted AND judge_match is True, matching docs/scoring.md's final_pass rule. The interception and judge stages each become their own GraderResult (gr_interception, gr_judge_match) rather than being collapsed into one opaque score, so the two-stage pipeline is still visible in the exported result.

Testing

tests/test_export_openeval.py — 11 tests, all passing locally: direct unit tests of run_to_result()/to_openeval() (interception/judge grader shapes, the never-intercepted case, an inconclusive judge verdict, the task_id-fallback path, rejecting an unknown --rubric, and — importantly — that a stale match_<rubric> key on a task row that was never actually intercepted doesn't produce a fabricated judge grader), plus CLI smoke tests via subprocess (matching the pattern tests/test_harbor_adapter.py::test_harbor_adapter_cli_smoke already uses) covering a full run, a clean error when rescore-summary.json is missing, and --help. All validated against the real, installed evalport-sdk 1.3.1's openeval.validate.validate_result_set() — not a mock. Also clean under this repo's own ruff/pyright config.

CHANGELOG.md updated under [Unreleased] per CONTRIBUTING.md.

Honest background on where this landed

I built and tested this adapter's logic once already, then hit a real tooling wall: at the time, I had no way to fork this repo or open a branch directly against it, so — rather than sit on tested work — I shipped the logic as a standalone package in EvalPort's own repo (adapters/clawbench-openeval-adapter/) as an interim home, while saying plainly in that package's README that the intent was still to land it here once that was possible. That's now resolved, so this PR ports the same tested logic into the shape you actually asked for on #322 (scripts/, not a new package) rather than leaving the interim version as the final answer.

— Sahi, independent contributor (not affiliated with TIGER-AI-Lab)

🤖 Generated with Claude Code

https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F

Adds an optional, additive script that converts a batch's
rescore-summary.json (+ each run's own run-meta.json) into an EvalPort
(https://github.com/adhabnr-ux/evalport) ResultSet -- a small open
interchange format for portable LLM/agent evaluation results.

Scoped in TIGER-AI-Lab#322: maintainer Perry2004 confirmed this is the results side
(complementing clawbench-harbor-adapt/clawbench-edgebench-adapt, which
import test-case definitions the other direction) and suggested it land
as "a simple script inside script/" rather than a new adapters/ package
or a core dependency -- this follows that shape exactly. The script has
no hard dependency on evalport-sdk: the conversion itself is pure
stdlib, and evalport-sdk is only used, if installed, to validate the
produced ResultSet against the real EvalPort schema.

Grounded in the current real source, not just docs/scoring.md:
src/clawbench/runner/run_support/metadata.py (make_run_meta()),
src/clawbench/eval/rescore.py (aggregate_batch()/rescore_one()), and
src/clawbench/runner/judge_llm.py (judge_request()'s match/reason
shape). Result.passed is `intercepted AND judge_match is True`, per
docs/scoring.md's final_pass rule; the interception and judge stages
each become their own GraderResult (gr_interception, gr_judge_match)
rather than being collapsed into one opaque score.

Testing: 11 tests (tests/test_export_openeval.py), covering run_to_result()
and to_openeval() directly plus CLI smoke/error-path tests via subprocess,
all passing against the real, installed evalport-sdk 1.3.1's
openeval.validate.validate_result_set() -- not a mock. Also clean under
this repo's own ruff and pyright configuration.

Signed-off-by: adhabnr-ux <adhabnr@gmail.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
Copilot AI lite review requested due to automatic review settings September 2, 2026 04:36
@github-project-automation github-project-automation Bot moved this to Todo in ClawBench Sep 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

adhabnr-ux added a commit to adhabnr-ux/evalport that referenced this pull request Sep 2, 2026
…/ClawBench#336 is open

The README's Status section described this package as an interim home
pending fork/PR access to TIGER-AI-Lab/ClawBench. That's now resolved --
TIGER-AI-Lab/ClawBench#336 ports this same tested logic into
scripts/export_openeval.py, the shape the maintainer actually asked for
on #322. Update the doc so it doesn't read as still-blocked.

Signed-off-by: adhabnr-ux <adhabnr@gmail.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
…d import

test_to_openeval_builds_valid_result_set_and_summary() unconditionally
imported openeval.validate, but evalport-sdk is intentionally NOT a
project dependency (per this PR's own design: export_openeval.py
validates against the real OpenEval schema only when evalport-sdk
happens to be installed, and skips that step otherwise). CI's `uv run
--frozen pytest` doesn't install it, so the test hard-failed with
ModuleNotFoundError: No module named 'openeval'.

Fixed by gating that one validation step behind
pytest.importorskip("openeval.validate"), mirroring the script's own
graceful-degradation behavior in the test suite. Confirmed both paths
locally:

  - uv sync --frozen && uv run --frozen pytest -q
    -> 228 passed, 1 skipped (openeval not installed, matches real CI)
  - same venv + `pip install evalport-sdk`
    -> 229 passed (real schema validation actually runs and passes)

Also fixes the static-check job: `uv run --frozen ruff format --check .`
was failing on both touched files (pre-existing formatting drift from
how I originally wrote them, not from this fix) - reformatted with the
exact pinned ruff==0.15.12 from uv.lock. Re-verified clean:

  - uv run --frozen ruff format --check .  -> 88 files already formatted
  - uv run --frozen ruff check .           -> All checks passed!
  - uv run --frozen pyright src/clawbench tests -> 0 errors, 0 warnings

No behavioral change to export_openeval.py; only formatting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtocdH3tKifGdkiCZxxV3F
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EvalPort adapter for ClawBench's run-meta.json / rescore-summary.json results

2 participants