Hi — really nice project; the variant×model matrix + scorecard + --dry-run cost estimate combination is exactly what I've wanted from ad-hoc eval scripts. I have a possibly-useful feature idea rather than a bug, so feel free to close if it's out of scope for now.
The idea
I maintain EvalPort (Apache 2.0), an open interchange spec + Python/TS SDK for portable eval test cases, graders, and results — the idea being that a suite or result set produced by one framework can be read by another without hand-translation. It's pre-1.0 (currently 1.0.0-rc.4) but has 32 real framework adapters merged (e.g. mlflow-openeval-adapter, langsmith-openeval-adapter) and has landed in Inspect AI's community extensions list.
evaling already has an evaling export RUN --format json|csv|md|html command with what looks like a small, addable format registry (docs/cli.md). A --format openeval (or a standalone converter, if you'd rather keep the CLI provider-agnostic) that emits a spec-valid EvalPort ResultSet would let an evaling run be diffed/aggregated/re-graded by any other EvalPort-speaking tool without writing a bespoke parser for results.jsonl.
How the real fields line up
I read src/evaling/config/schema.py and EvalPort's schemas (spec/schemas/{suite,testcase,grader,resultset}.json) to check this isn't hand-wavy:
| evaling |
EvalPort |
Case (id, vars, expected) |
TestCase (id, input, expected_output) |
CriterionSpec + ScorerSpec (criterion, scorer.type) |
Grader (id, type, params) |
a results.jsonl record (case_id, output, scores, latency_ms) |
a ResultSet.results[] entry (test_case_id, actual_output, grader_results[], duration_ms) |
run.json (id, started_at, finished_at) |
ResultSet (run_id, started_at, completed_at) |
Sketch, using the real field names on both sides:
# evaling: what's already in a scorecard (src/evaling/config/schema.py)
CriterionSpec(
criterion="cites-the-signal",
weight=1.0,
scorer=ScorerSpec(type="exact"),
)
// EvalPort spec/schemas/grader.json + testcase.json shape
{
"id": "gr_cites-the-signal",
"type": "exact_match"
}
// one results.jsonl record -> one ResultSet.results[] entry
{
"test_case_id": "midnight-watch",
"actual_output": "Fraud — the charge is card not present at an unusual hour for a first-time merchant.",
"grader_results": [
{"grader_id": "gr_cites-the-signal", "type": "exact_match", "score": 1.0, "passed": true}
],
"passed": true,
"duration_ms": 1204,
"metadata": {"variant": "concise", "model": "claude-sonnet-5", "cost_usd": 0.0021}
}
Two honest wrinkles, not glossed over:
- The matrix vs. single-provider ResultSet.
EvalConfig runs variant × model × case, but EvalPort's ResultSet carries one provider per run. The natural fix is one ResultSet per (variant, model) pair from a single evaling run, with variant carried in metadata (as sketched above) since the spec doesn't have a first-class variant concept — that'd be closer to an EvalPort spec gap than an evaling one.
- Scorer coverage isn't 1:1.
exact/contains/regex/json-schema/llm-judge map cleanly onto exact_match/contains/regex/json_schema/llm_judge. python maps reasonably onto EvalPort's custom grader type. not-contains and agreement (the judge-calibration scorer, which is genuinely evaling-specific) don't have a clean EvalPort equivalent yet — those would need to round-trip through metadata rather than a typed grader, at least for now.
Ask
Does this look worth a PR, and if so — would you rather it live as --format openeval inside evaling itself, or as a standalone evaling-side converter script/package (mirroring how EvalPort's own adapters are structured, e.g. the two linked above)? Happy to build either, on whichever side is a better fit for how you want evaling's dependency surface to look, and it'd obviously need real converter tests per your fuzzing/mutation-testing bar in CONTRIBUTING.md before it'd be worth merging.
— Sahi, independent contributor (not affiliated with this project)
Hi — really nice project; the variant×model matrix + scorecard +
--dry-runcost estimate combination is exactly what I've wanted from ad-hoc eval scripts. I have a possibly-useful feature idea rather than a bug, so feel free to close if it's out of scope for now.The idea
I maintain EvalPort (Apache 2.0), an open interchange spec + Python/TS SDK for portable eval test cases, graders, and results — the idea being that a suite or result set produced by one framework can be read by another without hand-translation. It's pre-1.0 (currently
1.0.0-rc.4) but has 32 real framework adapters merged (e.g.mlflow-openeval-adapter,langsmith-openeval-adapter) and has landed in Inspect AI's community extensions list.evalingalready has anevaling export RUN --format json|csv|md|htmlcommand with what looks like a small, addable format registry (docs/cli.md). A--format openeval(or a standalone converter, if you'd rather keep the CLI provider-agnostic) that emits a spec-valid EvalPortResultSetwould let anevalingrun be diffed/aggregated/re-graded by any other EvalPort-speaking tool without writing a bespoke parser forresults.jsonl.How the real fields line up
I read
src/evaling/config/schema.pyand EvalPort's schemas (spec/schemas/{suite,testcase,grader,resultset}.json) to check this isn't hand-wavy:Case(id,vars,expected)TestCase(id,input,expected_output)CriterionSpec+ScorerSpec(criterion,scorer.type)Grader(id,type,params)results.jsonlrecord (case_id,output,scores,latency_ms)ResultSet.results[]entry (test_case_id,actual_output,grader_results[],duration_ms)run.json(id,started_at,finished_at)ResultSet(run_id,started_at,completed_at)Sketch, using the real field names on both sides:
Two honest wrinkles, not glossed over:
EvalConfigruns variant × model × case, but EvalPort'sResultSetcarries oneproviderper run. The natural fix is oneResultSetper (variant, model) pair from a singleevaling run, withvariantcarried inmetadata(as sketched above) since the spec doesn't have a first-class variant concept — that'd be closer to an EvalPort spec gap than anevalingone.exact/contains/regex/json-schema/llm-judgemap cleanly ontoexact_match/contains/regex/json_schema/llm_judge.pythonmaps reasonably onto EvalPort'scustomgrader type.not-containsandagreement(the judge-calibration scorer, which is genuinelyevaling-specific) don't have a clean EvalPort equivalent yet — those would need to round-trip throughmetadatarather than a typed grader, at least for now.Ask
Does this look worth a PR, and if so — would you rather it live as
--format openevalinsideevalingitself, or as a standaloneevaling-side converter script/package (mirroring how EvalPort's own adapters are structured, e.g. the two linked above)? Happy to build either, on whichever side is a better fit for how you wantevaling's dependency surface to look, and it'd obviously need real converter tests per your fuzzing/mutation-testing bar inCONTRIBUTING.mdbefore it'd be worth merging.— Sahi, independent contributor (not affiliated with this project)