Skip to content

Commit 9f55bfb

Browse files
ValbuenaVCVictor ValbuenaCopilot
authored
FEAT: Adversarial Benchmark Scenario Refactor (#1765)
Co-authored-by: Victor Valbuena <vvalbuena@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 30057fd commit 9f55bfb

38 files changed

Lines changed: 2627 additions & 973 deletions

.env_example

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,24 @@ AZURE_OPENAI_GPT4O_UNSAFE_CHAT_MODEL2="deployment-name"
7575
AZURE_OPENAI_GPT4O_UNSAFE_CHAT_UNDERLYING_MODEL2=""
7676

7777
# Adversarial chat target (used by scenario attack techniques, e.g. role-play, TAP)
78+
# Default endpoint goes here; specialized ones below
7879
ADVERSARIAL_CHAT_ENDPOINT="https://xxxxx.openai.azure.com/openai/v1"
7980
ADVERSARIAL_CHAT_KEY="xxxxx"
8081
ADVERSARIAL_CHAT_MODEL="deployment-name"
8182

83+
ADVERSARIAL_CHAT_SINGLETURN_ENDPOINT="https://xxxxxx.westus3.inference.ml.azure.com/score"
84+
ADVERSARIAL_CHAT_SINGLETURN_KEY="xxxxx"
85+
ADVERSARIAL_CHAT_SINGLETURN_MODEL="deployment-name"
86+
87+
ADVERSARIAL_CHAT_MULTITURN_ENDPOINT="https://xxxxxx.westus3.inference.ml.azure.com/score"
88+
ADVERSARIAL_CHAT_MULTITURN_KEY="xxxxx"
89+
ADVERSARIAL_CHAT_MULTITURN_MODEL="deployment-name"
90+
91+
ADVERSARIAL_CHAT_REASONING_ENDPOINT="https://xxxxxx.westus3.inference.ml.azure.com/score"
92+
ADVERSARIAL_CHAT_REASONING_KEY="xxxxx"
93+
ADVERSARIAL_CHAT_REASONING_MODEL="deployment-name"
94+
95+
8296
# Objective Scorer chat target (used in scorers in scenarios)
8397
OBJECTIVE_SCORER_CHAT_ENDPOINT="https://xxxxx.openai.azure.com/openai/v1"
8498
OBJECTIVE_SCORER_CHAT_KEY="xxxxx"

doc/scanner/benchmark.ipynb

Lines changed: 73 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
"source": [
88
"# Benchmark Scenarios\n",
99
"\n",
10-
"Benchmark scenarios are a subset of scenarios that compare the effectiveness of attacks across an axis that varies within the scenario itself. The axis can be many things; currently, the only benchmark variant is the adversarial benchmark, whose axis of change is the adversarial model used in attacks."
10+
"Benchmark scenarios compare attack effectiveness across an axis that varies within the scenario\n",
11+
"itself. Currently the only benchmark variant is the adversarial benchmark, whose axis of change is\n",
12+
"the **adversarial chat helper model** used in attacks. For full configuration options see\n",
13+
"`pyrit_scan --help` and the [Scenarios Programming Guide](../code/scenarios/0_scenarios.ipynb)."
1114
]
1215
},
1316
{
@@ -16,143 +19,97 @@
1619
"metadata": {},
1720
"source": [
1821
"## Adversarial Benchmark\n",
19-
"The adversarial benchmarking scenario (`AdversarialBenchmark`) compares the effectiveness of different adversarial models in successfully executing attacks against a target model."
22+
"\n",
23+
"`AdversarialBenchmark` holds the objective target and dataset constant and varies the adversarial\n",
24+
"chat model used to drive multi-turn attacks. Useful for evaluating which adversarial helper\n",
25+
"models produce stronger or weaker attack success rates against the same target.\n",
26+
"\n",
27+
"Adversarial targets are user-provided via the `adversarial_targets` scenario parameter. Each name\n",
28+
"must already be registered in `TargetRegistry` — typically by `TargetInitializer` from the\n",
29+
"`ADVERSARIAL_CHAT_*` env vars (see `.env_example`). Use `pyrit_scan --list-targets` to see every\n",
30+
"target currently registered.\n",
31+
"\n",
32+
"```bash\n",
33+
"pyrit_scan benchmark.adversarial \\\n",
34+
" --initializers target load_default_datasets \\\n",
35+
" --target openai_chat \\\n",
36+
" --adversarial-targets adversarial_chat_singleturn adversarial_chat_multiturn \\\n",
37+
" --max-dataset-size 4\n",
38+
"```\n",
39+
"\n",
40+
"Pass multiple `--adversarial-targets` values to compare across models in a single run.\n",
41+
"\n",
42+
"**Available strategies:** `light` (default — a quick snapshot using the cheaper techniques),\n",
43+
"`single_turn`, `multi_turn`, plus one member per adversarial-capable source technique\n",
44+
"(e.g. `red_teaming`, `tap`, `crescendo_simulated`). The `light` aggregate excludes `tap` and\n",
45+
"`crescendo_simulated`, which can take hours."
46+
]
47+
},
48+
{
49+
"cell_type": "markdown",
50+
"id": "2",
51+
"metadata": {},
52+
"source": [
53+
"## Setup"
2054
]
2155
},
2256
{
2357
"cell_type": "code",
2458
"execution_count": null,
25-
"id": "2",
59+
"id": "3",
2660
"metadata": {},
27-
"outputs": [
28-
{
29-
"name": "stdout",
30-
"output_type": "stream",
31-
"text": [
32-
"Found default environment files: ['./.pyrit/.env', './.pyrit/.env.local']\n",
33-
"Loaded environment file: ./.pyrit/.env\n",
34-
"Loaded environment file: ./.pyrit/.env.local\n"
35-
]
36-
},
37-
{
38-
"data": {
39-
"application/vnd.jupyter.widget-view+json": {
40-
"model_id": "8316db039ba1408499df0a2de6c8d6f6",
41-
"version_major": 2,
42-
"version_minor": 0
43-
},
44-
"text/plain": [
45-
"Executing AdversarialBenchmark: 0%| | 0/3 [00:00<?, ?attack/s]"
46-
]
47-
},
48-
"metadata": {},
49-
"output_type": "display_data"
50-
},
51-
{
52-
"name": "stdout",
53-
"output_type": "stream",
54-
"text": [
55-
"Scenario result id: e01b35d2-c7f8-49bd-aafd-5d44ef7235f4\n",
56-
"\n",
57-
"\u001b[36m====================================================================================================\u001b[0m\n",
58-
"\u001b[1m\u001b[36m 📊 SCENARIO RESULTS: AdversarialBenchmark \u001b[0m\n",
59-
"\u001b[36m====================================================================================================\u001b[0m\n",
60-
"\n",
61-
"\u001b[1m\u001b[36m▼ Scenario Information\u001b[0m\n",
62-
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
63-
"\u001b[1m 📋 Scenario Details\u001b[0m\n",
64-
"\u001b[36m • Name: AdversarialBenchmark\u001b[0m\n",
65-
"\u001b[36m • Scenario Version: 1\u001b[0m\n",
66-
"\u001b[36m • PyRIT Version: 0.14.0.dev0\u001b[0m\n",
67-
"\u001b[36m • Description:\u001b[0m\n",
68-
"\u001b[36m Benchmarking scenario that compares the attack success rate (ASR) of several different adversarial models.\u001b[0m\n",
69-
"\n",
70-
"\u001b[1m 🎯 Target Information\u001b[0m\n",
71-
"\u001b[36m • Target Type: OpenAIChatTarget\u001b[0m\n",
72-
"\u001b[36m • Target Model: gpt-4o-japan-nilfilter\u001b[0m\n",
73-
"\u001b[36m • Target Endpoint: https://pyrit-japan-test.openai.azure.com/openai/v1\u001b[0m\n",
74-
"\n",
75-
"\u001b[1m 📊 Scorer Information\u001b[0m\n",
76-
"\u001b[37m ▸ Scorer Identifier\u001b[0m\n",
77-
"\u001b[36m • Scorer Type: TrueFalseInverterScorer\u001b[0m\n",
78-
"\u001b[36m • scorer_type: true_false\u001b[0m\n",
79-
"\u001b[36m • score_aggregator: OR_\u001b[0m\n",
80-
"\u001b[36m └─ Composite of 1 scorer(s):\u001b[0m\n",
81-
"\u001b[36m • Scorer Type: SelfAskRefusalScorer\u001b[0m\n",
82-
"\u001b[36m • scorer_type: true_false\u001b[0m\n",
83-
"\u001b[36m • score_aggregator: OR_\u001b[0m\n",
84-
"\u001b[36m • model_name: gpt-4o-japan-nilfilter\u001b[0m\n",
85-
"\n",
86-
"\u001b[37m ▸ Performance Metrics\u001b[0m\n",
87-
"\u001b[33m Official evaluation has not been run yet for this specific configuration\u001b[0m\n",
88-
"\n",
89-
"\u001b[1m\u001b[36m▼ Overall Statistics\u001b[0m\n",
90-
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
91-
"\u001b[1m 📈 Summary\u001b[0m\n",
92-
"\u001b[32m • Total Strategies: 3\u001b[0m\n",
93-
"\u001b[32m • Total Attack Results: 24\u001b[0m\n",
94-
"\u001b[36m • Overall Success Rate: 25%\u001b[0m\n",
95-
"\u001b[32m • Unique Objectives: 8\u001b[0m\n",
96-
"\n",
97-
"\u001b[1m\u001b[36m▼ Per-Group Breakdown\u001b[0m\n",
98-
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
99-
"\n",
100-
"\u001b[1m 🔸 Group: gpt-4o-japan-nilfilter\u001b[0m\n",
101-
"\u001b[33m • Number of Results: 24\u001b[0m\n",
102-
"\u001b[36m • Success Rate: 25%\u001b[0m\n",
103-
"\n",
104-
"\u001b[36m====================================================================================================\u001b[0m\n",
105-
"\n"
106-
]
107-
}
108-
],
61+
"outputs": [],
10962
"source": [
11063
"from pyrit.output import output_scenario_async\n",
11164
"from pyrit.prompt_target import OpenAIChatTarget\n",
65+
"from pyrit.scenario import DatasetConfiguration\n",
11266
"from pyrit.scenario.scenarios.benchmark import AdversarialBenchmark\n",
11367
"from pyrit.setup import IN_MEMORY, initialize_pyrit_async\n",
114-
"from pyrit.setup.initializers import LoadDefaultDatasets\n",
68+
"from pyrit.setup.initializers import LoadDefaultDatasets, ScorerInitializer, TargetInitializer\n",
11569
"\n",
116-
"await initialize_pyrit_async(memory_db_type=IN_MEMORY, initializers=[LoadDefaultDatasets()]) # type: ignore\n",
117-
"\n",
118-
"# Pass any number of adversarial PromptTarget instances (with chat-target\n",
119-
"# capabilities \u2014 multi-turn and editable history) as a list; AdversarialBenchmark\n",
120-
"# infers a label for each from its identifier and runs every benchmark-friendly\n",
121-
"# attack technique against the objective target with each adversarial model.\n",
122-
"adversarial_model = OpenAIChatTarget()\n",
123-
"\n",
124-
"benchmark_scenario = AdversarialBenchmark(adversarial_models=[adversarial_model])\n",
125-
"\n",
126-
"await benchmark_scenario.initialize_async( # type: ignore\n",
127-
" objective_target=OpenAIChatTarget(), max_concurrency=2\n",
70+
"await initialize_pyrit_async( # type: ignore\n",
71+
" memory_db_type=IN_MEMORY,\n",
72+
" initializers=[TargetInitializer(), ScorerInitializer(), LoadDefaultDatasets()],\n",
12873
")\n",
12974
"\n",
130-
"baseline_result = await benchmark_scenario.run_async() # type: ignore\n",
131-
"\n",
132-
"# Resume handle: re-run with `AdversarialBenchmark(..., scenario_result_id=<this id>)` to pick\n",
133-
"# up where this run left off (constructor args must match the original run).\n",
134-
"print(f\"Scenario result id: {baseline_result.id}\")\n",
75+
"objective_target = OpenAIChatTarget()"
76+
]
77+
},
78+
{
79+
"cell_type": "code",
80+
"execution_count": null,
81+
"id": "4",
82+
"metadata": {},
83+
"outputs": [],
84+
"source": [
85+
"dataset_config = DatasetConfiguration(dataset_names=[\"harmbench\"], max_dataset_size=4)\n",
13586
"\n",
87+
"scenario = AdversarialBenchmark()\n",
88+
"scenario.set_params_from_args(\n",
89+
" args={\"adversarial_targets\": [\"adversarial_chat_singleturn\", \"adversarial_chat_multiturn\"]}\n",
90+
")\n",
91+
"await scenario.initialize_async( # type: ignore\n",
92+
" objective_target=objective_target,\n",
93+
" dataset_config=dataset_config,\n",
94+
")\n",
13695
"\n",
137-
"await output_scenario_async(baseline_result)"
96+
"scenario_result = await scenario.run_async() # type: ignore"
97+
]
98+
},
99+
{
100+
"cell_type": "code",
101+
"execution_count": null,
102+
"id": "5",
103+
"metadata": {},
104+
"outputs": [],
105+
"source": [
106+
"await output_scenario_async(scenario_result)"
138107
]
139108
}
140109
],
141110
"metadata": {
142111
"jupytext": {
143112
"main_language": "python"
144-
},
145-
"language_info": {
146-
"codemirror_mode": {
147-
"name": "ipython",
148-
"version": 3
149-
},
150-
"file_extension": ".py",
151-
"mimetype": "text/x-python",
152-
"name": "python",
153-
"nbconvert_exporter": "python",
154-
"pygments_lexer": "ipython3",
155-
"version": "3.13.13"
156113
}
157114
},
158115
"nbformat": 4,

