Repository navigation
fix(evaluator): preserve multi-alias targets for inclusion-based scoring - #1727
Conversation
TaskState.target always returned Target.text, which joins every accepted answer alternative into a single string with no separator (e.g. TriviaQA's ["Oprah Winfrey", "Oprah", ...] became "Oprah WinfreyOprah..."). Metrics that accept multiple alternatives - Accuracy(allow_inclusion=True), used by trivia_qa and mmlu_redux - branch on whether the reference is a str or a list, but always received the joined string, so a correct prediction could never exact-match the concatenated blob and every sample scored as incorrect regardless of the model's actual answer. Return the full list of alternatives when a sample has more than one, and keep returning the plain string in the common single-target case, which every other call site already expects.
Follow-up to the previous commit: TaskState.target can now return a list of alternatives, but ReviewResult (built from state.target when caching a review) still typed its own target field as Optional[str], so Pydantic validation rejected every multi-alias sample outright.
Yunnglin
left a comment
There was a problem hiding this comment.
Thanks for identifying the multi-alias scoring bug — the underlying issue is real, and preserving aliases for Accuracy(allow_inclusion=True) fixes the scoring path.
However, this currently breaks the predictions API for affected runs:
TaskState.target now returns list[str] → ReviewResult.from_score_state() persists that list → _build_prediction_row() assigns it to Gold, while PredictionRow.gold and the generated frontend contract still require Gold: string.
get_predictions() validates its successful response through PredictionsResponse, so a row with Gold=['A', 'B'] raises a Pydantic validation error and the endpoint returns 500. I reproduced this directly with PredictionsResponse.model_validate().
Could you please keep the serialized/display target as a string and expose aliases through a scoring-specific path, or update the complete API/frontend contract chain to support string | string[]? The former seems more scoped for this fix.
Please also add a regression test covering the full TaskState -> scoring -> ReviewResult -> predictions endpoint path for a multi-alias TriviaQA/MMLU-Redux sample.
Yunnglin
left a comment
There was a problem hiding this comment.
LGTM. The multi-reference scoring, cache migration, predictions API, and Dashboard rendering paths are covered by the updated tests and all required checks are green.
|
Thxxx @Yunnglin |
Bug
TaskState.targetalways returnsTarget.text, which joins every accepted answer alternative into a single string with no separator:For a benchmark like
trivia_qa, whose samples settargetto a list of answer aliases (record['answer']['aliases'] + record['answer']['normalized_aliases']), this collapses e.g.["Oprah Winfrey", "Oprah", "Winfrey, Oprah Gail", ...]into one unmatchable blob:"Oprah WinfreyOprahWinfrey, Oprah Gail...".Accuracy(allow_inclusion=True)(used bytrivia_qaandmmlu_redux) is explicitly written to accept either a single string or a list of alternatives:but every call site passes
task_state.target, which is always astr- soalternativesis always[the_joined_blob], and a correct prediction can never exact-match it. Every sample intrivia_qascores 0 regardless of whether the model's answer was right, because there's nothing left to match against.Confirmed on a real run: the model answered "Of which African country is Niamey the capital?" with
ANSWER: Niger(correct), and the sample still scored 0 - the gold reference used for grading was the fully-concatenated alias blob shown above.Fix
Return the full list of alternatives from
TaskState.targetwhen a sample has more than one, instead of always flattening toTarget.text. Single-target samples (the common case across the rest of the benchmark suite) are unaffected and still return a plain string, sincelen(self._target) > 1is false for them - so this is scoped to exactly the multi-alias case that was broken.Checked every other call site that reads
task_state.targetfor astr-only assumption (e.g.json.loads(task_state.target)inresearchrubrics) - none of them ever set a multi-item target, so they're untouched by this change.