Skip to content

Commit e6f1a32

Browse files
rlundeen2Copilot
andauthored
MAINT: Migrating AttackResult to Pydantic (#1899)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 729edfa commit e6f1a32

25 files changed

Lines changed: 638 additions & 397 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# PyRIT-specific configs
22
submodules/
3-
results/
3+
/results/
44
dbdata/
55
eval/
66
default_memory.json.memory

pyrit/auxiliary_attacks/gcg/generator.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
import numpy as np
4444
import torch.multiprocessing as mp
45+
from pydantic import Field
4546

4647
import pyrit.auxiliary_attacks.gcg.attack.gcg.gcg_attack as attack_lib
4748
from pyrit.auxiliary_attacks.gcg.attack.base.attack_manager import (
@@ -96,7 +97,6 @@ class GCGContext(PromptGeneratorStrategyContext):
9697
logfile_path: Optional[str] = None
9798

9899

99-
@dataclass
100100
class GCGResult(PromptGeneratorStrategyResult):
101101
"""Result of one GCGGenerator run.
102102
@@ -117,10 +117,10 @@ class GCGResult(PromptGeneratorStrategyResult):
117117
final_suffix: str = ""
118118
final_loss: float = float("nan")
119119
step_count: int = 0
120-
loss_history: list[float] = field(default_factory=list)
121-
control_history: list[str] = field(default_factory=list)
122-
log_path: Optional[str] = None
123-
memory_labels: dict[str, str] = field(default_factory=dict)
120+
loss_history: list[float] = Field(default_factory=list)
121+
control_history: list[str] = Field(default_factory=list)
122+
log_path: str | None = None
123+
memory_labels: dict[str, str] = Field(default_factory=dict)
124124

125125

126126
class GCGGenerator(

pyrit/executor/attack/compound/sequential_attack.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
from enum import Enum
2929
from typing import TYPE_CHECKING, Any, Optional
3030

31+
from pydantic import Field
32+
3133
from pyrit.executor.attack.core.attack_executor import AttackExecutor
3234
from pyrit.executor.attack.core.attack_parameters import AttackParameters
3335
from pyrit.executor.attack.core.attack_strategy import AttackContext, AttackStrategy
@@ -110,7 +112,6 @@ class SequentialChildAttack:
110112
memory_labels: Mapping[str, str] = field(default_factory=dict)
111113

112114

113-
@dataclass
114115
class SequentialAttackResult(AttackResult):
115116
"""
116117
Result of a ``SequentialAttack`` execution.
@@ -138,7 +139,7 @@ class SequentialAttackResult(AttackResult):
138139
round-trip.
139140
"""
140141

141-
child_attack_results: list[AttackResult] = field(default_factory=list)
142+
child_attack_results: list[AttackResult] = Field(default_factory=list)
142143
completion_policy: SequenceCompletionPolicy = SequenceCompletionPolicy.FIRST_SUCCESS
143144

144145
@property

pyrit/executor/attack/multi_turn/crescendo.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ class CrescendoAttackContext(MultiTurnAttackContext[Any]):
8181
backtrack_count: int = 0
8282

8383

84-
@dataclass
8584
class CrescendoAttackResult(AttackResult):
8685
"""Result of the Crescendo attack strategy execution."""
8786

@@ -832,7 +831,9 @@ async def _perform_backtrack_if_refused_async(
832831

833832
# Check for refusal using the scorer (handles blocked/error responses internally)
834833
refusal_score = await self._check_refusal_async(context, prompt_sent)
835-
self._logger.debug(f"Refusal check: {refusal_score.get_value()} - {refusal_score.score_rationale[:100]}...")
834+
self._logger.debug(
835+
f"Refusal check: {refusal_score.get_value()} - {(refusal_score.score_rationale or '')[:100]}..."
836+
)
836837
is_refusal = bool(refusal_score.get_value())
837838

838839
if not is_refusal:

pyrit/executor/attack/multi_turn/tree_of_attacks.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ class TAPAttackContext(MultiTurnAttackContext[Any]):
176176
best_adversarial_conversation_id: Optional[str] = None
177177

178178

179-
@dataclass
180179
class TAPAttackResult(AttackResult):
181180
"""
182181
Result of the Tree of Attacks with Pruning (TAP) attack strategy execution.
@@ -699,7 +698,8 @@ async def _score_response_async(self, *, response: Message, objective: str) -> N
699698
# Extract auxiliary scores
700699
auxiliary_scores = scoring_results["auxiliary_scores"]
701700
for score in auxiliary_scores:
702-
scorer_name = score.scorer_class_identifier.class_name
701+
scorer_identifier = score.scorer_class_identifier
702+
scorer_name = scorer_identifier.class_name if scorer_identifier else "unknown"
703703
self.auxiliary_scores[scorer_name] = score
704704
logger.debug(f"Node {self.node_id}: {scorer_name} score: {score.get_value()}")
705705

@@ -904,7 +904,7 @@ async def _generate_red_teaming_prompt_async(self, objective: str) -> str:
904904
# Generate feedback prompt and get a new response
905905
feedback_prompt = self._generate_off_topic_feedback_prompt(
906906
original_prompt=prompt,
907-
off_topic_rationale=on_topic_score.score_rationale,
907+
off_topic_rationale=on_topic_score.score_rationale or "",
908908
objective=objective,
909909
)
910910

pyrit/executor/promptgen/anecdoctor.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ class AnecdoctorContext(PromptGeneratorStrategyContext):
5757
memory_labels: dict[str, str] = field(default_factory=dict)
5858

5959

60-
@dataclass
6160
class AnecdoctorResult(PromptGeneratorStrategyResult):
6261
"""
6362
Result of Anecdoctor prompt generation.

pyrit/executor/promptgen/core/prompt_generator_strategy.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ class PromptGeneratorStrategyContext(StrategyContext, ABC):
2626
"""Base class for all prompt generator strategy contexts."""
2727

2828

29-
@dataclass
30-
class PromptGeneratorStrategyResult(StrategyResult, ABC):
29+
class PromptGeneratorStrategyResult(StrategyResult, ABC): # noqa: B024
3130
"""Base class for all prompt generator strategy results."""
3231

3332

pyrit/executor/promptgen/fuzzer/fuzzer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import numpy as np
1414
from colorama import Fore, Style
15+
from pydantic import Field
1516

1617
from pyrit.common.utils import combine_dict, get_kwarg_param
1718
from pyrit.exceptions import MissingPromptPlaceholderException, pyrit_placeholder_retry
@@ -212,7 +213,6 @@ def __post_init__(self) -> None:
212213
)
213214

214215

215-
@dataclass
216216
class FuzzerResult(PromptGeneratorStrategyResult):
217217
"""
218218
Result of the Fuzzer prompt generation strategy execution.
@@ -222,8 +222,8 @@ class FuzzerResult(PromptGeneratorStrategyResult):
222222
"""
223223

224224
# Concrete fields instead of metadata storage
225-
successful_templates: list[str] = field(default_factory=list)
226-
jailbreak_conversation_ids: list[Union[str, uuid.UUID]] = field(default_factory=list)
225+
successful_templates: list[str] = Field(default_factory=list)
226+
jailbreak_conversation_ids: list[Union[str, uuid.UUID]] = Field(default_factory=list)
227227
total_queries: int = 0
228228
templates_explored: int = 0
229229

pyrit/executor/workflow/core/workflow_strategy.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ class WorkflowContext(StrategyContext, ABC):
2727
"""Base class for all workflow contexts."""
2828

2929

30-
@dataclass
31-
class WorkflowResult(StrategyResult, ABC):
30+
class WorkflowResult(StrategyResult, ABC): # noqa: B024
3231
"""Base class for all workflow results."""
3332

3433

pyrit/executor/workflow/xpia.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ class XPIAContext(WorkflowContext):
8181
memory_labels: dict[str, str] = field(default_factory=dict)
8282

8383

84-
@dataclass
8584
class XPIAResult(WorkflowResult):
8685
"""
8786
Result of XPIA workflow execution.

0 commit comments

Comments
 (0)