Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
0acd48a
new .env_example with adversrial target models.
May 20, 2026
6881d19
.env_example formatting
May 20, 2026
1f1a07b
More .env_example formatting
May 20, 2026
81d529d
Reorganized unit test directory
May 20, 2026
6290bbe
Renaming unit tests for consistency
May 20, 2026
42887b9
FIX: TargetInitializer propagates config.tags to registry entries
May 21, 2026
b7af0de
FEAT: Add TargetRegistry.get_by_tag_query for TagQuery-based lookup
May 21, 2026
190ee4a
FEAT: Register ADVERSARIAL_CHAT singleturn/multiturn/reasoning variants
May 21, 2026
c1476bf
FEAT: Add BenchmarkInitializer for adversarial-target fan-out
May 21, 2026
9ec8234
Merge remote-tracking branch 'origin/main' into adversarial_benchmark…
May 21, 2026
d652a56
REFACTOR: Collapse AdversarialBenchmark to RapidResponse pattern
May 21, 2026
b54c47f
FEAT: Add skip_cached cross-run caching to AdversarialBenchmark
May 21, 2026
8b5bcc9
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 21, 2026
091a501
FEAT: Widen AdversarialBenchmark objective_scorer to Scorer (stage 1)
May 21, 2026
40ff08b
TEST: e2e per-scenario initializer override for benchmark.adversarial
May 21, 2026
4323383
DOCS: Rewrite benchmark scanner notebook for registry-driven flow
May 21, 2026
7e450b1
STYLE: Move TYPE_CHECKING-only imports in target_registry
May 21, 2026
34b86f0
FIX: TargetInitializer initialized adversarial chats as OpenAIChatTar…
May 21, 2026
2f2c209
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 21, 2026
a3e1ffa
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 22, 2026
37a22c7
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 26, 2026
c7e7b93
Removed _cached_strategy_class and scorer type validation.
May 26, 2026
5840b25
REFACTOR: Remove BenchmarkInitializer and source adversarial targets …
May 26, 2026
4697a1a
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 26, 2026
53bd312
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 26, 2026
2ec2a49
DOCS: Trim adversarial benchmark scanner doc to basic-usage shape
May 26, 2026
cfdf5bf
DOCS: Add executed benchmark scanner notebook outputs
May 26, 2026
7a664c5
Add mcp Python SDK dependency
May 26, 2026
d68d75c
Merge branch 'main' into MCP
ValbuenaVC May 26, 2026
c7d65d3
Add tools/ package with tool_loop decorator and CallableToolBackend
May 26, 2026
39752ac
Addition of pyrit/tools package and unit tests. Introduces base model…
May 26, 2026
e8ab8ff
Addition of MCP components including the MCP client and tool backend.
May 26, 2026
c61bcfe
Add supports_tool_use capability + ToolEventPolicy and wire tool_loop…
May 27, 2026
f566914
Merge branch 'main' into MCP
ValbuenaVC May 27, 2026
3a3c26b
Drop C5 (Chat target tool calling): defer to follow-up, redesign arou…
May 28, 2026
eb84ed5
Migrate OpenAIResponseTarget onto @tool_loop and LocalToolBackend
May 28, 2026
e57ff8f
DOCS: Migrate reST roles, drop dead spec-selector and stale scorer-fl…
May 28, 2026
0e622c7
Add integration tests for RedTeamingAttack with real MCP tool dispatch
May 28, 2026
fe0f2d3
REFACTOR: Delete unused `TargetInitializerTags.ADVERSARIAL`
May 28, 2026
610ed05
DOCS: Compare singleturn vs multiturn adversarial targets in benchmar…
May 28, 2026
4c10a4d
Merge branch 'main' into adversarial_benchmark_refactor
May 28, 2026
8164e7c
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 28, 2026
f43a303
FEAT: Add ObjectiveTargetEvaluationIdentifier + analytics cache lookup
May 28, 2026
cf62a83
Merge branch 'vvalbuena-microsoft/mcp-target-decorator-design' into a…
May 28, 2026
e7ee9c0
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 28, 2026
94f1f9f
FEAT: Wire adversarial benchmark cache to analytics primitives
May 28, 2026
6827aa9
TEST: Add real-memory coverage for adversarial cache wiring
May 28, 2026
bbe5121
Merge remote-tracking branch 'origin/main' into vvalbuena-microsoft/f…
May 28, 2026
0af14e7
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 29, 2026
ccebf2f
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 29, 2026
31ed2fb
Removing tool components from this branch. Tool-calling is a feature …
May 29, 2026
5dbd1e4
Remove remaining tool-calling leakage from PR #1811
May 29, 2026
80e1d4a
Merge resolution.
May 29, 2026
1f5d061
Fix merge conflicts with main (PR #1785/#1784): adopt factory registr…
May 29, 2026
eae30ed
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 29, 2026
db03b0c
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC May 30, 2026
c39d7cc
Merge branch 'main' into adversarial_benchmark_refactor
Jun 1, 2026
6fc708a
FEAT: inject cached attack results into AdversarialBenchmark Scenario…
Jun 1, 2026
7672e1e
feat: add dataset-level scoping to _collect_cached_completion_pairs
Jun 1, 2026
68cff20
Merge remote-tracking branch 'origin/main' into adversarial_benchmark…
Jun 1, 2026
b0dc4b0
fix: use uuid4 for conversation_id in _persist_attack_result to preve…
Jun 1, 2026
617572b
Merge branch 'main' into adversarial_benchmark_refactor
ValbuenaVC Jun 1, 2026
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
14 changes: 14 additions & 0 deletions .env_example
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,24 @@ AZURE_OPENAI_GPT4O_UNSAFE_CHAT_MODEL2="deployment-name"
AZURE_OPENAI_GPT4O_UNSAFE_CHAT_UNDERLYING_MODEL2=""

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

ADVERSARIAL_CHAT_SINGLETURN_ENDPOINT="https://xxxxxx.westus3.inference.ml.azure.com/score"
ADVERSARIAL_CHAT_SINGLETURN_KEY="xxxxx"
ADVERSARIAL_CHAT_SINGLETURN_MODEL="deployment-name"

ADVERSARIAL_CHAT_MULTITURN_ENDPOINT="https://xxxxxx.westus3.inference.ml.azure.com/score"
ADVERSARIAL_CHAT_MULTITURN_KEY="xxxxx"
ADVERSARIAL_CHAT_MULTITURN_MODEL="deployment-name"

ADVERSARIAL_CHAT_REASONING_ENDPOINT="https://xxxxxx.westus3.inference.ml.azure.com/score"
Comment thread
ValbuenaVC marked this conversation as resolved.
ADVERSARIAL_CHAT_REASONING_KEY="xxxxx"
ADVERSARIAL_CHAT_REASONING_MODEL="deployment-name"


# Objective Scorer chat target (used in scorers in scenarios)
OBJECTIVE_SCORER_CHAT_ENDPOINT="https://xxxxx.openai.azure.com/openai/v1"
OBJECTIVE_SCORER_CHAT_KEY="xxxxx"
Expand Down
189 changes: 73 additions & 116 deletions doc/scanner/benchmark.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"source": [
"# Benchmark Scenarios\n",
"\n",
"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."
"Benchmark scenarios compare attack effectiveness across an axis that varies within the scenario\n",
"itself. Currently the only benchmark variant is the adversarial benchmark, whose axis of change is\n",
"the **adversarial chat helper model** used in attacks. For full configuration options see\n",
"`pyrit_scan --help` and the [Scenarios Programming Guide](../code/scenarios/0_scenarios.ipynb)."
]
},
{
Expand All @@ -16,143 +19,97 @@
"metadata": {},
"source": [
"## Adversarial Benchmark\n",
"The adversarial benchmarking scenario (`AdversarialBenchmark`) compares the effectiveness of different adversarial models in successfully executing attacks against a target model."
"\n",
"`AdversarialBenchmark` holds the objective target and dataset constant and varies the adversarial\n",
Comment thread
ValbuenaVC marked this conversation as resolved.
"chat model used to drive multi-turn attacks. Useful for evaluating which adversarial helper\n",
"models produce stronger or weaker attack success rates against the same target.\n",
"\n",
"Adversarial targets are user-provided via the `adversarial_targets` scenario parameter. Each name\n",
"must already be registered in `TargetRegistry` — typically by `TargetInitializer` from the\n",
"`ADVERSARIAL_CHAT_*` env vars (see `.env_example`). Use `pyrit_scan --list-targets` to see every\n",
"target currently registered.\n",
"\n",
"```bash\n",
"pyrit_scan benchmark.adversarial \\\n",
" --initializers target load_default_datasets \\\n",
" --target openai_chat \\\n",
" --adversarial-targets adversarial_chat_singleturn adversarial_chat_multiturn \\\n",
Comment thread
ValbuenaVC marked this conversation as resolved.
" --max-dataset-size 4\n",
"```\n",
"\n",
"Pass multiple `--adversarial-targets` values to compare across models in a single run.\n",
"\n",
"**Available strategies:** `light` (default — a quick snapshot using the cheaper techniques),\n",
"`single_turn`, `multi_turn`, plus one member per adversarial-capable source technique\n",
"(e.g. `red_teaming`, `tap`, `crescendo_simulated`). The `light` aggregate excludes `tap` and\n",
"`crescendo_simulated`, which can take hours."
]
},
{
"cell_type": "markdown",
"id": "2",
"metadata": {},
"source": [
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2",
"id": "3",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Found default environment files: ['./.pyrit/.env', './.pyrit/.env.local']\n",
"Loaded environment file: ./.pyrit/.env\n",
"Loaded environment file: ./.pyrit/.env.local\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "8316db039ba1408499df0a2de6c8d6f6",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Executing AdversarialBenchmark: 0%| | 0/3 [00:00<?, ?attack/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Scenario result id: e01b35d2-c7f8-49bd-aafd-5d44ef7235f4\n",
"\n",
"\u001b[36m====================================================================================================\u001b[0m\n",
"\u001b[1m\u001b[36m 📊 SCENARIO RESULTS: AdversarialBenchmark \u001b[0m\n",
"\u001b[36m====================================================================================================\u001b[0m\n",
"\n",
"\u001b[1m\u001b[36m▼ Scenario Information\u001b[0m\n",
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
"\u001b[1m 📋 Scenario Details\u001b[0m\n",
"\u001b[36m • Name: AdversarialBenchmark\u001b[0m\n",
"\u001b[36m • Scenario Version: 1\u001b[0m\n",
"\u001b[36m • PyRIT Version: 0.14.0.dev0\u001b[0m\n",
"\u001b[36m • Description:\u001b[0m\n",
"\u001b[36m Benchmarking scenario that compares the attack success rate (ASR) of several different adversarial models.\u001b[0m\n",
"\n",
"\u001b[1m 🎯 Target Information\u001b[0m\n",
"\u001b[36m • Target Type: OpenAIChatTarget\u001b[0m\n",
"\u001b[36m • Target Model: gpt-4o-japan-nilfilter\u001b[0m\n",
"\u001b[36m • Target Endpoint: https://pyrit-japan-test.openai.azure.com/openai/v1\u001b[0m\n",
"\n",
"\u001b[1m 📊 Scorer Information\u001b[0m\n",
"\u001b[37m ▸ Scorer Identifier\u001b[0m\n",
"\u001b[36m • Scorer Type: TrueFalseInverterScorer\u001b[0m\n",
"\u001b[36m • scorer_type: true_false\u001b[0m\n",
"\u001b[36m • score_aggregator: OR_\u001b[0m\n",
"\u001b[36m └─ Composite of 1 scorer(s):\u001b[0m\n",
"\u001b[36m • Scorer Type: SelfAskRefusalScorer\u001b[0m\n",
"\u001b[36m • scorer_type: true_false\u001b[0m\n",
"\u001b[36m • score_aggregator: OR_\u001b[0m\n",
"\u001b[36m • model_name: gpt-4o-japan-nilfilter\u001b[0m\n",
"\n",
"\u001b[37m ▸ Performance Metrics\u001b[0m\n",
"\u001b[33m Official evaluation has not been run yet for this specific configuration\u001b[0m\n",
"\n",
"\u001b[1m\u001b[36m▼ Overall Statistics\u001b[0m\n",
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
"\u001b[1m 📈 Summary\u001b[0m\n",
"\u001b[32m • Total Strategies: 3\u001b[0m\n",
"\u001b[32m • Total Attack Results: 24\u001b[0m\n",
"\u001b[36m • Overall Success Rate: 25%\u001b[0m\n",
"\u001b[32m • Unique Objectives: 8\u001b[0m\n",
"\n",
"\u001b[1m\u001b[36m▼ Per-Group Breakdown\u001b[0m\n",
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
"\n",
"\u001b[1m 🔸 Group: gpt-4o-japan-nilfilter\u001b[0m\n",
"\u001b[33m • Number of Results: 24\u001b[0m\n",
"\u001b[36m • Success Rate: 25%\u001b[0m\n",
"\n",
"\u001b[36m====================================================================================================\u001b[0m\n",
"\n"
]
}
],
"outputs": [],
"source": [
"from pyrit.output import output_scenario_async\n",
"from pyrit.prompt_target import OpenAIChatTarget\n",
"from pyrit.scenario import DatasetConfiguration\n",
"from pyrit.scenario.scenarios.benchmark import AdversarialBenchmark\n",
"from pyrit.setup import IN_MEMORY, initialize_pyrit_async\n",
"from pyrit.setup.initializers import LoadDefaultDatasets\n",
"from pyrit.setup.initializers import LoadDefaultDatasets, ScorerInitializer, TargetInitializer\n",
"\n",
"await initialize_pyrit_async(memory_db_type=IN_MEMORY, initializers=[LoadDefaultDatasets()]) # type: ignore\n",
"\n",
"# Pass any number of adversarial PromptTarget instances (with chat-target\n",
"# capabilities \u2014 multi-turn and editable history) as a list; AdversarialBenchmark\n",
"# infers a label for each from its identifier and runs every benchmark-friendly\n",
"# attack technique against the objective target with each adversarial model.\n",
"adversarial_model = OpenAIChatTarget()\n",
"\n",
"benchmark_scenario = AdversarialBenchmark(adversarial_models=[adversarial_model])\n",
"\n",
"await benchmark_scenario.initialize_async( # type: ignore\n",
" objective_target=OpenAIChatTarget(), max_concurrency=2\n",
"await initialize_pyrit_async( # type: ignore\n",
" memory_db_type=IN_MEMORY,\n",
" initializers=[TargetInitializer(), ScorerInitializer(), LoadDefaultDatasets()],\n",
")\n",
"\n",
"baseline_result = await benchmark_scenario.run_async() # type: ignore\n",
"\n",
"# Resume handle: re-run with `AdversarialBenchmark(..., scenario_result_id=<this id>)` to pick\n",
"# up where this run left off (constructor args must match the original run).\n",
"print(f\"Scenario result id: {baseline_result.id}\")\n",
"objective_target = OpenAIChatTarget()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4",
"metadata": {},
"outputs": [],
"source": [
"dataset_config = DatasetConfiguration(dataset_names=[\"harmbench\"], max_dataset_size=4)\n",
"\n",
"scenario = AdversarialBenchmark()\n",
"scenario.set_params_from_args(\n",
" args={\"adversarial_targets\": [\"adversarial_chat_singleturn\", \"adversarial_chat_multiturn\"]}\n",
")\n",
"await scenario.initialize_async( # type: ignore\n",
" objective_target=objective_target,\n",
" dataset_config=dataset_config,\n",
")\n",
"\n",
"await output_scenario_async(baseline_result)"
"scenario_result = await scenario.run_async() # type: ignore"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5",
"metadata": {},
"outputs": [],
"source": [
"await output_scenario_async(scenario_result)"
]
}
],
"metadata": {
"jupytext": {
"main_language": "python"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.13"
}
},
"nbformat": 4,
Expand Down
68 changes: 50 additions & 18 deletions doc/scanner/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,69 @@
# %% [markdown]
# # Benchmark Scenarios
#
# 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.
# Benchmark scenarios compare attack effectiveness across an axis that varies within the scenario
# itself. Currently the only benchmark variant is the adversarial benchmark, whose axis of change is
# the **adversarial chat helper model** used in attacks. For full configuration options see
# `pyrit_scan --help` and the [Scenarios Programming Guide](../code/scenarios/0_scenarios.ipynb).

