Skip to content

Commit b029f02

Browse files
authored
FEAT Breaking: Registry protocol + ScorerRegistry (Azure#1308)
1 parent 0af160c commit b029f02

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3711
-2005
lines changed

doc/_toc.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ chapters:
131131
- file: code/scenarios/0_scenarios
132132
sections:
133133
- file: code/scenarios/1_configuring_scenarios
134+
- file: code/registry/0_registry
135+
sections:
136+
- file: code/registry/1_class_registry
137+
- file: code/registry/2_instance_registry
134138
- file: code/front_end/0_front_end
135139
sections:
136140
- file: code/front_end/1_pyrit_scan

doc/api.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,9 +632,9 @@ API Reference
632632
:nosignatures:
633633
:toctree: _autosummary/
634634

635-
Foundry
636635
FoundryScenario
637636
FoundryStrategy
637+
RedTeamAgent
638638

639639
:py:mod:`pyrit.scenario.garak`
640640
==============================

doc/code/front_end/1_pyrit_scan.ipynb

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,33 @@
164164
" Default Datasets (1, max 4 per dataset):\n",
165165
" airt_malware\n",
166166
"\u001b[1m\u001b[36m\n",
167-
" foundry\u001b[0m\n",
168-
" Class: FoundryScenario\n",
167+
" airt.scam\u001b[0m\n",
168+
" Class: Scam\n",
169169
" Description:\n",
170-
" Deprecated alias for Foundry. This class is deprecated and will be\n",
171-
" removed in version 0.13.0. Use `Foundry` instead.\n",
170+
" Scam scenario evaluates an endpoint's ability to generate scam-related\n",
171+
" materials (e.g., phishing emails, fraudulent messages) with primarily\n",
172+
" persuasion-oriented techniques.\n",
173+
" Aggregate Strategies:\n",
174+
" - all, single_turn, multi_turn\n",
175+
" Available Strategies (3):\n",
176+
" context_compliance, role_play, persuasive_rta\n",
177+
" Default Strategy: all\n",
178+
" Default Datasets (1, max 4 per dataset):\n",
179+
" airt_scams\n",
180+
"\u001b[1m\u001b[36m\n",
181+
" foundry.red_team_agent\u001b[0m\n",
182+
" Class: RedTeamAgent\n",
183+
" Description:\n",
184+
" RedTeamAgent is a preconfigured scenario that automatically generates\n",
185+
" multiple AtomicAttack instances based on the specified attack\n",
186+
" strategies. It supports both single-turn attacks (with various\n",
187+
" converters) and multi-turn attacks (Crescendo, RedTeaming), making it\n",
188+
" easy to quickly test a target against multiple attack vectors. The\n",
189+
" scenario can expand difficulty levels (EASY, MODERATE, DIFFICULT) into\n",
190+
" their constituent attack strategies, or you can specify individual\n",
191+
" strategies directly. This scenario is designed for use with the Foundry\n",
192+
" AI Red Teaming Agent library, providing a consistent PyRIT contract for\n",
193+
" their integration.\n",
172194
" Aggregate Strategies:\n",
173195
" - all, easy, moderate, difficult\n",
174196
" Available Strategies (25):\n",
@@ -205,7 +227,7 @@
205227
"\n",
206228
"================================================================================\n",
207229
"\n",
208-
"Total scenarios: 4\n"
230+
"Total scenarios: 5\n"
209231
]
210232
}
211233
],
@@ -320,7 +342,7 @@
320342
"Or concretely:\n",
321343
"\n",
322344
"```shell\n",
323-
"!pyrit_scan foundry --initializers simple openai_objective_target --scenario-strategies base64\n",
345+
"!pyrit_scan foundry.red_team_agent --initializers simple openai_objective_target --scenario-strategies base64\n",
324346
"```\n",
325347
"\n",
326348
"Example with a basic configuration that runs the Foundry scenario against the objective target defined in `openai_objective_target` (which just is an OpenAIChatTarget with `DEFAULT_OPENAI_FRONTEND_ENDPOINT` and `DEFAULT_OPENAI_FRONTEND_KEY`)."
@@ -345,14 +367,27 @@
345367
"Loaded environment file: C:\\Users\\rlundeen\\.pyrit\\.env\n",
346368
"Loaded environment file: C:\\Users\\rlundeen\\.pyrit\\.env.local\n",
347369
"\n",
348-
"Running scenario: foundry\n",
370+
"Running scenario: foundry.red_team_agent\n",
371+
"\n",
372+
"\u001b[36m====================================================================================================\u001b[0m\n",
349373
"\n",
350-
"Error: SeedGroup at index 0 is missing an objective. Use seed_group.set_objective(value) to set one.\n"
374+
"Error: 'charmap' codec can't encode character '\\U0001f4ca' in position 43: character maps to <undefined>\n"
375+
]
376+
},
377+
{
378+
"name": "stderr",
379+
"output_type": "stream",
380+
"text": [
381+
"\n",
382+
"Executing RedTeamAgent: 0%| | 0/2 [00:00<?, ?attack/s]\n",
383+
"Executing RedTeamAgent: 50%|##### | 1/2 [00:07<00:07, 7.38s/attack]\n",
384+
"Executing RedTeamAgent: 100%|##########| 2/2 [00:35<00:00, 19.33s/attack]\n",
385+
"Executing RedTeamAgent: 100%|##########| 2/2 [00:35<00:00, 17.54s/attack]\n"
351386
]
352387
}
353388
],
354389
"source": [
355-
"!pyrit_scan foundry --initializers openai_objective_target --strategies base64"
390+
"!pyrit_scan foundry.red_team_agent --initializers openai_objective_target --strategies base64"
356391
]
357392
},
358393
{
@@ -363,17 +398,17 @@
363398
"Or with all options and multiple initializers and multiple strategies:\n",
364399
"\n",
365400
"```shell\n",
366-
"pyrit_scan foundry --database InMemory --initializers simple objective_target objective_list --scenario-strategies easy crescendo\n",
401+
"pyrit_scan foundry.red_team_agent --database InMemory --initializers simple objective_target objective_list --scenario-strategies easy crescendo\n",
367402
"```\n",
368403
"\n",
369404
"You can also override scenario execution parameters:\n",
370405
"\n",
371406
"```shell\n",
372407
"# Override concurrency and retry settings\n",
373-
"pyrit_scan foundry --initializers simple objective_target --max-concurrency 10 --max-retries 3\n",
408+
"pyrit_scan foundry.red_team_agent --initializers simple objective_target --max-concurrency 10 --max-retries 3\n",
374409
"\n",
375410
"# Add custom memory labels for tracking (must be valid JSON)\n",
376-
"pyrit_scan foundry --initializers simple objective_target --memory-labels '{\"experiment\": \"test1\", \"version\": \"v2\", \"researcher\": \"alice\"}'\n",
411+
"pyrit_scan foundry.red_team_agent --initializers simple objective_target --memory-labels '{\"experiment\": \"test1\", \"version\": \"v2\", \"researcher\": \"alice\"}'\n",
377412
"```\n",
378413
"\n",
379414
"Available CLI parameter overrides:\n",
@@ -384,7 +419,7 @@
384419
"You can also use custom initialization scripts by passing file paths. It is relative to your current working directory, but to avoid confusion, full paths are always better:\n",
385420
"\n",
386421
"```shell\n",
387-
"pyrit_scan encoding_scenario --initialization-scripts ./my_custom_config.py\n",
422+
"pyrit_scan garak.encoding --initialization-scripts ./my_custom_config.py\n",
388423
"```"
389424
]
390425
},
@@ -418,7 +453,7 @@
418453
{
419454
"data": {
420455
"text/plain": [
421-
"<__main__.MyCustomScenario at 0x19e7c2a70e0>"
456+
"<__main__.MyCustomScenario at 0x13c63b4c2f0>"
422457
]
423458
},
424459
"execution_count": null,
@@ -428,9 +463,11 @@
428463
],
429464
"source": [
430465
"# my_custom_scenarios.py\n",
466+
"\n",
431467
"from pyrit.common import apply_defaults\n",
432-
"from pyrit.scenario import DatasetConfiguration, Scenario\n",
433-
"from pyrit.scenario.core.scenario_strategy import ScenarioStrategy\n",
468+
"from pyrit.prompt_target.openai.openai_chat_target import OpenAIChatTarget\n",
469+
"from pyrit.scenario import DatasetConfiguration, Scenario, ScenarioStrategy\n",
470+
"from pyrit.score import SelfAskRefusalScorer, TrueFalseInverterScorer\n",
434471
"from pyrit.setup import initialize_pyrit_async\n",
435472
"\n",
436473
"\n",
@@ -464,6 +501,7 @@
464501
" super().__init__(\n",
465502
" name=\"My Custom Scenario\",\n",
466503
" version=1,\n",
504+
" objective_scorer=TrueFalseInverterScorer(scorer=SelfAskRefusalScorer(chat_target=OpenAIChatTarget())),\n",
467505
" strategy_class=MyCustomStrategy,\n",
468506
" scenario_result_id=scenario_result_id,\n",
469507
" )\n",

doc/code/front_end/1_pyrit_scan.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,29 +78,29 @@
7878
# Or concretely:
7979
#
8080
# ```shell
81-
# !pyrit_scan foundry --initializers simple openai_objective_target --scenario-strategies base64
81+
# !pyrit_scan foundry.red_team_agent --initializers simple openai_objective_target --scenario-strategies base64
8282
# ```
8383
#
8484
# Example with a basic configuration that runs the Foundry scenario against the objective target defined in `openai_objective_target` (which just is an OpenAIChatTarget with `DEFAULT_OPENAI_FRONTEND_ENDPOINT` and `DEFAULT_OPENAI_FRONTEND_KEY`).
8585

8686
# %%
87-
# !pyrit_scan foundry --initializers openai_objective_target --strategies base64
87+
# !pyrit_scan foundry.red_team_agent --initializers openai_objective_target --strategies base64
8888

8989
# %% [markdown]
9090
# Or with all options and multiple initializers and multiple strategies:
9191
#
9292
# ```shell
93-
# pyrit_scan foundry --database InMemory --initializers simple objective_target objective_list --scenario-strategies easy crescendo
93+
# pyrit_scan foundry.red_team_agent --database InMemory --initializers simple objective_target objective_list --scenario-strategies easy crescendo
9494
# ```
9595
#
9696
# You can also override scenario execution parameters:
9797
#
9898
# ```shell
9999
# # Override concurrency and retry settings
100-
# pyrit_scan foundry --initializers simple objective_target --max-concurrency 10 --max-retries 3
100+
# pyrit_scan foundry.red_team_agent --initializers simple objective_target --max-concurrency 10 --max-retries 3
101101
#
102102
# # Add custom memory labels for tracking (must be valid JSON)
103-
# pyrit_scan foundry --initializers simple objective_target --memory-labels '{"experiment": "test1", "version": "v2", "researcher": "alice"}'
103+
# pyrit_scan foundry.red_team_agent --initializers simple objective_target --memory-labels '{"experiment": "test1", "version": "v2", "researcher": "alice"}'
104104
# ```
105105
#
106106
# Available CLI parameter overrides:
@@ -111,7 +111,7 @@
111111
# You can also use custom initialization scripts by passing file paths. It is relative to your current working directory, but to avoid confusion, full paths are always better:
112112
#
113113
# ```shell
114-
# pyrit_scan encoding_scenario --initialization-scripts ./my_custom_config.py
114+
# pyrit_scan garak.encoding --initialization-scripts ./my_custom_config.py
115115
# ```
116116

117117
# %% [markdown]
@@ -120,12 +120,13 @@
120120
# You can define your own scenarios in initialization scripts. The CLI will automatically discover any `Scenario` subclasses and make them available:
121121
#
122122

123-
from pyrit.common import apply_defaults
124-
from pyrit.scenario import DatasetConfiguration, Scenario
125-
from pyrit.scenario.core.scenario_strategy import ScenarioStrategy
126-
127123
# %%
128124
# my_custom_scenarios.py
125+
126+
from pyrit.common import apply_defaults
127+
from pyrit.prompt_target.openai.openai_chat_target import OpenAIChatTarget
128+
from pyrit.scenario import DatasetConfiguration, Scenario, ScenarioStrategy
129+
from pyrit.score import SelfAskRefusalScorer, TrueFalseInverterScorer
129130
from pyrit.setup import initialize_pyrit_async
130131

131132

@@ -159,6 +160,7 @@ def __init__(self, *, scenario_result_id=None, **kwargs):
159160
super().__init__(
160161
name="My Custom Scenario",
161162
version=1,
163+
objective_scorer=TrueFalseInverterScorer(scorer=SelfAskRefusalScorer(chat_target=OpenAIChatTarget())),
162164
strategy_class=MyCustomStrategy,
163165
scenario_result_id=scenario_result_id,
164166
)

doc/code/front_end/2_pyrit_shell.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,32 +48,32 @@ The `run` command executes scenarios with the same options as `pyrit_scan`:
4848
### Basic Usage
4949

5050
```bash
51-
pyrit> run foundry_scenario --initializers openai_objective_target load_default_datasets
51+
pyrit> run foundry.red_team_agent --initializers openai_objective_target load_default_datasets
5252
```
5353

5454
### With Strategies
5555

5656
```bash
57-
pyrit> run garak.encoding_scenario --initializers openai_objective_target load_default_datasets --strategies base64 rot13
57+
pyrit> run garak.encoding --initializers openai_objective_target load_default_datasets --strategies base64 rot13
5858

59-
pyrit> run foundry_scenario --initializers openai_objective_target load_default_datasets -s jailbreak crescendo
59+
pyrit> run foundry.red_team_agent --initializers openai_objective_target load_default_datasets -s jailbreak crescendo
6060
```
6161

6262
### With Runtime Parameters
6363

6464
```bash
6565
# Set concurrency and retries
66-
pyrit> run foundry_scenario --initializers openai_objective_target load_default_datasets --max-concurrency 10 --max-retries 3
66+
pyrit> run foundry.red_team_agent --initializers openai_objective_target load_default_datasets --max-concurrency 10 --max-retries 3
6767

6868
# Add memory labels for tracking
69-
pyrit> run garak.encoding_scenario --initializers openai_objective_target load_default_datasets --memory-labels '{"experiment":"test1","version":"v2"}'
69+
pyrit> run garak.encoding --initializers openai_objective_target load_default_datasets --memory-labels '{"experiment":"test1","version":"v2"}'
7070
```
7171

7272
### Override Defaults Per-Run
7373

7474
```bash
7575
# Override database and log level for this run only
76-
pyrit> run garak.encoding_scenario --initializers openai_objective_target load_default_datasets --database InMemory --log-level DEBUG
76+
pyrit> run garak.encoding --initializers openai_objective_target load_default_datasets --database InMemory --log-level DEBUG
7777
```
7878

7979
### Run Command Options
@@ -114,9 +114,9 @@ pyrit> scenario-history
114114
115115
Scenario Run History:
116116
================================================================================
117-
1) foundry_scenario --initializers openai_objective_target load_default_datasets --strategies base64
118-
2) garak.encoding_scenario --initializers openai_objective_target load_default_datasets --strategies rot13
119-
3) foundry_scenario --initializers openai_objective_target load_default_datasets -s jailbreak
117+
1) foundry.red_team_agent --initializers openai_objective_target load_default_datasets --strategies base64
118+
2) garak.encoding --initializers openai_objective_target load_default_datasets --strategies rot13
119+
3) foundry.red_team_agent --initializers openai_objective_target load_default_datasets -s jailbreak
120120
================================================================================
121121
122122
Total runs: 3
@@ -134,9 +134,9 @@ pyrit_shell --database InMemory --initializers openai_objective_target load_defa
134134

