Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 6 additions & 12 deletions doc/code/scenarios/0_scenarios.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"1. **Strategy Enum**: Create a `ScenarioStrategy` enum that defines the available strategies for your scenario.\n",
" - Each enum member is defined as `(value, tags)` where value is a string and tags is a set of strings\n",
" - Include an `ALL` aggregate strategy that expands to all available strategies\n",
" - Optionally implement `supports_composition()` and `validate_composition()` for strategy composition rules\n",
" - Optionally override `_prepare_strategies()` for custom composition logic (see `FoundryComposite`)\n",
"\n",
"2. **Scenario Class**: Extend `Scenario` and implement these abstract methods:\n",
" - `get_strategy_class()`: Return your strategy enum class\n",
Expand Down Expand Up @@ -108,7 +108,6 @@
" Scenario,\n",
" ScenarioStrategy,\n",
")\n",
"from pyrit.scenario.core.scenario_strategy import ScenarioCompositeStrategy\n",
"from pyrit.score.true_false.true_false_scorer import TrueFalseScorer\n",
"from pyrit.setup import initialize_pyrit_async\n",
"\n",
Expand Down Expand Up @@ -162,19 +161,14 @@
" Build atomic attacks based on selected strategies.\n",
"\n",
" This method is called by initialize_async() after strategies are prepared.\n",
" Use self._scenario_composites to access the selected strategies.\n",
" Use self._scenario_strategies to access the resolved strategy list.\n",
" \"\"\"\n",
" atomic_attacks = []\n",
"\n",
" # objective_target is guaranteed to be non-None by parent class validation\n",
" assert self._objective_target is not None\n",
"\n",
" # Extract individual strategy values from the composites\n",
" selected_strategies = ScenarioCompositeStrategy.extract_single_strategy_values(\n",
" self._scenario_composites, strategy_type=MyStrategy\n",
" )\n",
"\n",
" for strategy in selected_strategies:\n",
" for strategy in self._scenario_strategies:\n",
" # self._dataset_config is set by the parent class\n",
" seed_groups = self._dataset_config.get_all_seed_groups()\n",
"\n",
Expand All @@ -185,7 +179,7 @@
" )\n",
" atomic_attacks.append(\n",
" AtomicAttack(\n",
" atomic_attack_name=strategy,\n",
" atomic_attack_name=strategy.value,\n",
" attack=attack,\n",
" seed_groups=seed_groups, # type: ignore[arg-type]\n",
" memory_labels=self._memory_labels,\n",
Expand Down Expand Up @@ -411,8 +405,8 @@
"each objective directly to the target without any converters or multi-turn techniques. This is\n",
"controlled by the `include_default_baseline` parameter (default: `True` for most scenarios).\n",
"\n",
"To run *only* the baseline (no attack strategies), pass `scenario_strategies=[]` programmatically.\n",
"This is useful for establishing a refusal rate before applying attacks. See\n",
"To run *only* the baseline (no attack strategies), create a `RedTeamAgent` with\n",
"`include_baseline=True` (the default) and pass `scenario_strategies=None`. See\n",
"[Scenario Parameters](./1_scenario_parameters.ipynb) for a working example."
]
},
Expand Down
18 changes: 6 additions & 12 deletions doc/code/scenarios/0_scenarios.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
# 1. **Strategy Enum**: Create a `ScenarioStrategy` enum that defines the available strategies for your scenario.
# - Each enum member is defined as `(value, tags)` where value is a string and tags is a set of strings
Comment thread
rlundeen2 marked this conversation as resolved.
# - Include an `ALL` aggregate strategy that expands to all available strategies
# - Optionally implement `supports_composition()` and `validate_composition()` for strategy composition rules
# - Optionally override `_prepare_strategies()` for custom composition logic (see `FoundryComposite`)
#
# 2. **Scenario Class**: Extend `Scenario` and implement these abstract methods:
# - `get_strategy_class()`: Return your strategy enum class
Expand Down Expand Up @@ -96,7 +96,6 @@
Scenario,
ScenarioStrategy,
)
from pyrit.scenario.core.scenario_strategy import ScenarioCompositeStrategy
from pyrit.score.true_false.true_false_scorer import TrueFalseScorer
from pyrit.setup import initialize_pyrit_async

Expand Down Expand Up @@ -150,19 +149,14 @@ async def _get_atomic_attacks_async(self) -> list[AtomicAttack]:
Build atomic attacks based on selected strategies.