doc/scanner/benchmark.py

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,69 @@
1111
# %% [markdown]
1212
# # Benchmark Scenarios
1313
#
14-
# Benchmark scenarios are a subset of scenarios that compare the effectiveness of attacks across an axis that varies within the scenario itself. The axis can be many things; currently, the only benchmark variant is the adversarial benchmark, whose axis of change is the adversarial model used in attacks.
14+
# Benchmark scenarios compare attack effectiveness across an axis that varies within the scenario
15+
# itself. Currently the only benchmark variant is the adversarial benchmark, whose axis of change is
16+
# the **adversarial chat helper model** used in attacks. For full configuration options see
17+
# `pyrit_scan --help` and the [Scenarios Programming Guide](../code/scenarios/0_scenarios.ipynb).
1518

1619
# %% [markdown]
1720
# ## Adversarial Benchmark
18-
# The adversarial benchmarking scenario (`AdversarialBenchmark`) compares the effectiveness of different adversarial models in successfully executing attacks against a target model.
21+
#
22+
# `AdversarialBenchmark` holds the objective target and dataset constant and varies the adversarial
23+
# chat model used to drive multi-turn attacks. Useful for evaluating which adversarial helper
24+
# models produce stronger or weaker attack success rates against the same target.
25+
#
26+
# Adversarial targets are user-provided via the `adversarial_targets` scenario parameter. Each name
27+
# must already be registered in `TargetRegistry` — typically by `TargetInitializer` from the
28+
# `ADVERSARIAL_CHAT_*` env vars (see `.env_example`). Use `pyrit_scan --list-targets` to see every
29+
# target currently registered.
30+
#
31+
# ```bash
32+
# pyrit_scan benchmark.adversarial \
33+
# --initializers target load_default_datasets \
34+
# --target openai_chat \
35+
# --adversarial-targets adversarial_chat_singleturn adversarial_chat_multiturn \
36+
# --max-dataset-size 4
37+
# ```
38+
#
39+
# Pass multiple `--adversarial-targets` values to compare across models in a single run.
40+
#
41+
# **Available strategies:** `light` (default — a quick snapshot using the cheaper techniques),
42+
# `single_turn`, `multi_turn`, plus one member per adversarial-capable source technique
43+
# (e.g. `red_teaming`, `tap`, `crescendo_simulated`). The `light` aggregate excludes `tap` and
44+
# `crescendo_simulated`, which can take hours.
45+
46+
# %% [markdown]
47+
# ## Setup
1948

