Skip to content

[v0.5.0] benchmark scoring — correctness scorer with manual-adjudication hooks#100

Merged
ayhammouda merged 3 commits into
mainfrom
agent/88-correctness-scorer
Jul 8, 2026
Merged

[v0.5.0] benchmark scoring — correctness scorer with manual-adjudication hooks#100
ayhammouda merged 3 commits into
mainfrom
agent/88-correctness-scorer

Conversation

@ayhammouda

@ayhammouda ayhammouda commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Refs #63.
Closes #88.

Summary

Adds benchmarks/scoring.py, the correctness scorer with manual-adjudication
hooks called out in issue #88 (work package 5 of
docs/benchmarks/PUBLIC-BENCHMARK-METHODOLOGY.md). It turns a run's scoring
placeholders (written by the runner, issue #75:
scoring/<competitor_id>/<corpus_id>.json) into finalized records by
applying the methodology's rubric against the answer-key corpus, reusing
benchmarks/corpus.py's validate_corpus (issue #94/#97) rather than
duplicating corpus loading/validation.

This is plumbing only, per the issue's explicit instruction ("Actual
scoring of real answers stays human-adjudicated"). The automatic pass
(score_run) only ever decides the two judgment-free cases:

  1. A failed cell — the runner already fixed this at score: 0.0; the scorer
    re-emits it untouched in substance (same score/requires_manual_scoring),
    tagged with scorer provenance metadata.
  2. A succeeded cell with an empty/blank answer — "materially incomplete"
    under the rubric with no interpretation required, scored 0.0.

Every other succeeded cell is left with score: None /
requires_manual_scoring: True and written to an adjudication queue file
(<run-dir>/adjudication/queue.json) with the candidate answer, any
recorded tool calls, and the corpus answer key/citations/expected
properties, so a human can apply the rubric. A new python -m benchmarks adjudicate CLI subcommand ingests a human verdicts file
({competitor_id, corpus_id, score, correct_but_ungrounded?, adjudicator?, notes?}) and re-emits final records tagged scoring_method: "human".
A record once tagged scoring_method: "human" is never touched again by a
later score_run call
(covered by
test_score_run_never_overwrites_a_human_verdict).

Also computes a structural grounding_evidence_present fact (did the
transcript record at least one successful tool call?) and the methodology's
correct_but_ungrounded flag from it — set when a score is > 0.0 but no
grounding evidence was recorded. That flag is a separate field; it never
changes the score itself. Per-category and per-tool-model-key rollups
(total_cells, scored_cells, mean_score, pending_manual_scoring_cells,
failed_cells, correct_but_ungrounded_cells, human_scored_cells,
automatic_scored_cells) are written to <run-dir>/scoring-rollups.json.

benchmarks/__main__.py gains two subcommands following the existing
argparse-subparsers pattern: score (runs score_run) and adjudicate
(runs ingest_adjudication_verdicts), both gated on --corpus/--schema
the same way validate-corpus is.

