Skip to content

Scorer evaluation category matching is guarded on the wrong axis and can silently score the wrong category #2661

Description

@varunj-msft

Summary

#2570 is fixed for every shipped scorer/dataset pairing (#2575 consolidated the Likert scales and labels, #2616 added the selfharm alias). This issue tracks the two durability gaps that remain, plus the broader taxonomy question raised on #2570.

Verified on main at a2d86755e.

1. The regression test guards a different value than the runtime uses

test_azure_categories_match_registered_evaluation asserts against the registered category:

config = AzureContentFilterScorer._get_eval_files_for_category(category)
assert ScorerEvaluator._select_evaluation_score(scores=scores, harm_category=config.harm_category) is selected

But at runtime the value that reaches the matcher comes from the CSV row, not the registration (pyrit/score/scorer_evaluation/scorer_evaluator.py):

first_entry = labeled_dataset.entries[0]
if isinstance(first_entry, HarmHumanLabeledEntry):
    harm_category = first_entry.harm_category

So renaming a CSV harm_category label keeps the test green while breaking evaluation. Renames on this axis do happen: #2575 moved fairness_biasREPRESENTATIONAL and sexual_contentSEXUAL_CONTENT, correctly updating the registration and the CSV together. Nothing in the suite would have caught it if only one side had moved.

There is also no equivalent all-presets invariant over LikertScalePaths; only two presets are asserted individually.

Suggested fix: one parametrized test over every registered pairing (all LikertScalePaths entries with evaluation_files and all AzureContentFilterScorer._CATEGORY_EVAL_FILES) that reads the actual CSV label and asserts _select_evaluation_score accepts the emitted category.

2. A custom file_mapping can silently score against the wrong category

file_mapping is the first public parameter of Scorer.evaluate_async, so callers can supply their own ScorerEvalDatasetFiles and bypass the safe default registration.

Matching is a set intersection over HarmCategory.parse_many, and several aliases are one-to-many — hate resolves to both HATE_SPEECH and REPRESENTATIONAL. The result is that a lone Azure Hate score is accepted for a REPRESENTATIONAL dataset, and bias is accepted for hate_speech:

ScorerEvaluator._select_evaluation_score(
    scores=[Score(score_type="float_scale", score_value="0.5", score_category=["Hate"])],
    harm_category="REPRESENTATIONAL",
)  # returns the score instead of rejecting it

_select_evaluation_score is the guard meant to reject a mis-paired dataset, so accepting here produces metrics for the wrong category rather than a loud failure. Non-overlapping pairs (Hate vs violence, Sexual vs violence) are still correctly rejected.

The one-to-many mapping is intentional for dataset classification — tests/unit/models/test_harm_category.py asserts hate maps to both. The point is that taxonomy classification and scorer/dataset identity are different contracts.

Suggested fix: use a one-to-one crosswalk for scorer-evaluation matching (Azure HateHATE_SPEECH, SelfHarmSELF_HARM, …) and keep the one-to-many aliases for dataset classification. Alternatively, document and test the permissive behaviour as intentional.

3. The matcher is alias-map dependent, so new categories will break again

A value that is neither a canonical HarmCategory name/value nor in the alias map matches only when the strings are byte-identical:

  • jailbreak vs jailbreak → matches
  • Jailbreak vs jailbreak → raises ValueError

Azure emits CamelCase (SelfHarm) while datasets use snake_case, so the next new Azure category reproduces the original #2570 failure until someone hand-adds an alias. This area has already needed two rounds of label/alias work (#2575 added alias matching and fixed the Sexual pairing; #2616 added the selfharm alias).

Worth noting that exploits and information_integrity both resolve to OTHER today — they pass purely by exact string equality, with no alias backing them. A rename on either side breaks them with nothing to catch it.

This is the broader "should seed-taxonomy aliases be involved in scoring at all" question from #2570.

Not affected

All 12 currently registered pairings evaluate correctly, so this is about preventing recurrence rather than fixing a live break.


cc Rudra Prasad Bhuyan (@Rudra-G-23) — this is the concrete follow-up from #2570 if you would still like to pick it up.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions