Skip to content

Commit f08ea83

Browse files
rlundeen2Copilothannahwestra25
authored
REFACTOR Breaking: Migrate CLI to use pyrit.models (#1997)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: hannahwestra25 <hannahwestra@microsoft.com>
1 parent 75dcdfe commit f08ea83

33 files changed

Lines changed: 1486 additions & 807 deletions

pyrit/backend/mappers/target_mappers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
``TargetIdentifier`` instead of poking ``identifier.params`` by string key.
1313
"""
1414

15-
from pyrit.backend.models import TargetCapabilitiesInfo, TargetInstance
1615
from pyrit.models import TargetIdentifier
16+
from pyrit.models.catalog.target import TargetCapabilitiesInfo, TargetInstance
1717
from pyrit.prompt_target import PromptTarget
1818
from pyrit.prompt_target.common.target_capabilities import CapabilityName, TargetCapabilities
1919
from pyrit.prompt_target.round_robin_target import RoundRobinTarget
@@ -46,6 +46,7 @@ def _target_capabilities_to_info(
4646
supports_json_output=capabilities.supports_json_output,
4747
supports_editable_history=capabilities.supports_editable_history,
4848
supports_system_prompt=capabilities.supports_system_prompt,
49+
supports_streaming_audio=capabilities.supports_streaming_audio,
4950
supported_input_modalities=sorted({str(t) for combo in capabilities.input_modalities for t in combo}),
5051
supported_output_modalities=sorted({str(t) for combo in capabilities.output_modalities for t in combo}),
5152
)

pyrit/backend/models/__init__.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,15 @@
4949
PreviewStep,
5050
)
5151
from pyrit.backend.models.initializers import (
52-
InitializerParameterSummary,
5352
ListRegisteredInitializersResponse,
54-
RegisteredInitializer,
5553
RegisterInitializerRequest,
5654
)
5755
from pyrit.backend.models.scenarios import (
5856
ListRegisteredScenariosResponse,
59-
RegisteredScenario,
60-
ScenarioParameterSummary,
57+
ScenarioRunListResponse,
6158
)
6259
from pyrit.backend.models.targets import (
6360
CreateTargetRequest,
64-
TargetCapabilitiesInfo,
65-
TargetInstance,
6661
TargetListResponse,
6762
)
6863

@@ -108,16 +103,11 @@
108103
"PreviewStep",
109104
# Scenarios
110105
"ListRegisteredScenariosResponse",
111-
"RegisteredScenario",
112-
"ScenarioParameterSummary",
106+
"ScenarioRunListResponse",
113107
# Initializers
114-
"InitializerParameterSummary",
115108
"ListRegisteredInitializersResponse",
116-
"RegisteredInitializer",
117109
"RegisterInitializerRequest",
118110
# Targets
119111
"CreateTargetRequest",
120-
"TargetCapabilitiesInfo",
121-
"TargetInstance",
122112
"TargetListResponse",
123113
]

pyrit/backend/models/initializers.py

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,23 @@
22
# Licensed under the MIT license.
33

44
"""
5-
Initializer API response models.
5+
REST envelopes for the initializer endpoints.
66
7-
Initializers configure the PyRIT environment (targets, datasets, env vars)
8-
before scenario execution. These models represent initializer metadata.
7+
Canonical initializer catalog types (``RegisteredInitializer``,
8+
``InitializerParameterSummary``) live in ``pyrit.models.catalog.initializer``
9+
and should be imported from there directly.
910
"""
1011

1112
from pydantic import BaseModel, Field
1213

1314
from pyrit.backend.models.common import PaginationInfo
1415
from pyrit.models import REGISTRY_NAME_PATTERN
16+
from pyrit.models.catalog.initializer import RegisteredInitializer
1517

16-
17-
class InitializerParameterSummary(BaseModel):
18-
"""Summary of an initializer-declared parameter."""
19-
20-
name: str = Field(..., description="Parameter name")
21-
description: str = Field(..., description="Human-readable description of the parameter")
22-
default: list[str] | None = Field(None, description="Default value(s), or None if required")
23-
24-
25-
class RegisteredInitializer(BaseModel):
26-
"""Summary of a registered initializer."""
27-
28-
initializer_name: str = Field(..., description="Initializer registry name (e.g., 'target')")
29-
initializer_type: str = Field(..., description="Initializer class name (e.g., 'TargetInitializer')")
30-
description: str = Field("", description="Human-readable description of the initializer")
31-
required_env_vars: list[str] = Field(
32-
default_factory=list, description="Environment variables required by this initializer"
33-
)
34-
supported_parameters: list[InitializerParameterSummary] = Field(
35-
default_factory=list, description="Parameters accepted by this initializer"
36-
)
18+
__all__ = [
19+
"ListRegisteredInitializersResponse",
20+
"RegisterInitializerRequest",
21+
]
3722

3823

3924
class ListRegisteredInitializersResponse(BaseModel):

pyrit/backend/models/scenarios.py

Lines changed: 10 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,23 @@
22
# Licensed under the MIT license.
33

44
"""
5-
Scenario API response models.
5+
REST envelopes for the scenario endpoints.
66
7-
Scenarios are multi-attack security testing campaigns. These models represent
8-
the metadata about available scenarios (listing) and scenario execution (runs).
7+
Canonical scenario catalog/run types (``RegisteredScenario``,
8+
``ScenarioParameterSummary``, ``ScenarioRunSummary``, ``RunScenarioRequest``)
9+
live in ``pyrit.models.catalog.scenario`` and should be imported from there
10+
directly.
911
"""
1012

11-
from datetime import datetime
12-
from enum import Enum
13-
from typing import Any
14-
1513
from pydantic import BaseModel, Field
1614

1715
from pyrit.backend.models.common import PaginationInfo
16+
from pyrit.models.catalog.scenario import RegisteredScenario, ScenarioRunSummary
1817

19-
20-
class ScenarioParameterSummary(BaseModel):
21-
"""Summary of a scenario-declared parameter."""
22-
23-
name: str = Field(..., description="Parameter name (e.g., 'max_turns')")
24-
description: str = Field(..., description="Human-readable description of the parameter")
25-
default: str | None = Field(None, description="Default value as a display string, or None if required")
26-
param_type: str = Field(..., description="Type of the parameter as a display string (e.g., 'int', 'str')")
27-
choices: list[str] | None = Field(None, description="Allowed values as strings, or None if unconstrained")
28-
is_list: bool = Field(False, description="True when the parameter accepts a list of values (e.g., list[str])")
29-
30-
31-
class RegisteredScenario(BaseModel):
32-
"""Summary of a registered scenario."""
33-
34-
scenario_name: str = Field(..., description="Scenario name (e.g., 'foundry.red_team_agent')")
35-
scenario_type: str = Field(..., description="Scenario type identifier (e.g., 'RedTeamAgentScenario')")
36-
description: str = Field(..., description="Human-readable description of the scenario")
37-
default_strategy: str = Field(..., description="Default strategy name used when none specified")
38-
aggregate_strategies: list[str] = Field(
39-
..., description="Aggregate strategies that combine multiple attack approaches"
40-
)
41-
all_strategies: list[str] = Field(..., description="All available concrete strategy names")
42-
default_datasets: list[str] = Field(..., description="Default dataset names used by the scenario")
43-
max_dataset_size: int | None = Field(None, description="Maximum items per dataset (None means unlimited)")
44-
supported_parameters: list[ScenarioParameterSummary] = Field(
45-
default_factory=list, description="Scenario-declared custom parameters"
46-
)
18+
__all__ = [
19+
"ListRegisteredScenariosResponse",
20+
"ScenarioRunListResponse",
21+
]
4722

4823

4924
class ListRegisteredScenariosResponse(BaseModel):
@@ -53,73 +28,6 @@ class ListRegisteredScenariosResponse(BaseModel):
5328
pagination: PaginationInfo = Field(..., description="Pagination metadata")
5429

5530

56-
# ============================================================================
57-
# Scenario Run Models
58-
# ============================================================================
59-
60-
61-
class ScenarioRunStatus(str, Enum):
62-
"""Status of a scenario run, aligned with core ScenarioRunState."""
63-
64-
CREATED = "CREATED"
65-
INITIALIZING = "INITIALIZING"
66-
IN_PROGRESS = "IN_PROGRESS"
67-
COMPLETED = "COMPLETED"
68-
FAILED = "FAILED"
69-
CANCELLED = "CANCELLED"
70-
71-
72-
class RunScenarioRequest(BaseModel):
73-
"""Request body for starting a scenario run."""
74-
75-
scenario_name: str = Field(..., description="Scenario name (e.g., 'foundry.red_team_agent')")
76-
target_name: str = Field(..., description="Name of a registered target from the TargetRegistry")
77-
initializers: list[str] | None = Field(
78-
None, description="Initializer names to run before scenario (e.g., ['target', 'load_default_datasets'])"
79-
)
80-
strategies: list[str] | None = Field(None, description="Strategy names to use (uses scenario default if omitted)")
81-
dataset_names: list[str] | None = Field(None, description="Dataset names to use (uses scenario default if omitted)")
82-
max_dataset_size: int | None = Field(None, ge=1, description="Maximum items per dataset")
83-
max_concurrency: int = Field(10, ge=1, le=100, description="Maximum concurrent operations")
84-
max_retries: int = Field(0, ge=0, le=20, description="Maximum retry attempts on failure")
85-
labels: dict[str, str] | None = Field(None, description="Labels to attach to memory entries")
86-
scenario_params: dict[str, Any] | None = Field(
87-
None,
88-
description="Custom parameters for the scenario (passed to scenario.set_params_from_args). "
89-
"Keys are parameter names declared by the scenario's supported_parameters().",
90-
)
91-
initializer_args: dict[str, dict[str, Any]] | None = Field(
92-
None,
93-
description="Per-initializer arguments keyed by initializer name. "
94-
"Each value is a dict of args passed to that initializer's set_params_from_args(). "
95-
"Example: {'target': {'endpoint': 'https://...'}}.",
96-
)
97-
scenario_result_id: str | None = Field(
98-
None,
99-
description="Optional ID of an existing ScenarioResult to resume. "
100-
"If provided, the scenario will resume from prior progress instead of starting fresh.",
101-
)
102-
103-
104-
class ScenarioRunSummary(BaseModel):
105-
"""Response for a scenario run (status + result details)."""
106-
107-
scenario_result_id: str = Field(..., description="UUID of the ScenarioResult in memory")
108-
scenario_name: str = Field(..., description="Registry key of the scenario being run")
109-
scenario_version: int = Field(0, ge=0, description="Version of the scenario")
110-
status: ScenarioRunStatus = Field(..., description="Current run status")
111-
created_at: datetime = Field(..., description="When the run was created")
112-
updated_at: datetime = Field(..., description="When the run status last changed")
113-
error: str | None = Field(None, description="Error message if status is FAILED")
114-
error_type: str | None = Field(None, description="Exception class name if status is FAILED")
115-
strategies_used: list[str] = Field(default_factory=list, description="Strategy names that were executed")
116-
total_attacks: int = Field(0, ge=0, description="Total number of attack results persisted for this run")
117-
completed_attacks: int = Field(0, ge=0, description="Number of attacks that reached a terminal outcome")
118-
objective_achieved_rate: int = Field(0, ge=0, le=100, description="Success rate as percentage (0-100)")
119-
labels: dict[str, str] = Field(default_factory=dict, description="Labels attached to this run")
120-
completed_at: datetime | None = Field(None, description="When the scenario finished")
121-
122-
12331
class ScenarioRunListResponse(BaseModel):
12432
"""Response for listing scenario runs."""
12533

pyrit/backend/models/targets.py

Lines changed: 9 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,72 +2,24 @@
22
# Licensed under the MIT license.
33

44
"""
5-
Target instance models.
5+
REST envelopes and write-request types for the target endpoints.
66
7-
Targets have two concepts:
8-
- Types: Static metadata bundled with frontend (from registry)
9-
- Instances: Runtime objects created via API with specific configuration
10-
11-
This module defines the Instance models for runtime target management.
7+
Canonical target catalog types (``TargetInstance``, ``TargetCapabilitiesInfo``)
8+
live in ``pyrit.models.catalog.target`` and should be imported from there
9+
directly.
1210
"""
1311

1412
from typing import Any, Literal
1513

1614
from pydantic import BaseModel, Field
1715

1816
from pyrit.backend.models.common import PaginationInfo
17+
from pyrit.models.catalog.target import TargetInstance
1918

20-
21-
class TargetCapabilitiesInfo(BaseModel):
22-
"""
23-
Wire-format snapshot of a target's capabilities.
24-
25-
Mirrors the domain ``TargetCapabilities`` dataclass for API consumers
26-
(notably the GUI). Modality combinations (``frozenset[frozenset[...]]``)
27-
are flattened into sorted unique modality lists since the frontend uses
28-
them only for per-piece modality checks.
29-
"""
30-
31-
supports_multi_turn: bool = Field(False, description="Target natively supports multi-turn conversations")
32-
supports_multi_message_pieces: bool = Field(
33-
False, description="Target supports multiple message pieces in a single request"
34-
)
35-
supports_json_schema: bool = Field(False, description="Target can constrain output to a provided JSON schema")
36-
supports_json_output: bool = Field(False, description="Target supports JSON output mode")
37-
supports_editable_history: bool = Field(False, description="Target allows attack history to be modified")
38-
supports_system_prompt: bool = Field(False, description="Target natively supports system prompts")
39-
supported_input_modalities: list[str] = Field(
40-
default_factory=lambda: ["text"],
41-
description="Sorted unique input modality data types the target accepts (e.g., ['image_path', 'text'])",
42-
)
43-
supported_output_modalities: list[str] = Field(
44-
default_factory=lambda: ["text"],
45-
description="Sorted unique output modality data types the target produces (e.g., ['audio_path', 'text'])",
46-
)
47-
48-
49-
class TargetInstance(BaseModel):
50-
"""
51-
A runtime target instance.
52-
53-
Created either by an initializer (at startup) or by user (via API).
54-
Also used as the create-target response (same shape as GET).
55-
"""
56-
57-
target_registry_name: str = Field(..., description="Target registry key (e.g., 'azure_openai_chat')")
58-
target_type: str = Field(..., description="Target class name (e.g., 'OpenAIChatTarget')")
59-
endpoint: str | None = Field(None, description="Target endpoint URL")
60-
model_name: str | None = Field(None, description="Model or deployment name used in API calls")
61-
underlying_model_name: str | None = Field(None, description="Underlying model name if different (e.g., 'gpt-4o')")
62-
temperature: float | None = Field(None, description="Temperature parameter for generation")
63-
top_p: float | None = Field(None, description="Top-p parameter for generation")
64-
max_requests_per_minute: int | None = Field(None, description="Maximum requests per minute")
65-
capabilities: TargetCapabilitiesInfo = Field(..., description="Structured snapshot of target capabilities")
66-
target_specific_params: dict[str, Any] | None = Field(None, description="Additional target-specific parameters")
67-
inner_targets: list["TargetInstance"] | None = Field(
68-
None, description="Inner targets for composite targets like RoundRobinTarget"
69-
)
70-
identifier_hash: str | None = Field(None, description="ComponentIdentifier content hash for duplicate detection")
19+
__all__ = [
20+
"CreateTargetRequest",
21+
"TargetListResponse",
22+
]
7123

7224

7325
class TargetListResponse(BaseModel):

pyrit/backend/routes/initializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
from pyrit.backend.models.common import ProblemDetail
1919
from pyrit.backend.models.initializers import (
2020
ListRegisteredInitializersResponse,
21-
RegisteredInitializer,
2221
RegisterInitializerRequest,
2322
)
2423
from pyrit.backend.services.initializer_service import get_initializer_service
24+
from pyrit.models.catalog.initializer import RegisteredInitializer
2525

2626
router = APIRouter(prefix="/initializers", tags=["initializers"])
2727

pyrit/backend/routes/scenarios.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@
1717
from pyrit.backend.models.common import ProblemDetail
1818
from pyrit.backend.models.scenarios import (
1919
ListRegisteredScenariosResponse,
20-
RegisteredScenario,
21-
RunScenarioRequest,
2220
ScenarioRunListResponse,
23-
ScenarioRunSummary,
2421
)
2522
from pyrit.backend.services.scenario_run_service import get_scenario_run_service
2623
from pyrit.backend.services.scenario_service import get_scenario_service
2724
from pyrit.models import ScenarioResult
25+
from pyrit.models.catalog.scenario import (
26+
RegisteredScenario,
27+
RunScenarioRequest,
28+
ScenarioRunSummary,
29+
)
2830

2931
router = APIRouter(prefix="/scenarios", tags=["scenarios"])
3032

pyrit/backend/routes/targets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
from pyrit.backend.models.common import ProblemDetail
1414
from pyrit.backend.models.targets import (
1515
CreateTargetRequest,
16-
TargetInstance,
1716
TargetListResponse,
1817
)
1918
from pyrit.backend.services.target_service import get_target_service
19+
from pyrit.models.catalog.target import TargetInstance
2020

2121
router = APIRouter(prefix="/targets", tags=["targets"])
2222

pyrit/backend/services/initializer_service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
from pyrit.backend.models.common import PaginationInfo
1515
from pyrit.backend.models.initializers import (
16-
InitializerParameterSummary,
1716
ListRegisteredInitializersResponse,
17+
)
18+
from pyrit.models.catalog.initializer import (
19+
InitializerParameterSummary,
1820
RegisteredInitializer,
1921
)
2022
from pyrit.registry import InitializerMetadata, InitializerRegistry

0 commit comments

Comments
 (0)