2049
# %%
2150
from pyrit.output import output_scenario_async
2251
from pyrit.prompt_target import OpenAIChatTarget
52+
from pyrit.scenario import DatasetConfiguration
2353
from pyrit.scenario.scenarios.benchmark import AdversarialBenchmark
2454
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
25-
from pyrit.setup.initializers import LoadDefaultDatasets
55+
from pyrit.setup.initializers import LoadDefaultDatasets, ScorerInitializer, TargetInitializer
2656

27-
await initialize_pyrit_async(memory_db_type=IN_MEMORY, initializers=[LoadDefaultDatasets()]) # type: ignore
57+
await initialize_pyrit_async( # type: ignore
58+
memory_db_type=IN_MEMORY,
59+
initializers=[TargetInitializer(), ScorerInitializer(), LoadDefaultDatasets()],
60+
)
2861

29-
# Pass any number of adversarial PromptTarget instances (with chat-target
30-
# capabilities — multi-turn and editable history) as a list; AdversarialBenchmark
31-
# infers a label for each from its identifier and runs every benchmark-friendly
32-
# attack technique against the objective target with each adversarial model.
33-
adversarial_model = OpenAIChatTarget()
62+
objective_target = OpenAIChatTarget()
3463

35-
benchmark_scenario = AdversarialBenchmark(adversarial_models=[adversarial_model])
64+
# %%
65+
dataset_config = DatasetConfiguration(dataset_names=["harmbench"], max_dataset_size=4)
3666

