FIX: strict scorer-eval category match + CSV-axis test - #2663
varunj-msft merged 2 commits into
Conversation
|
@microsoft-github-policy-service agree |
Fixes microsoft#2661. - Use HarmCategory.parse() for strict 1-to-1 canonical category matching instead of set intersection over parse_many(), preventing cross-category match between multi-value aliases (e.g., Hate vs REPRESENTATIONAL, bias vs hate_speech). - Add casefold fast-path in _score_matches_harm_category to handle casing discrepancies (e.g., Jailbreak vs jailbreak) and suppress spurious Unknown harm category warning logs for unaliased categories. - Replace registration-metadata unit test with an integration test parametrized over all 12 shipped pairings (4 Azure, 8 Likert) directly validating CSV datasets against emitted categories. - Add edge-case unit tests for cross-category rejection and case-folding.
82bf218 to
3655a30
Compare
|
Nice work — this is a complete fix for #2661. I ran the PR head directly and confirmed:
Two notes, neither blocking:
Optional, and genuinely useful if you want it: there's no assertion anywhere on One thing I'd like to sort out before this closes #2661, because it's cheap: the casefold fast path covers case-only differences but not separator ones.
|
varunj-msft
left a comment
There was a problem hiding this comment.
Verified at 124fbca3c after the branch update: both changed files are byte-identical to the previously reviewed head, so the merge-in of main altered nothing. All 12 shipped pairings still match (including the Azure SelfHarm path from #2616), the cross-category false matches are rejected, and targeted suites pass locally (491 passed). CI is green.
Merging. I'll note the separator-normalisation residual on #2661 so it isn't lost when this auto-closes.
|
Hi varunj-msft, It looks like the merge queue run failed on an unrelated frontend test ( This is the known JSDOM dialog focus/timing flake that also failed on Could you please re-enqueue or retry the merge queue run when you have a chance. Thank you! |
|
Confirmed and re-enqueued — it's at the front of the queue now. Your diagnosis checks out: run Thanks for tracking down the runs and linking #2645 — that saved me the digging. |
Description
Addresses the durability follow-ups identified in #2661 after #2570, #2575, and #2616:
test_azure_categories_match_registered_evaluationvalidated againstconfig.harm_categoryfrom the registration dictionary, whereas runtime evaluation inScorerEvaluator.evaluate_asyncextracts the label from the dataset CSV row (first_entry.harm_category). Renaming a dataset CSV label keeps the registration test green while breaking evaluation. Additionally,LikertScalePathsonly had 2 of 8 presets asserted (against hardcoded strings rather than evaluation datasets)._score_matches_harm_categoryperformed set-intersection overHarmCategory.parse_many(). Because aliases like"hate"and"bias"are one-to-many ({HATE_SPEECH, REPRESENTATIONAL}),Score(score_category=["Hate"])was accepted for aREPRESENTATIONALdataset, andScore(score_category=["bias"])was accepted for ahate_speechdataset.exploits,information_integrity,jailbreak) fall back toHarmCategory.OTHER, which gets stripped by theOTHERguard. The remaining check was an exact byte comparison (score_category == harm_category), causingScore(score_category=["Jailbreak"])vsjailbreakto raiseValueError.Changes
scorer_evaluator.py):casefold()fast-path check first, allowing unaliased strings (Jailbreakvsjailbreak,exploits) to match case-insensitively while avoiding unnecessaryHarmCategory.parse()calls that emit spuriousUnknown harm category ... mapping to OTHERlogger warnings.HarmCategory.parse(score) == HarmCategory.parse(label), retaining theOTHERguard (unknown ≠ unknown unless exact casefold match). BecauseHarmCategory.parse()resolves to the primary canonical category (parse_many()[0]),"Hate"resolves toHATE_SPEECHand rejectsREPRESENTATIONAL, while"bias"resolves toREPRESENTATIONALand rejectsHATE_SPEECH.test_scorer_evaluator.py):test_azure_categories_match_registered_evaluationwithtest_shipped_pairings_match_csv_evaluation, parametrized over all 12 shipped configurations (4 Azure Content Safety + 8 Likert presets with evaluation files).harm_category, asserts that registration metadata matches the CSV label, and verifies_select_evaluation_scorewith both single-score and multi-score responses.["Hate"]is rejected forREPRESENTATIONAL,["bias"]is rejected forhate_speech, and["Jailbreak"]is accepted forjailbreak.Closes #2661. Related to #2570.
Tests and Documentation
uv run pytest tests/unit/score/test_scorer_evaluator.py tests/unit/score/test_azure_content_filter.py tests/unit/score/test_self_ask_likert.py tests/unit/models/test_harm_category.py tests/unit/score/test_scorer_eval_csv_schema.py -q: 491 passed in 9.95s.uv run pytest tests/unit/score/ -q: 1797 passed in 219.14s.uv run ruff check pyrit/score/scorer_evaluation/scorer_evaluator.py tests/unit/score/test_scorer_evaluator.py: All checks passed.uv run ruff format --check pyrit/score/scorer_evaluation/scorer_evaluator.py tests/unit/score/test_scorer_evaluator.py: 2 files already formatted.