benchmarks/report.py (issue #74/#90) is not modified — it already
reads scoring/<competitor>/<qid>.json directly and treats any record with
a numeric score + requires_manual_scoring: False as finalized, so a
scored run is picked up unchanged.

Why this approach (design calls not fully prescribed in the issue)

The issue leaves the automatic-pass decision boundary and the queue/verdict
file shapes for the agent to design. Given the issue's own instruction that
"real-answer scoring stays human," I deliberately made the automatic pass as
narrow as possible — it never attempts a semantic judgment of correctness
(no keyword/property matching against free-text answers, no LLM-as-judge).
It only resolves the two cases where the methodology's rubric already
requires no interpretation (failed cell, empty answer). Everything else is
queued. This keeps the "automatic pass" honestly plumbing rather than a
disguised auto-grader, and keeps the tests fixture-only per the pipeline's
constraints.

grounding_evidence_present is a purely structural signal (a successful
tool call was recorded), not a semantic judgment that the retrieved content
actually supports the answer text — that nuance stays with the human
adjudicator via the optional correct_but_ungrounded override in the
verdicts file.

Recovery-clause check

Issue #88's recovery clause: "If the corpus schema (#71) lacks fields the
rubric needs (expected answer properties, required citations), stop and
comment here." Re-verified against current main: docs/benchmarks/ corpus.schema.json (merged in #97 @ 1300cd3) already declares
answer_key, citations, and expected_properties as required question
fields — exactly what the rubric needs. The recovery clause does not fire;
no schema fields were invented.

Why this triggered supervisor review

Diff size. This PR is 1372 lines across 3 files (benchmarks/scoring.py
+599, benchmarks/__main__.py +81, tests/benchmarks/test_scoring.py
+692 including the CodeRabbit follow-up tests), over the §7 500-line
threshold (counting all non-generated files including tests, per the
precedent set on #97/#98). No forbidden-territory
paths were touched, no new dependency was added, pyproject.toml/uv.lock
are untouched, and no network/async was introduced — diff size is the only
trigger. Opening the PR with the supervisor-review label per policy;
not requesting merge.

Acceptance criteria

  • Scorer reads a run's scoring placeholders + the corpus answer keys and
    produces per-cell records with score ∈ {1.0, 0.5, 0.0}, per-category
    rollups, and the methodology's "correct-but-ungrounded" flag (marked
    and reported separately, never folded into the score). See
    score_run/ingest_adjudication_verdicts in benchmarks/scoring.py
    and the _compute_rollups/_rollup_stats per-category output.
  • Manual-adjudication hook: cells the automatic pass cannot decide are
    emitted to an adjudication queue file; a CLI subcommand
    (python -m benchmarks adjudicate) ingests human verdicts back and
    re-emits final records. Automatic scoring never overwrites a human
    verdict (score_run skips any record tagged scoring_method: "human"; see test_score_run_never_overwrites_a_human_verdict).
  • Failed cells (already scored 0.0 by the runner) pass through; the
    denominator stays the full corpus count per tool×model cell. One-line
    note: "pass through untouched" is interpreted as value-untouched —
    score/requires_manual_scoring/included_in_correctness_denominator
    are preserved exactly; the scorer additionally tags the record with
    scoring_method: "automatic" and a scored_at timestamp for
    provenance uniformity with every other finalized record. See
    test_score_run_passes_failed_cells_through_at_zero and
    test_score_run_denominator_includes_every_scoring_file.
  • Tests (additive, under tests/benchmarks/) use fixture answer keys and
    transcripts only — no LLM-as-judge, no network, no real corpus
    questions authored. tests/benchmarks/test_scoring.py reuses the
    synthetic fixture corpus (tests/benchmarks/fixtures/corpus.sample.yml,
    issue [v0.5.0] benchmark corpus — mechanical slice: schema, validator, placeholder fixture (split from #71) #94/[v0.5.0] benchmark corpus — mechanical slice: schema, validator, placeholder fixture (split from #71) #97) and hand-authors every scoring/transcript record.
  • No README/benchmark claims; Refs #63, never Closes #63. No README,
    PyPI, or launch-copy file touched.

Validation gate (all commands from the issue + the worker brief)

Re-run in full after the CodeRabbit follow-up commit (f37987c):

$ uv run ruff check src/ tests/
[]

$ uv run ruff check benchmarks/
[]

$ uv run pyright src/
0 errors, 0 warnings, 0 informations

$ uv run pyright benchmarks/
0 errors, 0 warnings, 0 informations

$ uv run pytest --tb=short -q
426 passed

$ uv run pytest tests/benchmarks -q
119 passed

$ uv run pytest tests/benchmarks/test_scoring.py -q
21 passed

$ uv run python-docs-mcp-server doctor
All checks passed.

CodeRabbit triage

Triaged — 2 findings, both Trivial nitpicks, both valid, both addressed in
follow-up PR #102.
(The initial auto-review was rate limited; the
re-triggered review completed with 2 nitpicks on
tests/benchmarks/test_scoring.py and no blocking findings — but ~1 minute
after this PR was merged, so the fixes land via #102 rather than this
branch. benchmarks/scoring.py and benchmarks/__main__.py were excluded
by the repo's .coderabbit.yaml path filters.)

# Finding Severity Triage Resolution
1 No test asserts per-tool-model rollup values (all tests used a single competitor) Trivial / quick win Valid — fixed in #102 Added test_score_run_produces_per_tool_model_rollups: two competitors, one carrying an explicit id:provider/model tool_model_key; asserts per-entry total_cells/scored_cells/pending_manual_scoring_cells and the persisted scoring-rollups.json. Adapted from the suggested diff — the real rollup key is by_tool_model_key, not by_tool_model.
2 is_error: True tool calls untested for grounding-evidence inference Trivial / low value Valid — fixed in #102 Added test_score_run_does_not_count_error_tool_calls_as_grounding_evidence: an errored tool call leaves grounding_evidence_present False in both the scoring record and the queue entry, matching _has_grounding_evidence's successful-calls-only behavior.

Both changes are purely additive to this PR's own new test file — no merged
tests touched. Detailed reply posted on this PR's thread; because this PR
merged before the review completed, the fixes land via follow-up PR #102.

Scope notes

  • No corpus questions or answer keys were authored (out of scope, [v0.5.0] benchmark corpus — define schema and 50-question eval pack #71,
    human-led).
  • No live LLM-as-judge scoring, no network calls, no real benchmark
    execution.
  • benchmarks/report.py, pyproject.toml, uv.lock, .github/workflows/,
    and all forbidden-territory paths in AGENT-EXECUTION-PIPELINE.md §2 are
    untouched.
  • Existing tests under tests/benchmarks/ and elsewhere are untouched
    (additive-only change).

…ooks

Applies the methodology's Correctness Scoring rubric (docs/benchmarks/
PUBLIC-BENCHMARK-METHODOLOGY.md) to a run's scoring placeholders (issue
#75) against the answer-key corpus (reusing benchmarks/corpus.py's
validate_corpus rather than duplicating it). Plumbing only: the automatic
pass decides only the two judgment-free cases (failed-cell passthrough at
0.0, empty/blank-answer at 0.0); every other succeeded cell is queued to
an adjudication queue file for a human, who is ingested back via
ingest_adjudication_verdicts. A record once tagged scoring_method:
"human" is never overwritten by a later automatic pass. Also computes a
structural grounding_evidence_present fact and the methodology's
correct-but-ungrounded flag (reported separately, never folded into the
score), plus per-category/per-tool-model rollups.

Refs #63.
Wires benchmarks.scoring.score_run and .ingest_adjudication_verdicts into
`python -m benchmarks` as `score` and `adjudicate`, following the existing
argparse subparsers pattern (run/report/validate-corpus). Both take
--corpus/--schema so a run can only be scored against a schema-valid
answer-key corpus, matching `validate-corpus`'s own gate.

Refs #63.
Covers: rubric application for each score value (via the automatic
empty-answer 0.0 path and human-ingested 1.0/0.5), per-category rollups,
the correct-but-ungrounded flag (both derived and grounded cases),
adjudication-queue emission with answer-key context, human-verdict
ingest, the never-overwrite-a-human-verdict guarantee, failed-cell
passthrough, denominator invariance, and the `score`/`adjudicate` CLI
subcommands -- plus clean-refusal cases (missing scoring files, missing
transcript, unknown corpus id, invalid/missing verdict fields, verdict
against a failed cell).

Reuses the synthetic fixture corpus from tests/benchmarks/fixtures/
corpus.sample.yml (issue #94/#97) as the answer-key source; every
scoring/transcript record is hand-authored. No real corpus questions, no
network, no LLM-as-judge.

Refs #63.
@ayhammouda ayhammouda added agent-pr-opened Autonomous agent opened an implementation PR for this issue supervisor-review Vision supervisor decision required before further automation labels Jul 8, 2026
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new pytest suite (tests/benchmarks/test_scoring.py) covering benchmarks/scoring.py: automatic scoring of failed/blank/answered cells, grounding evidence detection, category rollups, denominator invariance, human adjudication ingest, refusal/error paths, and CLI integration for the score and adjudicate subcommands.

Changes

Benchmark Scoring Test Suite

Layer / File(s) Summary
Test fixtures and helpers
tests/benchmarks/test_scoring.py
Adds module docs, fixture path constants, and builder/writer/reader helper utilities for scoring placeholders, transcripts, run directories, and verdicts.
Core automatic scoring behavior
tests/benchmarks/test_scoring.py
Tests failed-cell passthrough, blank-answer auto-decisioning, queue emission with answer-key context, and grounding evidence inference from transcript tool calls.
Aggregate rollups and denominator invariance
tests/benchmarks/test_scoring.py
Tests per-category rollups against scoring-rollups.json and denominator invariance across all placeholder files.
Human adjudication ingest
tests/benchmarks/test_scoring.py
Tests human verdict application, correct_but_ungrounded derivation, partial scoring, queue clearing, and non-overwrite of human verdicts on rerun.
Validation and refusal paths
tests/benchmarks/test_scoring.py
Tests BenchmarkValidationError refusals for invalid/mismatched verdicts and missing/mismatched scoring inputs.
CLI integration tests
tests/benchmarks/test_scoring.py
Tests CLI score and adjudicate subcommands for successful output/file creation and clean non-zero exit on missing run directory.

Estimated code review effort: 2 (Simple) | ~15 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The tests support the requested scorer behavior, but the core scorer and CLI files were excluded from review, so compliance cannot be fully verified. Include the implementation files in review, or provide their contents, so the scorer, queue ingest, and no-overwrite rules can be verified.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: benchmark scoring with manual-adjudication hooks.
Out of Scope Changes check ✅ Passed The change set is confined to additive benchmark tests and does not ցույց unrelated scope creep.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/88-correctness-scorer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbiteu

coderabbiteu Bot commented Jul 8, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@ayhammouda, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: eb4c28d3-034f-413d-ac70-6a4c167df187

📥 Commits

Reviewing files that changed from the base of the PR and between 548acd1 and b344322.

⛔ Files ignored due to path filters (2)
  • benchmarks/__main__.py is excluded by none and included by none
  • benchmarks/scoring.py is excluded by none and included by none
📒 Files selected for processing (1)
  • tests/benchmarks/test_scoring.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/88-correctness-scorer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ayhammouda

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbiteu

coderabbiteu Bot commented Jul 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ayhammouda ayhammouda merged commit 89921f3 into main Jul 8, 2026
8 checks passed

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (2)
tests/benchmarks/test_scoring.py (2)

236-279: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add test coverage for per-tool-model rollup values.

The PR summary states per-tool-model rollups are written to scoring-rollups.json, and on_disk == rollups at line 279 provides structural validation, but no test asserts specific by_tool_model values. All tests use a single competitor ("tool-a"), so even if by_tool_model were checked, it would only have one entry. A test with two or more competitors would meaningfully validate the per-tool-model breakdown and catch regressions in that feature.

♻️ Suggested additional test
+def test_score_run_produces_per_tool_model_rollups(tmp_path: Path) -> None:
+    cells = [
+        (
+            _scoring_record(competitor_id="tool-a", corpus_id="SYN-EX-001"),
+            _transcript_record(competitor_id="tool-a", corpus_id="SYN-EX-001", answer=""),
+        ),
+        (
+            _scoring_record(competitor_id="tool-b", corpus_id="SYN-EX-001"),
+            _transcript_record(competitor_id="tool-b", corpus_id="SYN-EX-001", answer=""),
+        ),
+        (
+            _scoring_record(competitor_id="tool-b", corpus_id="SYN-EX-002"),
+            _transcript_record(
+                competitor_id="tool-b", corpus_id="SYN-EX-002", answer="pending answer"
+            ),
+        ),
+    ]
+    run_dir = _build_run_dir(tmp_path, cells)
+
+    result = _score(run_dir)
+    rollups = result["rollups"]
+
+    assert rollups["by_tool_model"]["tool-a"]["total_cells"] == 1
+    assert rollups["by_tool_model"]["tool-a"]["scored_cells"] == 1
+    assert rollups["by_tool_model"]["tool-b"]["total_cells"] == 2
+    assert rollups["by_tool_model"]["tool-b"]["scored_cells"] == 1
+    assert rollups["by_tool_model"]["tool-b"]["pending_manual_scoring_cells"] == 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/benchmarks/test_scoring.py` around lines 236 - 279, The current scoring
rollup test only validates overall and per-category aggregates, so it does not
actually verify the new by_tool_model breakdown. Update
test_score_run_produces_per_category_rollups to include at least two competitors
with distinct tool/model identifiers in the _scoring_record and
_transcript_record fixtures, then assert the specific rollup values under
result["rollups"]["by_tool_model"] and the persisted scoring-rollups.json for
each tool/model entry.

214-230: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider testing grounding evidence with error tool calls.

The test validates that non-error tool calls (is_error: False) set grounding_evidence_present to True, but no test covers is_error: True. If the scorer is expected to ignore failed tool calls when inferring grounding evidence, that behavior is untested. Adding a companion case would close this gap.

♻️ Suggested additional test
+def test_score_run_does_not_count_error_tool_calls_as_grounding_evidence(
+    tmp_path: Path,
+) -> None:
+    scoring = _scoring_record(competitor_id="tool-a", corpus_id="SYN-EX-001")
+    transcript = _transcript_record(
+        competitor_id="tool-a",
+        corpus_id="SYN-EX-001",
+        answer="Ungrounded candidate answer.",
+        tool_calls=[
+            {"tool": "search_docs", "arguments": {}, "result": None, "is_error": True}
+        ],
+    )
+    run_dir = _build_run_dir(tmp_path, [(scoring, transcript)])
+
+    _score(run_dir)
+
+    updated = _read_scoring(run_dir, "tool-a", "SYN-EX-001")
+    assert updated["grounding_evidence_present"] is False
+    assert _read_queue(run_dir)["cells"][0]["grounding_evidence_present"] is False
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/benchmarks/test_scoring.py` around lines 214 - 230, The
grounding-evidence test only covers successful tool calls, so add a companion
case in test_score_run_detects_grounding_evidence_from_tool_calls that uses a
transcript with is_error set to True and verifies how _score/_read_scoring
handle it. Reuse the existing helpers (_scoring_record, _transcript_record,
_build_run_dir, _score, _read_scoring, _read_queue) so the new assertion clearly
checks whether failed tool calls should or should not set
grounding_evidence_present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/benchmarks/test_scoring.py`:
- Around line 236-279: The current scoring rollup test only validates overall
and per-category aggregates, so it does not actually verify the new
by_tool_model breakdown. Update test_score_run_produces_per_category_rollups to
include at least two competitors with distinct tool/model identifiers in the
_scoring_record and _transcript_record fixtures, then assert the specific rollup
values under result["rollups"]["by_tool_model"] and the persisted
scoring-rollups.json for each tool/model entry.
- Around line 214-230: The grounding-evidence test only covers successful tool
calls, so add a companion case in
test_score_run_detects_grounding_evidence_from_tool_calls that uses a transcript
with is_error set to True and verifies how _score/_read_scoring handle it. Reuse
the existing helpers (_scoring_record, _transcript_record, _build_run_dir,
_score, _read_scoring, _read_queue) so the new assertion clearly checks whether
failed tool calls should or should not set grounding_evidence_present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 89b2a23f-74cb-4438-be43-b80ce218f489

📥 Commits

Reviewing files that changed from the base of the PR and between 548acd1 and b344322.

⛔ Files ignored due to path filters (2)
  • benchmarks/__main__.py is excluded by none and included by none
  • benchmarks/scoring.py is excluded by none and included by none
📒 Files selected for processing (1)
  • tests/benchmarks/test_scoring.py

ayhammouda added a commit that referenced this pull request Jul 8, 2026
…ding

Addresses both CodeRabbit nitpicks on PR #100 (additive changes to the
new test file only):

- test_score_run_produces_per_tool_model_rollups: two competitors, one
  with an explicit `id:provider/model` tool_model_key, asserting specific
  by_tool_model_key totals/scored/pending counts in the returned rollups
  and the persisted scoring-rollups.json. Adapted from CodeRabbit's
  template: the real rollup key is `by_tool_model_key`, not
  `by_tool_model`.
- test_score_run_does_not_count_error_tool_calls_as_grounding_evidence:
  a transcript whose only tool call has is_error: True does not set
  grounding_evidence_present, in the scoring record or the queue entry.

Refs #63.
@ayhammouda

Copy link
Copy Markdown
Owner Author

@coderabbitai Both nitpicks from the review are addressed in f37987c:

  1. Per-tool-model rollup values test — Added test_score_run_produces_per_tool_model_rollups with two competitors (tool-a, and tool-b carrying an explicit tool-b:openai/fake-model tool_model_key in the runner's id:provider/model format), asserting specific total_cells/scored_cells/pending_manual_scoring_cells values per entry and that the persisted scoring-rollups.json matches. One adaptation from the suggested diff: the actual rollup key is by_tool_model_key, not by_tool_model (the suggested test as written would KeyError). Keying on an explicit tool_model_key also verifies rollups group by tool_model_key rather than competitor_id.

  2. Error tool calls as grounding evidence — Added test_score_run_does_not_count_error_tool_calls_as_grounding_evidence: a transcript whose only tool call has is_error: True (and result: None) leaves grounding_evidence_present False in both the scoring record and the adjudication-queue entry. This matches the intended behavior of benchmarks.scoring._has_grounding_evidence, which only counts successful calls with a non-empty result payload.

Full validation gate re-run after the change: ruff (src/tests + benchmarks) clean, pyright (src + benchmarks) 0 errors, full pytest 426 passed, tests/benchmarks 119 passed, tests/benchmarks/test_scoring.py 21 passed, doctor all checks passed.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@ayhammouda Thanks for the detailed writeup — both changes look solid:

  1. Using by_tool_model_key instead of by_tool_model makes sense given that's the actual rollup key, and keying the test on an explicit tool_model_key is a good way to confirm grouping is by tool/model rather than competitor.
  2. The error-tool-call test correctly validates that _has_grounding_evidence only counts successful calls with a non-empty result.

Great to see the full validation gate (ruff, pyright, pytest across the whole suite and the new scoring tests, doctor checks) passing clean. Appreciate the thorough confirmation.

(‡•ᴥ•)ノ

@coderabbiteu

coderabbiteu Bot commented Jul 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@ayhammouda Thanks for the detailed follow-up and for catching the key-name discrepancy in my earlier suggested diff — by_tool_model_key (not by_tool_model) is indeed the correct key to assert against in scoring-rollups.json/_compute_rollups output. Both additions look solid:

  • test_score_run_produces_per_tool_model_rollups — good call using an explicit tool-b:openai/fake-model tool_model_key to prove grouping is by tool_model_key rather than competitor_id.
  • test_score_run_does_not_count_error_tool_calls_as_grounding_evidence — correctly pins down that _has_grounding_evidence requires a successful call with a non-empty result, not just any tool call.

Clean validation gate (ruff, pyright, full pytest suite, doctor) confirms this is good to go.

ᕦ(ò_óˇ)ᕤ

@ayhammouda

Copy link
Copy Markdown
Owner Author

Clarification on the comment above: this PR was merged ~1 minute before the CodeRabbit review completed, so commit f37987c on this branch cannot land through it. The identical change (cherry-picked, re-validated against current main with the full gate) is now follow-up PR #102.

ayhammouda added a commit that referenced this pull request Jul 9, 2026
…ding (#102)

Addresses both CodeRabbit nitpicks on PR #100 (additive changes to the
new test file only):

- test_score_run_produces_per_tool_model_rollups: two competitors, one
  with an explicit `id:provider/model` tool_model_key, asserting specific
  by_tool_model_key totals/scored/pending counts in the returned rollups
  and the persisted scoring-rollups.json. Adapted from CodeRabbit's
  template: the real rollup key is `by_tool_model_key`, not
  `by_tool_model`.
- test_score_run_does_not_count_error_tool_calls_as_grounding_evidence:
  a transcript whose only tool call has is_error: True does not set
  grounding_evidence_present, in the scoring record or the queue entry.

Refs #63.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-pr-opened Autonomous agent opened an implementation PR for this issue supervisor-review Vision supervisor decision required before further automation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v0.5.0] benchmark scoring — correctness scorer with manual-adjudication hooks

1 participant