diff --git a/pyrit/score/float_scale/float_scale_score_aggregator.py b/pyrit/score/float_scale/float_scale_score_aggregator.py index a9752f7c43..ed4fed7311 100644 --- a/pyrit/score/float_scale/float_scale_score_aggregator.py +++ b/pyrit/score/float_scale/float_scale_score_aggregator.py @@ -94,8 +94,7 @@ def _build_rationale(scores: list[Score], *, aggregate_description: str) -> tupl rationale = scores[0].score_rationale or "" else: description = aggregate_description - # Only include scores with non-empty rationales - rationale_parts = [format_score_for_rationale(s) for s in scores if s.score_rationale] + rationale_parts = [format_score_for_rationale(s) for s in scores] rationale = "\n".join(rationale_parts) if rationale_parts else "" return description, rationale diff --git a/pyrit/score/score_utils.py b/pyrit/score/score_utils.py index f64ba099db..b1e00d3e83 100644 --- a/pyrit/score/score_utils.py +++ b/pyrit/score/score_utils.py @@ -77,11 +77,13 @@ def format_score_for_rationale(score: Score) -> str: score: The Score object to format. Returns: - Formatted string with scorer class, value, and rationale. + Formatted string with scorer class, value, categories (if any), and rationale. """ class_type = score.scorer_class_identifier.class_name or "Unknown" if score.scorer_class_identifier else "Unknown" value = score.score_value if score.score_value is not None else "undetermined" - return f" - {class_type} {value}: {score.score_rationale or ''}" + categories = [category for category in score.score_category or [] if category] + category_label = f" (Category: {', '.join(categories)})" if categories else "" + return f" - {class_type} {value}{category_label}: {score.score_rationale or ''}" def normalize_score_to_float(score: Score | None) -> float: diff --git a/tests/unit/score/test_float_scale_score_aggregator.py b/tests/unit/score/test_float_scale_score_aggregator.py index 37909e9b10..276ab51dcb 100644 --- a/tests/unit/score/test_float_scale_score_aggregator.py +++ b/tests/unit/score/test_float_scale_score_aggregator.py @@ -94,6 +94,20 @@ def test_float_scale_aggregator_category_deduplication(): assert results[0].category == ["Hate"] # Should be deduplicated +def test_float_scale_aggregator_preserves_scores_without_rationales(): + scores = [ + _mk_score(0.5, category=["Hate"], rationale="explained"), + _mk_score(0.7, category=["Violence"]), + ] + + result = FloatScaleScoreAggregator.MAX(scores)[0] + + assert result.rationale.splitlines() == [ + " - UnitTestScorer 0.5 (Category: Hate): explained", + " - UnitTestScorer 0.7 (Category: Violence): ", + ] + + def test_float_scale_aggregator_multiple_categories_preserved(): """Test that multiple unique categories are preserved and sorted.""" scores = [ diff --git a/tests/unit/score/test_score_utils.py b/tests/unit/score/test_score_utils.py index 880e5618f7..de222fb340 100644 --- a/tests/unit/score/test_score_utils.py +++ b/tests/unit/score/test_score_utils.py @@ -284,3 +284,27 @@ def test_handles_missing_rationale(self) -> None: assert "TestScorer" in result assert "False" in result + + def test_formats_categories_without_rationale(self) -> None: + score = Score( + score_type="float_scale", + score_value="0.8", + score_category=["Violence", "Hate"], + score_rationale="", + message_piece_id=str(uuid.uuid4()), + scorer_class_identifier=_make_scorer_id("TestScorer"), + ) + + assert format_score_for_rationale(score) == " - TestScorer 0.8 (Category: Violence, Hate): " + + def test_uncategorized_score_keeps_original_format(self) -> None: + score = Score( + score_type="float_scale", + score_value="0.2", + score_category=None, + score_rationale="explained", + message_piece_id=str(uuid.uuid4()), + scorer_class_identifier=_make_scorer_id("TestScorer"), + ) + + assert format_score_for_rationale(score) == " - TestScorer 0.2: explained"