pyrit.scenario.Scenario#
- class Scenario(*, name: str, version: int, strategy_class: Type[ScenarioStrategy], objective_scorer_identifier: Dict[str, str] | None = None, include_default_baseline: bool = True, scenario_result_id: str | UUID | None = None)[source]#
Bases:
ABCGroups and executes multiple AtomicAttack instances sequentially.
A Scenario represents a comprehensive testing campaign composed of multiple atomic attack tests (AtomicAttacks). It executes each AtomicAttack in sequence and aggregates the results into a ScenarioResult.
Example
>>> from pyrit.scenario import Scenario, AtomicAttack >>> from pyrit.executor.attack.single_turn.prompt_sending import PromptSendingAttack >>> from pyrit.prompt_target import OpenAIChatTarget >>> from pyrit.prompt_converter import Base64Converter >>> >>> target = OpenAIChatTarget() >>> >>> # Create a custom scenario subclass >>> class MyScenario(Scenario): ... async def _get_atomic_attacks_async(self) -> List[AtomicAttack]: ... base64_attack = PromptSendingAttack( ... objective_target=target, ... converters=[Base64Converter()] ... ) ... return [ ... AtomicAttack( ... attack=base64_attack, ... objectives=["Tell me how to make a bomb"] ... ) ... ] >>> >>> # Create and execute scenario >>> scenario = MyScenario( ... name="Security Test Campaign", ... version=1, ... attack_strategies=["base64"] ... ) >>> await scenario.initialize_async() >>> result = await scenario.run_async() >>> print(f"Completed {len(result.attack_results)} tests")
- __init__(*, name: str, version: int, strategy_class: Type[ScenarioStrategy], objective_scorer_identifier: Dict[str, str] | None = None, include_default_baseline: bool = True, scenario_result_id: str | UUID | None = None) None[source]#
Initialize a scenario.
- Parameters:
name (str) – Descriptive name for the scenario.
version (int) – Version number of the scenario.
strategy_class (Type[ScenarioStrategy]) – The strategy enum class for this scenario.
objective_scorer_identifier (Optional[Dict[str, str]]) – Identifier for the objective scorer.
include_default_baseline (bool) – Whether to include a baseline atomic attack that sends all objectives from the first atomic attack without modifications. Most scenarios should have some kind of baseline so users can understand the impact of strategies, but subclasses can optionally write their own custom baselines. Defaults to True.
scenario_result_id (Optional[Union[uuid.UUID, str]]) – Optional ID of an existing scenario result to resume. Can be either a UUID object or a string representation of a UUID. If provided and found in memory, the scenario will resume from prior progress. All other parameters must still match the stored scenario configuration.
Note
Attack runs are populated by calling initialize_async(), which invokes the subclass’s _get_atomic_attacks_async() method.
The scenario description is automatically extracted from the class’s docstring (__doc__) with whitespace normalized for display.
Methods
__init__(*, name, version, strategy_class[, ...])Initialize a scenario.
Get the default strategy used when no strategies are specified.
Get the strategy enum class for this scenario.
initialize_async(*[, objective_target, ...])Initialize the scenario by populating self._atomic_attacks and creating the ScenarioResult.
Execute all atomic attacks in the scenario sequentially.
Attributes
Get the number of atomic attacks in this scenario.
Get the name of the scenario.
- abstract classmethod get_default_strategy() ScenarioStrategy[source]#
Get the default strategy used when no strategies are specified.
This abstract method must be implemented by all scenario subclasses to return the default aggregate strategy (like EASY, ALL) used when scenario_strategies parameter is None.
- Returns:
The default aggregate strategy (e.g., FoundryStrategy.EASY, EncodingStrategy.ALL).
- Return type:
Example
>>> class MyScenario(Scenario): ... @classmethod ... def get_default_strategy(cls) -> ScenarioStrategy: ... return MyStrategy.EASY >>> >>> # Registry can discover default strategy without instantiation >>> default = MyScenario.get_default_strategy()
- abstract classmethod get_strategy_class() Type[ScenarioStrategy][source]#
Get the strategy enum class for this scenario.
This abstract method must be implemented by all scenario subclasses to return the ScenarioStrategy enum class that defines the available attack strategies for the scenario.
- Returns:
The strategy enum class (e.g., FoundryStrategy, EncodingStrategy).
- Return type:
Type[ScenarioStrategy]
Example
>>> class MyScenario(Scenario): ... @classmethod ... def get_strategy_class(cls) -> Type[ScenarioStrategy]: ... return MyStrategy >>> >>> # Registry can now discover strategies without instantiation >>> strategy_class = MyScenario.get_strategy_class() >>> all_strategies = list(strategy_class)
- initialize_async(*, objective_target: PromptTarget = REQUIRED_VALUE, scenario_strategies: Sequence[ScenarioStrategy | ScenarioCompositeStrategy] | None = None, max_concurrency: int = 1, max_retries: int = 0, memory_labels: Dict[str, str] | None = None) None[source]#
Initialize the scenario by populating self._atomic_attacks and creating the ScenarioResult.
This method allows scenarios to be initialized with atomic attacks after construction, which is useful when atomic attacks require async operations to be built.
If a scenario_result_id was provided in __init__, this method will check if it exists in memory and validate that the stored scenario matches the current configuration. If it matches, the scenario will resume from prior progress. If it doesn’t match or doesn’t exist, a new scenario result will be created.
- Parameters:
objective_target (PromptTarget) – The target system to attack.
scenario_strategies (Optional[Sequence[ScenarioStrategy | ScenarioCompositeStrategy]]) – The strategies to execute. Can be a list of bare ScenarioStrategy enums or ScenarioCompositeStrategy instances for advanced composition. Bare enums are automatically wrapped into composites. If None, uses the default aggregate from the scenario’s configuration.
max_concurrency (int) – Maximum number of concurrent attack executions. Defaults to 1.
max_retries (int) – Maximum number of automatic retries if the scenario raises an exception. Set to 0 (default) for no automatic retries. If set to a positive number, the scenario will automatically retry up to this many times after an exception. For example, max_retries=3 allows up to 4 total attempts (1 initial + 3 retries).
memory_labels (Optional[Dict[str, str]]) – Additional labels to apply to all attack runs in the scenario. These help track and categorize the scenario.
- Raises:
ValueError – If no objective_target is provided.
Example
>>> # New scenario >>> scenario = MyScenario( ... name="Security Test", ... version=1 ... ) >>> await scenario.initialize_async( ... objective_target=target, ... scenario_strategies=[MyStrategy.Base64, MyStrategy.ROT13] ... ) >>> results = await scenario.run_async() >>> >>> # Resume existing scenario >>> scenario_id = results.id >>> resumed_scenario = MyScenario( ... name="Security Test", ... version=1, ... scenario_result_id=str(scenario_id) ... ) >>> await resumed_scenario.initialize_async( ... objective_target=target, ... scenario_strategies=[MyStrategy.Base64, MyStrategy.ROT13] ... ) >>> results = await resumed_scenario.run_async() # Resumes from progress
- async run_async() ScenarioResult[source]#
Execute all atomic attacks in the scenario sequentially.
Each AtomicAttack is executed in order, and all results are aggregated into a ScenarioResult containing the scenario metadata and all attack results. This method supports resumption - if the scenario raises an exception partway through, calling run_async again will skip already-completed objectives.
If max_retries is set, the scenario will automatically retry after an exception up to the specified number of times. Each retry will resume from where it left off, skipping completed objectives.
- Returns:
- Contains scenario identifier and aggregated list of all
attack results from all atomic attacks.
- Return type:
- Raises:
ValueError – If the scenario has no atomic attacks configured. If your scenario requires initialization, call await scenario.initialize() first.
ValueError – If the scenario raises an exception after exhausting all retry attempts.
RuntimeError – If the scenario fails for any other reason while executing.
Example
>>> result = await scenario.run_async() >>> print(f"Scenario: {result.scenario_identifier.name}") >>> print(f"Total results: {len(result.attack_results)}")