# %% [markdown]
# ## Adversarial Benchmark
# The adversarial benchmarking scenario (`AdversarialBenchmark`) compares the effectiveness of different adversarial models in successfully executing attacks against a target model.
#
# `AdversarialBenchmark` holds the objective target and dataset constant and varies the adversarial
# chat model used to drive multi-turn attacks. Useful for evaluating which adversarial helper
# models produce stronger or weaker attack success rates against the same target.
#
# Adversarial targets are user-provided via the `adversarial_targets` scenario parameter. Each name
# must already be registered in `TargetRegistry` — typically by `TargetInitializer` from the
# `ADVERSARIAL_CHAT_*` env vars (see `.env_example`). Use `pyrit_scan --list-targets` to see every
# target currently registered.
#
# ```bash
# pyrit_scan benchmark.adversarial \
Comment thread
ValbuenaVC marked this conversation as resolved.
# --initializers target load_default_datasets \
# --target openai_chat \
# --adversarial-targets adversarial_chat_singleturn adversarial_chat_multiturn \
# --max-dataset-size 4
# ```
#
# Pass multiple `--adversarial-targets` values to compare across models in a single run.
#
# **Available strategies:** `light` (default — a quick snapshot using the cheaper techniques),
# `single_turn`, `multi_turn`, plus one member per adversarial-capable source technique
# (e.g. `red_teaming`, `tap`, `crescendo_simulated`). The `light` aggregate excludes `tap` and
# `crescendo_simulated`, which can take hours.