135135
# Quick exploration
136136
pyrit> list-scenarios
137-
pyrit> run garak.encoding_scenario --strategies base64
138-
pyrit> run garak.encoding_scenario --strategies rot13
139-
pyrit> run garak.encoding_scenario --strategies morse_code
137+
pyrit> run garak.encoding --strategies base64
138+
pyrit> run garak.encoding --strategies rot13
139+
pyrit> run garak.encoding --strategies morse_code
140140

141141
# Review and compare
142142
pyrit> scenario-history
@@ -161,7 +161,7 @@ pyrit> print-scenario 2
161161

162162
2. **Use short strategy aliases** with `-s`:
163163
```bash
164-
pyrit> run foundry_scenario --initializers openai_objective_target load_default_datasets -s base64 rot13
164+
pyrit> run foundry.red_team_agent --initializers openai_objective_target load_default_datasets -s base64 rot13
165165
```
166166

167167
3. **Review history regularly** to track what you've tested:

doc/code/registry/0_registry.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Registry
2+
3+
Registries in PyRIT provide a centralized way to discover, manage, and access components. They support lazy loading, singleton access, and metadata introspection.
4+
5+
## Why Registries?
6+
7+
- **Discovery**: Automatically find available components (scenarios, scorers, etc.)
8+
- **Consistency**: Access components through a uniform API
9+
- **Metadata**: Inspect what's available without instantiating everything
10+
- **Extensibility**: Register custom components alongside built-in ones
11+
12+
## Two Types of Registries
13+
14+
PyRIT has two registry patterns for different use cases:
15+
16+
| Type | Stores | Use Case |
17+
|------|--------|----------|
18+
| **Class Registry** | Classes (Type[T]) | Components instantiated with user-provided parameters |
19+
| **Instance Registry** | Pre-configured instances | Components requiring complex setup before use |
20+
21+
## Common API (RegistryProtocol)
22+
23+
Both registry types implement `RegistryProtocol`, sharing a consistent interface:
24+
25+
| Method | Description |
26+
|--------|-------------|
27+
| `get_registry_singleton()` | Get the singleton registry instance |
28+
| `get_names()` | List all registered names |
29+
| `list_metadata()` | Get descriptive metadata for all items |
30+
| `reset_instance()` | Reset the singleton (useful for testing) |
31+
32+
This protocol enables writing code that works with any registry type:
33+
34+
```python
35+
from pyrit.registry import RegistryProtocol
36+
37+
def show_registry_contents(registry: RegistryProtocol) -> None:
38+
for name in registry.get_names():
39+
print(name)
40+
```
41+
42+
43+
## Key Difference with Class and Instance Registries
44+
45+
| Aspect | Class Registry | Instance Registry |
46+
|--------|----------------|-------------------|
47+
| Stores | Classes (Type[T]) | Instances (T) |
48+
| Registration | Automatic discovery | Explicit via `register_instance()` |
49+
| Returns | Class to instantiate | Ready-to-use instance |
50+
| Instantiation | Caller provides parameters | Pre-configured by initializer |
51+
| When to use | Self-contained components with deferred configuration | Components requiring constructor parameters or compositional setup |
52+
53+
## See Also
54+
55+
- [Class Registries](1_class_registry.ipynb) - ScenarioRegistry, InitializerRegistry
56+
- [Instance Registries](2_instance_registry.ipynb) - ScorerRegistry

0 commit comments

Comments
 (0)