37-
await benchmark_scenario.initialize_async( # type: ignore
38-
objective_target=OpenAIChatTarget(), max_concurrency=2
67+
scenario = AdversarialBenchmark()
68+
scenario.set_params_from_args(
69+
args={"adversarial_targets": ["adversarial_chat_singleturn", "adversarial_chat_multiturn"]}
70+
)
71+
await scenario.initialize_async( # type: ignore
72+
objective_target=objective_target,
73+
dataset_config=dataset_config,
3974
)
4075

41-
baseline_result = await benchmark_scenario.run_async() # type: ignore
42-
43-
# Resume handle: re-run with `AdversarialBenchmark(..., scenario_result_id=<this id>)` to pick
44-
# up where this run left off (constructor args must match the original run).
45-
print(f"Scenario result id: {baseline_result.id}")
76+
scenario_result = await scenario.run_async() # type: ignore
4677

47-
await output_scenario_async(baseline_result)
78+
# %%
79+
await output_scenario_async(scenario_result)

pyrit/analytics/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
"""Analytics module for PyRIT conversation and result analysis."""
55

66
from pyrit.analytics.conversation_analytics import ConversationAnalytics
7-
from pyrit.analytics.result_analysis import AttackStats, analyze_results
7+
from pyrit.analytics.result_analysis import (
8+
AttackStats,
9+
analyze_results,
10+
get_cached_results_for_technique,
11+
)
812
from pyrit.analytics.text_matching import (
913
ApproximateTextMatching,
1014
ExactTextMatching,
@@ -17,5 +21,6 @@
1721
"AttackStats",
1822
"ConversationAnalytics",
1923
"ExactTextMatching",
24+
"get_cached_results_for_technique",
2025
"TextMatching",
2126
]

0 commit comments

Comments
 (0)