# %% [markdown]
# ## Setup

# %%
from pyrit.output import output_scenario_async
from pyrit.prompt_target import OpenAIChatTarget
from pyrit.scenario import DatasetConfiguration
from pyrit.scenario.scenarios.benchmark import AdversarialBenchmark
from pyrit.setup import IN_MEMORY, initialize_pyrit_async
from pyrit.setup.initializers import LoadDefaultDatasets
from pyrit.setup.initializers import LoadDefaultDatasets, ScorerInitializer, TargetInitializer

await initialize_pyrit_async(memory_db_type=IN_MEMORY, initializers=[LoadDefaultDatasets()]) # type: ignore
await initialize_pyrit_async( # type: ignore
memory_db_type=IN_MEMORY,
initializers=[TargetInitializer(), ScorerInitializer(), LoadDefaultDatasets()],
)

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

benchmark_scenario = AdversarialBenchmark(adversarial_models=[adversarial_model])
# %%
dataset_config = DatasetConfiguration(dataset_names=["harmbench"], max_dataset_size=4)

await benchmark_scenario.initialize_async( # type: ignore
objective_target=OpenAIChatTarget(), max_concurrency=2
scenario = AdversarialBenchmark()
scenario.set_params_from_args(
args={"adversarial_targets": ["adversarial_chat_singleturn", "adversarial_chat_multiturn"]}
)
await scenario.initialize_async( # type: ignore
Comment thread
ValbuenaVC marked this conversation as resolved.
objective_target=objective_target,
dataset_config=dataset_config,
)

baseline_result = await benchmark_scenario.run_async() # type: ignore

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

await output_scenario_async(baseline_result)
# %%
await output_scenario_async(scenario_result)
7 changes: 6 additions & 1 deletion pyrit/analytics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"""Analytics module for PyRIT conversation and result analysis."""

from pyrit.analytics.conversation_analytics import ConversationAnalytics
from pyrit.analytics.result_analysis import AttackStats, analyze_results
from pyrit.analytics.result_analysis import (
AttackStats,
analyze_results,
get_cached_results_for_technique,
)
from pyrit.analytics.text_matching import (
ApproximateTextMatching,
ExactTextMatching,
Expand All @@ -17,5 +21,6 @@
"AttackStats",
"ConversationAnalytics",
"ExactTextMatching",
"get_cached_results_for_technique",
"TextMatching",
]
Loading
Loading