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_bias → REPRESENTATIONAL and sexual_content → SEXUAL_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 Hate → HATE_SPEECH, SelfHarm → SELF_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.
Summary
#2570 is fixed for every shipped scorer/dataset pairing (#2575 consolidated the Likert scales and labels, #2616 added the
selfharmalias). This issue tracks the two durability gaps that remain, plus the broader taxonomy question raised on #2570.Verified on
mainata2d86755e.1. The regression test guards a different value than the runtime uses
test_azure_categories_match_registered_evaluationasserts against the registered category:But at runtime the value that reaches the matcher comes from the CSV row, not the registration (
pyrit/score/scorer_evaluation/scorer_evaluator.py):So renaming a CSV
harm_categorylabel keeps the test green while breaking evaluation. Renames on this axis do happen: #2575 movedfairness_bias→REPRESENTATIONALandsexual_content→SEXUAL_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
LikertScalePathsentries withevaluation_filesand allAzureContentFilterScorer._CATEGORY_EVAL_FILES) that reads the actual CSV label and asserts_select_evaluation_scoreaccepts the emitted category.2. A custom
file_mappingcan silently score against the wrong categoryfile_mappingis the first public parameter ofScorer.evaluate_async, so callers can supply their ownScorerEvalDatasetFilesand bypass the safe default registration.Matching is a set intersection over
HarmCategory.parse_many, and several aliases are one-to-many —hateresolves to bothHATE_SPEECHandREPRESENTATIONAL. The result is that a lone AzureHatescore is accepted for aREPRESENTATIONALdataset, andbiasis accepted forhate_speech:_select_evaluation_scoreis 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 (Hatevsviolence,Sexualvsviolence) are still correctly rejected.The one-to-many mapping is intentional for dataset classification —
tests/unit/models/test_harm_category.pyassertshatemaps 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
Hate→HATE_SPEECH,SelfHarm→SELF_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
HarmCategoryname/value nor in the alias map matches only when the strings are byte-identical:jailbreakvsjailbreak→ matchesJailbreakvsjailbreak→ raisesValueErrorAzure 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 theSexualpairing; #2616 added theselfharmalias).Worth noting that
exploitsandinformation_integrityboth resolve toOTHERtoday — 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.