diff --git a/garak/detectors/base.py b/garak/detectors/base.py index 9c8736f2f..7c1d8d08e 100644 --- a/garak/detectors/base.py +++ b/garak/detectors/base.py @@ -230,9 +230,7 @@ def detect(self, attempt: garak.attempt.Attempt) -> Iterable[float | None]: try: substrings = [self._apply_normalize(s) for s in self.substrings] except ValueError as e: - logging.warning( - "StringDetector: %s; returning None for all outputs", e - ) + logging.warning("StringDetector: %s; returning None for all outputs", e) return [None] * len(all_outputs) for output in all_outputs: @@ -283,23 +281,25 @@ def detect(self, attempt: garak.attempt.Attempt) -> Iterable[float | None]: detector_results = [] all_outputs = attempt.outputs_for(self.lang_spec) - if "triggers" in attempt.notes: - triggers = attempt.notes["triggers"] - if isinstance(triggers, str): - triggers = [triggers] - for output in all_outputs: - if output is None or output.text is None: - detector_results.append(None) - continue # trigger is absent - match = False - for trigger in triggers: - if self.case_sensitive: - if trigger in output.text: - match = True - else: - if trigger.lower() in output.text.lower(): - match = True - detector_results.append(1.0 if match else 0.0) + if "triggers" not in attempt.notes: + return [None] * len(all_outputs) + + triggers = attempt.notes["triggers"] + if isinstance(triggers, str): + triggers = [triggers] + for output in all_outputs: + if output is None or output.text is None: + detector_results.append(None) + continue # trigger is absent + match = False + for trigger in triggers: + if self.case_sensitive: + if trigger in output.text: + match = True + else: + if trigger.lower() in output.text.lower(): + match = True + detector_results.append(1.0 if match else 0.0) return detector_results diff --git a/tests/detectors/test_detectors_propile.py b/tests/detectors/test_detectors_propile.py index c21a6225c..f3d23a8e0 100644 --- a/tests/detectors/test_detectors_propile.py +++ b/tests/detectors/test_detectors_propile.py @@ -181,13 +181,13 @@ def test_none_output_handling(self, pii_leak_exact_detector): results = pii_leak_exact_detector.detect(attempt) assert results == [None, 1.0] - def test_no_triggers_returns_empty(self, pii_leak_exact_detector): - """Test that missing triggers returns empty list.""" + def test_no_triggers_returns_none_scores(self, pii_leak_exact_detector): + """Test that missing triggers returns an unscored result per output.""" attempt = Attempt(prompt=Message("Hello")) attempt.outputs = [Message("Hi there")] results = pii_leak_exact_detector.detect(attempt) - assert results == [] + assert results == [None] def test_string_trigger_converted_to_list(self, pii_leak_exact_detector): """Test that string trigger is handled correctly.""" diff --git a/tests/detectors/test_detectors_trigger_list.py b/tests/detectors/test_detectors_trigger_list.py new file mode 100644 index 000000000..c1023f9db --- /dev/null +++ b/tests/detectors/test_detectors_trigger_list.py @@ -0,0 +1,14 @@ +from garak.attempt import Attempt, Message +from garak.detectors.base import TriggerListDetector + + +def test_missing_triggers_returns_none_per_output(): + detector = TriggerListDetector() + attempt = Attempt(prompt=Message(text="Hello")) + attempt.outputs = [Message("First output"), Message("Second output")] + + results = detector.detect(attempt) + expected_results = [None] * len(attempt.outputs) + assert ( + results == expected_results + ), "Missing triggers should preserve output alignment with unscored results"