Skip to content

Commit afebeb4

Browse files
FEAT: Update evaluate_scorers (microsoft#1406)
Co-authored-by: Richard Lundeen <rlundeen@microsoft.com>
1 parent d1ce4ce commit afebeb4

10 files changed

Lines changed: 807 additions & 188 deletions

File tree

build_scripts/evaluate_scorers.py

Lines changed: 21 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,15 @@
1212
"""
1313

1414
import asyncio
15-
import os
1615
import sys
1716
import time
1817

19-
from azure.ai.contentsafety.models import TextCategory
2018
from tqdm import tqdm
2119

2220
from pyrit.common.path import SCORER_EVALS_PATH
23-
from pyrit.prompt_target import OpenAIChatTarget
24-
from pyrit.score import (
25-
AzureContentFilterScorer,
26-
FloatScaleThresholdScorer,
27-
LikertScalePaths,
28-
SelfAskLikertScorer,
29-
SelfAskRefusalScorer,
30-
SelfAskScaleScorer,
31-
TrueFalseCompositeScorer,
32-
TrueFalseInverterScorer,
33-
TrueFalseScoreAggregator,
34-
)
35-
from pyrit.score.true_false.self_ask_true_false_scorer import (
36-
SelfAskTrueFalseScorer,
37-
TrueFalseQuestionPaths,
38-
)
21+
from pyrit.registry import ScorerRegistry
3922
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
23+
from pyrit.setup.initializers import ScorerInitializer, TargetInitializer
4024

4125

4226
async def evaluate_scorers() -> None:
@@ -45,137 +29,42 @@ async def evaluate_scorers() -> None:
4529
4630
This will:
4731
1. Initialize PyRIT with in-memory database
48-
2. Create a shared chat target for consistency
49-
3. Instantiate each scorer with appropriate configuration
32+
2. Register all scorers from ScorerInitializer into the ScorerRegistry
33+
3. Iterate through all registered scorers
5034
4. Run evaluate_async() on each scorer
5135
5. Save results to scorer_evals directory
5236
"""
5337
print("Initializing PyRIT...")
54-
await initialize_pyrit_async(memory_db_type=IN_MEMORY)
55-
56-
# Targets
57-
gpt_4o_target = OpenAIChatTarget(
58-
endpoint=os.environ.get("AZURE_OPENAI_GPT4O_ENDPOINT"),
59-
api_key=os.environ.get("AZURE_OPENAI_GPT4O_KEY"),
60-
model_name=os.environ.get("AZURE_OPENAI_GPT4O_MODEL"),
61-
)
62-
63-
gpt_4o_unsafe = OpenAIChatTarget(
64-
endpoint=os.environ.get("AZURE_OPENAI_GPT4O_UNSAFE_CHAT_ENDPOINT"),
65-
api_key=os.environ.get("AZURE_OPENAI_GPT4O_UNSAFE_CHAT_KEY"),
66-
model_name=os.environ.get("AZURE_OPENAI_GPT4O_UNSAFE_CHAT_MODEL"),
67-
)
68-
69-
gpt_4o_unsafe_temp9 = OpenAIChatTarget(
70-
endpoint=os.environ.get("AZURE_OPENAI_GPT4O_UNSAFE_CHAT_ENDPOINT"),
71-
api_key=os.environ.get("AZURE_OPENAI_GPT4O_UNSAFE_CHAT_KEY"),
72-
model_name=os.environ.get("AZURE_OPENAI_GPT4O_UNSAFE_CHAT_MODEL"),
73-
temperature=0.9,
74-
)
75-
76-
# Refusal Scorer
77-
refusal = SelfAskRefusalScorer(chat_target=gpt_4o_target)
78-
79-
# Objective Scorers
80-
refusal_gpt_4o = TrueFalseInverterScorer(scorer=SelfAskRefusalScorer(chat_target=gpt_4o_target))
81-
82-
refusal_gpt_4o_unsafe = TrueFalseInverterScorer(scorer=SelfAskRefusalScorer(chat_target=gpt_4o_unsafe))
83-
84-
refusal_gpt_4o_unsafe_temp9 = TrueFalseInverterScorer(scorer=SelfAskRefusalScorer(chat_target=gpt_4o_unsafe_temp9))
85-
86-
acs_t1 = FloatScaleThresholdScorer(scorer=AzureContentFilterScorer(), threshold=0.1)
87-
acs_t5 = FloatScaleThresholdScorer(scorer=AzureContentFilterScorer(), threshold=0.5)
88-
acs_t7 = FloatScaleThresholdScorer(scorer=AzureContentFilterScorer(), threshold=0.7)
89-
90-
acs_with_refusal = TrueFalseCompositeScorer(
91-
aggregator=TrueFalseScoreAggregator.AND,
92-
scorers=[
93-
FloatScaleThresholdScorer(scorer=AzureContentFilterScorer(), threshold=0.5),
94-
TrueFalseInverterScorer(
95-
scorer=SelfAskRefusalScorer(chat_target=gpt_4o_unsafe_temp9),
96-
),
97-
],
38+
await initialize_pyrit_async(
39+
memory_db_type=IN_MEMORY,
40+
initializers=[TargetInitializer(tags=["default", "scorer"]), ScorerInitializer()],
9841
)
9942

100-
_scale_scorer_gpt_4o = SelfAskScaleScorer(
101-
chat_target=OpenAIChatTarget(
102-
endpoint=os.environ.get("AZURE_OPENAI_GPT4O_ENDPOINT"),
103-
api_key=os.environ.get("AZURE_OPENAI_GPT4O_KEY"),
104-
model_name=os.environ.get("AZURE_OPENAI_GPT4O_MODEL"),
105-
temperature=0.9,
106-
),
107-
)
43+
registry = ScorerRegistry.get_registry_singleton()
44+
scorer_names = registry.get_names()
10845

109-
task_achieved_tf_scorer = SelfAskTrueFalseScorer(
110-
chat_target=OpenAIChatTarget(
111-
endpoint=os.environ.get("AZURE_OPENAI_GPT4O_ENDPOINT"),
112-
api_key=os.environ.get("AZURE_OPENAI_GPT4O_KEY"),
113-
model_name=os.environ.get("AZURE_OPENAI_GPT4O_MODEL"),
114-
temperature=0.9,
115-
),
116-
true_false_question_path=TrueFalseQuestionPaths.TASK_ACHIEVED.value,
117-
)
46+
if not scorer_names:
47+
print("No scorers registered. Check environment variable configuration.")
48+
return
11849

119-
scale_gpt_4o_9_threshhold = FloatScaleThresholdScorer(scorer=_scale_scorer_gpt_4o, threshold=0.9)
120-
121-
scale_and_refusal = TrueFalseCompositeScorer(
122-
scorers=[scale_gpt_4o_9_threshhold, refusal_gpt_4o],
123-
aggregator=TrueFalseScoreAggregator.AND,
124-
)
125-
126-
# Harm Scorers
127-
likert_scorers_gpt_4o = [
128-
SelfAskLikertScorer(
129-
chat_target=gpt_4o_target,
130-
likert_scale=scale,
131-
)
132-
for scale in LikertScalePaths
133-
if scale.evaluation_files is not None
134-
]
135-
136-
acs_hate = AzureContentFilterScorer(harm_categories=[TextCategory.HATE])
137-
acs_self_harm = AzureContentFilterScorer(harm_categories=[TextCategory.SELF_HARM])
138-
acs_sexual = AzureContentFilterScorer(harm_categories=[TextCategory.SEXUAL])
139-
acs_violence = AzureContentFilterScorer(harm_categories=[TextCategory.VIOLENCE])
140-
141-
# Build list of scorers to evaluate
142-
scorers = [
143-
refusal,
144-
refusal_gpt_4o,
145-
refusal_gpt_4o_unsafe,
146-
refusal_gpt_4o_unsafe_temp9,
147-
acs_t1,
148-
acs_t5,
149-
acs_t7,
150-
acs_with_refusal,
151-
scale_gpt_4o_9_threshhold,
152-
scale_and_refusal,
153-
acs_hate,
154-
acs_self_harm,
155-
acs_sexual,
156-
acs_violence,
157-
task_achieved_tf_scorer,
158-
]
159-
160-
scorers.extend(likert_scorers_gpt_4o)
161-
162-
print(f"\nEvaluating {len(scorers)} scorer(s)...\n")
50+
print(f"\nEvaluating {len(scorer_names)} scorer(s)...\n")
16351

16452
# Use tqdm for progress tracking across all scorers
165-
scorer_iterator = tqdm(enumerate(scorers, 1), total=len(scorers), desc="Scorers") if tqdm else enumerate(scorers, 1)
53+
scorer_iterator = (
54+
tqdm(enumerate(scorer_names, 1), total=len(scorer_names), desc="Scorers")
55+
if tqdm
56+
else enumerate(scorer_names, 1)
57+
)
16658

16759
# Evaluate each scorer
168-
for i, scorer in scorer_iterator:
169-
scorer_name = scorer.__class__.__name__
170-
print(f"\n[{i}/{len(scorers)}] Evaluating {scorer_name}...")
60+
for i, scorer_name in scorer_iterator:
61+
scorer = registry.get_instance_by_name(scorer_name)
62+
print(f"\n[{i}/{len(scorer_names)}] Evaluating {scorer_name}...")
17163
print(" Status: Starting evaluation (this may take several minutes)...")
17264

17365
start_time = time.time()
17466

17567
try:
176-
# Run evaluation with production settings:
177-
# - num_scorer_trials=3 for variance measurement
178-
# - add_to_evaluation_results=True to save to registry
17968
print(" Status: Running evaluations...")
18069
results = await scorer.evaluate_async(
18170
num_scorer_trials=3,
@@ -184,7 +73,6 @@ async def evaluate_scorers() -> None:
18473

18574
elapsed_time = time.time() - start_time
18675

187-
# Results are saved to disk by evaluate_async() with add_to_evaluation_results=True
18876
print(" ✓ Evaluation complete and saved!")
18977
print(f" Elapsed time: {elapsed_time:.1f}s")
19078
if results:

doc/api.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,8 @@ API Reference
719719

720720
PyRITInitializer
721721
AIRTInitializer
722-
AIRTTargetInitializer
722+
TargetInitializer
723+
ScorerInitializer
723724
SimpleInitializer
724725
LoadDefaultDatasets
725726
ScenarioObjectiveListInitializer

doc/code/registry/2_instance_registry.ipynb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@
196196
"source": [
197197
"## Using Target Initializer\n",
198198
"\n",
199-
"You can optionally use the `AIRTTargetInitializer` to automatically configure and register targets that use commonly used environment variables (from `.env_example`). This initializer does not strictly require any environment variables - it simply registers whatever endpoints are available."
199+
"You can optionally use the `TargetInitializer` to automatically configure and register targets that use commonly used environment variables (from `.env_example`). This initializer does not strictly require any environment variables - it simply registers whatever endpoints are available."
200200
]
201201
},
202202
{
@@ -218,18 +218,18 @@
218218
"source": [
219219
"from pyrit.registry import TargetRegistry\n",
220220
"from pyrit.setup import initialize_pyrit_async\n",
221-
"from pyrit.setup.initializers import AIRTTargetInitializer\n",
221+
"from pyrit.setup.initializers import TargetInitializer\n",
222222
"\n",
223223
"# Using built-in initializer\n",
224224
"await initialize_pyrit_async( # type: ignore\n",
225-
" memory_db_type=\"InMemory\", initializers=[AIRTTargetInitializer()]\n",
225+
" memory_db_type=\"InMemory\", initializers=[TargetInitializer()]\n",
226226
")\n",
227227
"\n",
228228
"# Get the registry singleton\n",
229229
"registry = TargetRegistry.get_registry_singleton()\n",
230230
"# List registered targets\n",
231231
"target_names = registry.get_names()\n",
232-
"print(f\"Registered targets after AIRT initialization: {target_names}\")"
232+
"print(f\"Registered targets after initialization: {target_names}\")"
233233
]
234234
}
235235
],

doc/code/registry/2_instance_registry.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,20 @@
100100
# %% [markdown]
101101
# ## Using Target Initializer
102102
#
103-
# You can optionally use the `AIRTTargetInitializer` to automatically configure and register targets that use commonly used environment variables (from `.env_example`). This initializer does not strictly require any environment variables - it simply registers whatever endpoints are available.
103+
# You can optionally use the `TargetInitializer` to automatically configure and register targets that use commonly used environment variables (from `.env_example`). This initializer does not strictly require any environment variables - it simply registers whatever endpoints are available.
104104

105105
# %%
106106
from pyrit.registry import TargetRegistry
107107
from pyrit.setup import initialize_pyrit_async
108-
from pyrit.setup.initializers import AIRTTargetInitializer
108+
from pyrit.setup.initializers import TargetInitializer
109109

110110
# Using built-in initializer
111111
await initialize_pyrit_async( # type: ignore
112-
memory_db_type="InMemory", initializers=[AIRTTargetInitializer()]
112+
memory_db_type="InMemory", initializers=[TargetInitializer()]
113113
)
114114

115115
# Get the registry singleton
116116
registry = TargetRegistry.get_registry_singleton()
117117
# List registered targets
118118
target_names = registry.get_names()
119-
print(f"Registered targets after AIRT initialization: {target_names}")
119+
print(f"Registered targets after initialization: {target_names}")

pyrit/setup/initializers/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"""PyRIT initializers package."""
55

66
from pyrit.setup.initializers.airt import AIRTInitializer
7-
from pyrit.setup.initializers.airt_targets import AIRTTargetInitializer
7+
from pyrit.setup.initializers.components.scorers import ScorerInitializer
8+
from pyrit.setup.initializers.components.targets import TargetInitializer
89
from pyrit.setup.initializers.pyrit_initializer import PyRITInitializer
910
from pyrit.setup.initializers.scenarios.load_default_datasets import LoadDefaultDatasets
1011
from pyrit.setup.initializers.scenarios.objective_list import ScenarioObjectiveListInitializer
@@ -14,7 +15,8 @@
1415
__all__ = [
1516
"PyRITInitializer",
1617
"AIRTInitializer",
17-
"AIRTTargetInitializer",
18+
"ScorerInitializer",
19+
"TargetInitializer",
1820
"SimpleInitializer",
1921
"LoadDefaultDatasets",
2022
"ScenarioObjectiveListInitializer",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT license.
3+
4+
"""Component initializers for targets, scorers, and other components."""
5+
6+
from pyrit.setup.initializers.components.scorers import ScorerInitializer
7+
from pyrit.setup.initializers.components.targets import TargetConfig, TargetInitializer
8+
9+
__all__ = [
10+
"ScorerInitializer",
11+
"TargetConfig",
12+
"TargetInitializer",
13+
]

0 commit comments

Comments
 (0)