feat(scorer): add aggregate(key, agg=metric) factory for dict-valued Score.value#3919
Closed
ppcvote wants to merge 1 commit into
Closed
feat(scorer): add aggregate(key, agg=metric) factory for dict-valued Score.value#3919ppcvote wants to merge 1 commit into
ppcvote wants to merge 1 commit into
Conversation
…Score.value Closes UKGovernmentBEIS#3735. Many scorers emit dict-valued `Score.value` (multiple numeric fields per sample) and then need per-key metrics at the task level. Today each eval re-implements that plumbing by hand. The mean-only `mean_of` helper in inspect_evals.utils.metrics solves the specific case for mean aggregation; this generalizes the pattern to any registered Metric. Public API ---------- from inspect_ai.scorer import aggregate, mean, metric, stderr @Metric def element_accuracy() -> Metric: return aggregate("element_acc", agg=mean(), on_missing="skip") @Metric def element_accuracy_stderr() -> Metric: return aggregate("element_acc", agg=stderr(), on_missing="skip") Files ----- src/inspect_ai/scorer/_metrics/aggregate.py (new -- factory + docstring) src/inspect_ai/scorer/_metrics/__init__.py (re-export) src/inspect_ai/scorer/__init__.py (public-API export) tests/scorer/test_aggregate.py (unit tests) CHANGELOG.md (Unreleased bullet) Implementation -------------- - Mirrors the wrapper shape of `grouped` (cast inner Metric -> MetricProtocol, rebuild SampleScore with unwrapped scalar value, call inner aggregator). - `on_missing` semantics match the existing `inspect_evals.utils.mean_of` helper for downstream-migration parity: `error` (default) -- raise ValueError `skip` -- exclude sample from aggregator `zero` -- treat missing entry as 0.0 - Preserves sample_id / sample_metadata / scorer / answer / explanation / metadata / history so the inner aggregator sees the same shape it would if a per-key scorer had been used directly. Tests (10 cases, all pass locally against inspect_ai 0.3.220): TestAggregateBasics::test_mean_of_key TestAggregateBasics::test_stderr_of_key TestAggregateBasics::test_std_of_key TestAggregateBasics::test_passes_metadata_through TestOnMissing::test_error_on_missing_key TestOnMissing::test_error_on_none_value TestOnMissing::test_skip_excludes_missing TestOnMissing::test_zero_treats_missing_as_zero TestValueShapes::test_rejects_non_dict_value TestValueShapes::test_to_float_converts_correctly Signed-off-by: ppcvote <risky9763@gmail.com>
Author
|
Closing as duplicate of #3850 — my miss. @antnewman claimed the issue on 2026-05-05 and opened the PR the same day; I didn't notice the existing PR cross-references when I audited the issue this morning before submitting. That's exactly the audit-before-PR check I should have run. @antnewman invited me to review #3850 instead. Doing that next. Apologies for the duplicate noise on the review queue. |
This was referenced May 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #3735.
Adds
aggregate(key, agg=metric)— a composition primitive that applies any registeredMetric(e.g.mean(),stderr(),std(),accuracy()) to the per-sample scalar values extracted atkeyfrom a dict-valuedScore.value. Many scorers ininspect_evalsemit dict-valued scores (multiple numeric fields per sample) and re-implement that plumbing by hand; this lifts the pattern to the framework.Public API
Signature:
Implementation notes
grouped: cast the innerMetrictoMetricProtocol, rebuild eachSampleScorewith the unwrapped scalar value, then call the inner aggregator on the rebuilt list.sample_id,sample_metadata,scorer, and the score'sanswer/explanation/metadata/historyso the inner aggregator sees the sameSampleScoreshape it would receive from a per-key scorer.on_missingsemantics matchinspect_evals.utils.metrics.mean_offor downstream-migration parity:"error"(default) — raiseValueErroron missing orNonevalue"skip"— exclude sample from the aggregator"zero"— treat missing orNonevalue as0.0Files
src/inspect_ai/scorer/_metrics/aggregate.py— new factory (~100 LOC including docstring)src/inspect_ai/scorer/_metrics/__init__.py— re-exportsrc/inspect_ai/scorer/__init__.py— public-API export, alphabetical__all__entrytests/scorer/test_aggregate.py— 10 unit tests (3 classes: basics, on_missing semantics, value-shape errors)CHANGELOG.md—UnreleasedbulletTests
10 cases, all pass locally against
inspect_ai==0.3.220:Verified manually:
aggregate("acc", agg=mean())over[{"acc": 1.0}, {"acc": 0.0}, {"acc": 0.5}]→0.5aggregate("acc", agg=stderr())over a four-sample bimodal series →≈ 0.289(non-zero, sensible)aggregate("acc", agg=mean(), on_missing="skip")over[{"acc": 1.0}, {"other": 0.0}, {"acc": 0.0}]→0.5aggregate("acc", agg=mean(), on_missing="zero")over the same →0.667Disclosure: AI-assistance
Per common OSS practice for AI-assisted contributions:
groupedandmean_ofpatterns.mean_of'son_missingsemantics, audited theScore/SampleScore/MetricProtocolAPI to make sureaggregatecould compose cleanly with any existing metric (not justmean), and ran the local smoke tests againstinspect_ai==0.3.220before submitting.DCO
The commit has a
Signed-off-byline.