This method is called by initialize_async() after strategies are prepared.
Use self._scenario_composites to access the selected strategies.
Use self._scenario_strategies to access the resolved strategy list.
"""
atomic_attacks = []

# objective_target is guaranteed to be non-None by parent class validation
assert self._objective_target is not None

# Extract individual strategy values from the composites
selected_strategies = ScenarioCompositeStrategy.extract_single_strategy_values(
self._scenario_composites, strategy_type=MyStrategy
)

for strategy in selected_strategies:
for strategy in self._scenario_strategies:
# self._dataset_config is set by the parent class
seed_groups = self._dataset_config.get_all_seed_groups()

Expand All @@ -173,7 +167,7 @@ async def _get_atomic_attacks_async(self) -> list[AtomicAttack]:
)
atomic_attacks.append(
AtomicAttack(
atomic_attack_name=strategy,
atomic_attack_name=strategy.value,
attack=attack,
seed_groups=seed_groups, # type: ignore[arg-type]
memory_labels=self._memory_labels,
Expand All @@ -199,8 +193,8 @@ async def _get_atomic_attacks_async(self) -> list[AtomicAttack]:
# each objective directly to the target without any converters or multi-turn techniques. This is
# controlled by the `include_default_baseline` parameter (default: `True` for most scenarios).
#
# To run *only* the baseline (no attack strategies), pass `scenario_strategies=[]` programmatically.
# This is useful for establishing a refusal rate before applying attacks. See
# To run *only* the baseline (no attack strategies), create a `RedTeamAgent` with
# `include_baseline=True` (the default) and pass `scenario_strategies=None`. See
# [Scenario Parameters](./1_scenario_parameters.ipynb) for a working example.

# %% [markdown]
Expand Down
17 changes: 9 additions & 8 deletions doc/code/scenarios/1_scenario_parameters.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@
"id": "10",
"metadata": {},
"source": [
"**Composite strategies** — multiple converters applied together in sequence using\n",
"`ScenarioCompositeStrategy`:"
"**Composite strategies** — pair an attack with one or more converters using `FoundryComposite`.\n",
"For example, to run Crescendo with Base64 encoding applied:"
]
},
{
Expand All @@ -171,9 +171,9 @@
"metadata": {},
"outputs": [],
"source": [
"from pyrit.scenario import ScenarioCompositeStrategy\n",
"from pyrit.scenario.scenarios.foundry import FoundryComposite\n",
"\n",
"composite_strategy = [ScenarioCompositeStrategy(strategies=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])]"
"composite_strategy = [FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Base64])]"
]
},
{
Expand All @@ -194,7 +194,7 @@
"scenario_strategies = [\n",
" FoundryStrategy.Base64,\n",
" FoundryStrategy.Binary,\n",
" ScenarioCompositeStrategy(strategies=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap]),\n",
" FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Caesar]),\n",
"]"
]
},
Expand All @@ -205,8 +205,9 @@
"source": [
"## Baseline Execution\n",
"\n",
"Pass an empty `scenario_strategies` list to run a baseline-only scenario. The baseline sends each\n",
"objective directly to the target without any converters or multi-turn strategies. This is useful for:\n",
"The baseline sends each objective directly to the target without any converters or multi-turn\n",
"strategies. It is included automatically when `include_baseline=True` (the default). This is\n",
"useful for:\n",
"\n",
"- **Measuring default defenses** — how does the target respond to unmodified harmful prompts?\n",
"- **Establishing comparison points** — compare baseline refusal rates against attack-enhanced runs\n",
Expand Down Expand Up @@ -304,7 +305,7 @@
"baseline_scenario = RedTeamAgent()\n",
"await baseline_scenario.initialize_async( # type: ignore\n",
" objective_target=objective_target,\n",
" scenario_strategies=[], # Empty list = baseline only\n",
" scenario_strategies=None, # Uses default strategies; baseline is prepended automatically\n",
" dataset_config=dataset_config,\n",
")\n",
"baseline_result = await baseline_scenario.run_async() # type: ignore\n",
Expand Down
17 changes: 9 additions & 8 deletions doc/code/scenarios/1_scenario_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@
aggregate_strategy = [FoundryStrategy.EASY]

# %% [markdown]
# **Composite strategies** — multiple converters applied together in sequence using
# `ScenarioCompositeStrategy`:
# **Composite strategies** — pair an attack with one or more converters using `FoundryComposite`.
# For example, to run Crescendo with Base64 encoding applied:

# %%
from pyrit.scenario import ScenarioCompositeStrategy
from pyrit.scenario.scenarios.foundry import FoundryComposite

composite_strategy = [ScenarioCompositeStrategy(strategies=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])]
composite_strategy = [FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Base64])]

# %% [markdown]
# You can mix all three types in a single list:
Expand All @@ -94,14 +94,15 @@
scenario_strategies = [
FoundryStrategy.Base64,
FoundryStrategy.Binary,
ScenarioCompositeStrategy(strategies=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap]),
FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Caesar]),
]

# %% [markdown]
# ## Baseline Execution
#
# Pass an empty `scenario_strategies` list to run a baseline-only scenario. The baseline sends each
# objective directly to the target without any converters or multi-turn strategies. This is useful for:
# The baseline sends each objective directly to the target without any converters or multi-turn
# strategies. It is included automatically when `include_baseline=True` (the default). This is
# useful for:
#
# - **Measuring default defenses** — how does the target respond to unmodified harmful prompts?
# - **Establishing comparison points** — compare baseline refusal rates against attack-enhanced runs
Expand All @@ -111,7 +112,7 @@
baseline_scenario = RedTeamAgent()
await baseline_scenario.initialize_async( # type: ignore
objective_target=objective_target,
scenario_strategies=[], # Empty list = baseline only
scenario_strategies=None, # Uses default strategies; baseline is prepended automatically
dataset_config=dataset_config,
)
baseline_result = await baseline_scenario.run_async() # type: ignore
Expand Down
12 changes: 6 additions & 6 deletions doc/scanner/foundry.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,13 @@
"source": [
"## Strategy Composition\n",
"\n",
"You can combine multiple converters into a single composite strategy using\n",
"`ScenarioCompositeStrategy`. Each converter in the composite is applied in sequence.\n",
"You can pair a multi-turn attack with one or more converter strategies using `FoundryComposite`.\n",
"Each converter in the composite is applied in sequence before the attack runs.\n",
"\n",
"```python\n",
"from pyrit.scenario import ScenarioCompositeStrategy\n",
"from pyrit.scenario.scenarios.foundry import FoundryComposite\n",
"\n",
"composed = ScenarioCompositeStrategy(strategies=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])\n",
"composed = FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])\n",
"```"
]
},
Expand All @@ -230,8 +230,8 @@
"metadata": {},
"outputs": [],
"source": [
"# from pyrit.scenario import ScenarioCompositeStrategy\n",
"# composed = ScenarioCompositeStrategy(strategies=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])\n",
"# from pyrit.scenario.scenarios.foundry import FoundryComposite\n",
"# composed = FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])\n",
"# scenario_strategies = [FoundryStrategy.Base64, composed]"
]
},
Expand Down
12 changes: 6 additions & 6 deletions doc/scanner/foundry.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,18 @@
# %% [markdown]
# ## Strategy Composition
#
# You can combine multiple converters into a single composite strategy using
# `ScenarioCompositeStrategy`. Each converter in the composite is applied in sequence.
# You can pair a multi-turn attack with one or more converter strategies using `FoundryComposite`.
# Each converter in the composite is applied in sequence before the attack runs.
#
# ```python
# from pyrit.scenario import ScenarioCompositeStrategy
# from pyrit.scenario.scenarios.foundry import FoundryComposite
#
# composed = ScenarioCompositeStrategy(strategies=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])
# composed = FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])
# ```

# %%
# from pyrit.scenario import ScenarioCompositeStrategy
# composed = ScenarioCompositeStrategy(strategies=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])
# from pyrit.scenario.scenarios.foundry import FoundryComposite
# composed = FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])
# scenario_strategies = [FoundryStrategy.Base64, composed]

# %% [markdown]
Expand Down
10 changes: 5 additions & 5 deletions pyrit/scenario/core/dataset_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from collections.abc import Sequence

from pyrit.models.seeds.seed import Seed
from pyrit.scenario.core.scenario_strategy import ScenarioCompositeStrategy
from pyrit.scenario.core.scenario_strategy import ScenarioStrategy

# Key used when seed_groups are provided directly (not from a named dataset)
EXPLICIT_SEED_GROUPS_KEY = "_explicit_seed_groups"
Expand All @@ -38,7 +38,7 @@ class DatasetConfiguration:
dataset_names (Optional[List[str]]): Names of datasets to load from memory.
max_dataset_size (Optional[int]): If set, randomly samples up to this many SeedGroups
from the configured dataset source (without replacement, so no duplicates).
scenario_composites (Optional[Sequence[ScenarioCompositeStrategy]]): The scenario
scenario_strategies (Optional[Sequence[ScenarioStrategy]]): The scenario
strategies being executed. Subclasses can use this to filter or customize
which seed groups are loaded based on the selected strategies.
"""
Expand All @@ -49,7 +49,7 @@ def __init__(
seed_groups: Optional[list[SeedGroup]] = None,
dataset_names: Optional[list[str]] = None,
max_dataset_size: Optional[int] = None,
scenario_composites: Optional[Sequence[ScenarioCompositeStrategy]] = None,
scenario_strategies: Optional[Sequence[ScenarioStrategy]] = None,
) -> None:
"""
Initialize a DatasetConfiguration.
Expand All @@ -59,7 +59,7 @@ def __init__(
dataset_names (Optional[List[str]]): Names of datasets to load from memory.
max_dataset_size (Optional[int]): If set, randomly samples up to this many SeedGroups
(without replacement).
scenario_composites (Optional[Sequence[ScenarioCompositeStrategy]]): The scenario
scenario_strategies (Optional[Sequence[ScenarioStrategy]]): The scenario
strategies being executed. Subclasses can use this to filter or customize
which seed groups are loaded.

Expand All @@ -82,7 +82,7 @@ def __init__(
self._seed_groups = list(seed_groups) if seed_groups is not None else None
self.max_dataset_size = max_dataset_size
self._dataset_names = list(dataset_names) if dataset_names is not None else None
self._scenario_composites = scenario_composites
self._scenario_strategies = scenario_strategies

def get_seed_groups(self) -> dict[str, list[SeedGroup]]:
"""
Expand Down
Loading
Loading