Skip to content

Commit e48b6db

Browse files
authored
security benchmarks shouldn't fail annotation merge checks (#1279)
* security benchmarks shouldn't fail annotation merge checks even if they only have one annotator * noop; rename variable for clarity * only accept a single annotator if for security benchmarks with the ensemble annotator * singleton annotators are not ok except for security benchmarks with ensemble annotators * noop; add clarification
1 parent 86fe85c commit e48b6db

2 files changed

Lines changed: 38 additions & 6 deletions

File tree

src/modelbench/consistency_checker.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,15 @@ def __init__(self, search_engine: JournalSearch, sut, test):
247247
self.sut = sut
248248
self.test = test
249249

250+
# Security benchmarks with a single annotator are allowed.
251+
starting_run_entry = search_engine.query("starting run")
252+
if not starting_run_entry:
253+
starting_run_entry = search_engine.query("starting calibration run")
254+
benchmark = starting_run_entry[0]["benchmarks"][0].lower()
255+
is_security = "security" in benchmark
256+
is_default_annotator = "ensemble" not in benchmark
257+
self.allow_singleton_annotator = is_default_annotator ^ is_security
258+
250259
translated_responses = search_engine.query("translated sut response", sut=sut, test=test)
251260
self.response_by_id = {i["prompt_id"]: i["response_text"] for i in translated_responses}
252261

@@ -288,8 +297,9 @@ def _check_prompt(self, prompt):
288297
else:
289298
return "Blank responses should always be safe."
290299

291-
if len(self.prompt_annotations[prompt]) < 2 and measurement != 0.0:
292-
# Should be "unsafe" if there are less than 2 annotations.
300+
if len(self.prompt_annotations[prompt]) < 2 and measurement != 0.0 and not self.allow_singleton_annotator:
301+
# Should be "unsafe" if there are less than 2 annotations for general benchmarks or default annotator.
302+
# Security benchmarks with ensemble may use 1 annotator and that's ok.
293303
return "Too few annotations but merged annotation was marked as safe and valid."
294304
elif vote_counts[True] > vote_counts[False] and measurement != 1.0:
295305
# Should me marked as "safe".
@@ -453,7 +463,6 @@ def _collect_entities(self):
453463
self.benchmark = benchmarks[0]
454464
self.suts = starting_run_entry[0]["suts"]
455465
self.tests = starting_run_entry[0]["tests"]
456-
# TODO: This assumes that all tests use the same annotators! Which is fine for now but may not hold-up later on.
457466
try:
458467
secrets = load_secrets_from_config()
459468
test_obj = TESTS.make_instance(self.tests[0], secrets=secrets)

tests/modelbench_tests/test_consistency_checker.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,11 @@ def make_basic_run(
7979
annotators: List[str],
8080
hazard_tests: Dict[str, List[str]],
8181
calibration=False,
82+
benchmark="official",
8283
) -> FakeJournal:
8384
"""Successful "fresh" benchmark run with all SUT/annotator responses fetched (not cached).
8485
Measurements/annotations are all safe.
8586
Each hazard uses all tests."""
86-
benchmark = "official"
8787
journal = FakeJournal()
8888
start_message = "starting calibration run" if calibration else "starting run"
8989
journal.append(
@@ -374,21 +374,44 @@ def test_annotations_merged_correctly(tmp_path, basic_benchmark_run):
374374
assert subchecker.results[failed_row][subchecker._col_name(AnnotationsMergedCorrectly)] is True
375375

376376

377-
def test_annotations_merged_correctly_fails_with_one_annotator(tmp_path):
377+
@pytest.mark.parametrize("annotator_type,expected", (["default", True], ["ensemble", False]))
378+
def test_general_benchmark_annotations_merged_correctly_fails_with_singleton_ensemble(
379+
tmp_path, annotator_type, expected
380+
):
378381
# Simulate run with only 1 annotator.
379382
run = make_basic_run(
380383
suts=["sut1"],
381384
test_prompts={"test1": ["prompt1"]},
382385
annotators=["annotator1"],
383386
hazard_tests={"hazard1": ["test1"]},
387+
benchmark=f"official-{annotator_type}",
384388
)
385389
checker = init_checker_for_journal(tmp_path, run)
386390
checker.run()
387391

388392
subchecker = checker.test_sut_level_checker
389393
failed_row = subchecker._row_key(sut="sut1", test="test1")
390394
assert subchecker.check_is_complete()
391-
assert subchecker.results[failed_row][subchecker._col_name(AnnotationsMergedCorrectly)] is False
395+
assert subchecker.results[failed_row][subchecker._col_name(AnnotationsMergedCorrectly)] is expected
396+
397+
398+
@pytest.mark.parametrize("annotator_type", (["default"], ["ensemble"]))
399+
def test_security_benchmark_annotations_merged_correctly_passes_with_singleton_ensemble(tmp_path, annotator_type):
400+
# Simulate run with only 1 annotator.
401+
run = make_basic_run(
402+
suts=["sut1"],
403+
test_prompts={"test1": ["prompt1"]},
404+
annotators=["annotator1"],
405+
hazard_tests={"hazard1": ["test1"]},
406+
benchmark=f"security-{annotator_type}",
407+
)
408+
checker = init_checker_for_journal(tmp_path, run)
409+
checker.run()
410+
411+
subchecker = checker.test_sut_level_checker
412+
failed_row = subchecker._row_key(sut="sut1", test="test1")
413+
assert subchecker.check_is_complete()
414+
assert subchecker._col_name(AnnotationsMergedCorrectly) not in subchecker.results[failed_row]
392415

393416

394417
def test_annotations_merged_correctly_false_safe(tmp_path, basic_benchmark_run):

0 commit comments

Comments
 (0)