Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pyrit/score/true_false/true_false_inverter_scorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)
from pyrit.score.scorer import Scorer
from pyrit.score.scorer_prompt_validator import ScorerPromptValidator
from pyrit.score.score_utils import ORIGINAL_FLOAT_VALUE_KEY
from pyrit.score.true_false.true_false_scorer import TrueFalseScorer


Expand Down Expand Up @@ -128,6 +129,10 @@ def _invert(self, scores: list[Score]) -> list[Score]:
)
else:
inv_score.score_value = str(True) if not inv_score.get_value() else str(False)
if inv_score.score_metadata and ORIGINAL_FLOAT_VALUE_KEY in inv_score.score_metadata:
inv_score.score_metadata[ORIGINAL_FLOAT_VALUE_KEY] = 1.0 - float(
inv_score.score_metadata[ORIGINAL_FLOAT_VALUE_KEY]
)
inv_score.score_value_description = "Inverted score: " + str(inv_score.score_value_description)
inv_score.score_rationale = (
f"Inverted score from {scorer_type} result: {inv_score.score_value}\n{inv_score.score_rationale}"
Expand Down
5 changes: 5 additions & 0 deletions pyrit/score/true_false/true_false_score_aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pyrit.score.score_utils import (
combine_metadata_and_categories,
format_score_for_rationale,
ORIGINAL_FLOAT_VALUE_KEY,
)

BinaryBoolOp = Callable[[bool | None, bool | None], bool | None]
Expand Down Expand Up @@ -144,6 +145,10 @@ def aggregator(scores: Iterable[Score]) -> ScoreAggregatorResult:
undetermined_msg=undetermined_msg,
)
metadata, category = combine_metadata_and_categories(scores_list)
# A single threshold score retains its precise float for feedback. Once multiple
# scorers are combined, no single child's float can represent the aggregate verdict.
if len(scores_list) > 1:
metadata.pop(ORIGINAL_FLOAT_VALUE_KEY, None)

return ScoreAggregatorResult(
value=result,
Expand Down
38 changes: 38 additions & 0 deletions tests/unit/score/test_true_false_inverter_scorer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

from unittest.mock import MagicMock

from pyrit.models import ComponentIdentifier, Score
from pyrit.score.score_utils import ORIGINAL_FLOAT_VALUE_KEY, normalize_score_to_float
from pyrit.score.true_false.true_false_inverter_scorer import TrueFalseInverterScorer
from pyrit.score.true_false.true_false_scorer import TrueFalseScorer


def _score(value: bool, original_float: float) -> Score:
return Score(
score_value=str(value),
score_value_description="threshold",
score_type="true_false",
score_metadata={ORIGINAL_FLOAT_VALUE_KEY: original_float},
message_piece_id="test-piece",
scorer_class_identifier=ComponentIdentifier(
class_name="FloatScaleThresholdScorer",
class_module="tests.unit.score",
),
)


def test_inverter_complements_original_float_metadata() -> None:
wrapped = MagicMock(spec=TrueFalseScorer)
wrapped.get_identifier.return_value = ComponentIdentifier(
class_name="FloatScaleThresholdScorer",
class_module="tests.unit.score",
)
inverter = TrueFalseInverterScorer(scorer=wrapped)

inverted = inverter._invert([_score(True, 0.73)])[0]

assert inverted.get_value() is False
assert inverted.score_metadata == {ORIGINAL_FLOAT_VALUE_KEY: 0.27}
assert normalize_score_to_float(inverted) == 0.27
24 changes: 24 additions & 0 deletions tests/unit/score/test_true_false_score_aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,27 @@ def test_generator_of_wrong_type_still_raises():
)
with pytest.raises(ValueError, match="must be of type 'true_false'"):
TrueFalseScoreAggregator.OR(s for s in [bad])


def test_single_threshold_float_metadata_is_preserved() -> None:
from pyrit.score.score_utils import ORIGINAL_FLOAT_VALUE_KEY

score = _mk_score(True, prr_id="1")
score.score_metadata = {ORIGINAL_FLOAT_VALUE_KEY: 0.73}

res = TrueFalseScoreAggregator.AND([score])

assert res.metadata == {ORIGINAL_FLOAT_VALUE_KEY: 0.73}


def test_composite_drops_ambiguous_threshold_float_metadata() -> None:
from pyrit.score.score_utils import ORIGINAL_FLOAT_VALUE_KEY

first = _mk_score(True, prr_id="1")
first.score_metadata = {ORIGINAL_FLOAT_VALUE_KEY: 0.73}
second = _mk_score(True, prr_id="1")
second.score_metadata = {ORIGINAL_FLOAT_VALUE_KEY: 0.41}

res = TrueFalseScoreAggregator.AND([first, second])

assert ORIGINAL_FLOAT_VALUE_KEY not in res.metadata