|
94 | 94 | "Like target-level retry, JSON retry is **automatically applied** where needed. It's particularly useful when:\n", |
95 | 95 | "- Working with targets that return structured data\n", |
96 | 96 | "- LLMs occasionally generate malformed JSON\n", |
97 | | - "- You need automatic recovery from parsing errors" |
98 | | - ] |
99 | | - }, |
100 | | - { |
101 | | - "cell_type": "markdown", |
102 | | - "id": "3", |
103 | | - "metadata": {}, |
104 | | - "source": [ |
| 97 | + "- You need automatic recovery from parsing errors\n", |
| 98 | + "\n", |
| 99 | + "\n", |
105 | 100 | "## 3. Scenario-Level Retries: High-Level Workflow Resiliency\n", |
106 | 101 | "\n", |
107 | | - "\"### What is Scenario-Level Retry?\\n\",\n", |
108 | | - " \"\\n\",\n", |
109 | | - " \"Scenario-level retry is the **highest-level** retry mechanism in PyRIT. When enabled via the `max_retries` parameter, it allows an entire scenario execution to automatically retry if an exception occurs during the workflow. \\n\", \n", |
| 102 | + "### What is Scenario-Level Retry?\n", |
| 103 | + "\n", |
| 104 | + "Scenario-level retry is the **highest-level** retry mechanism in PyRIT. When enabled via the `max_retries` parameter, it allows an entire scenario execution to automatically retry if an exception occurs during the workflow.\n", |
110 | 105 | "\n", |
111 | 106 | "But also note, if you rerun the same scenario manually, that follows the same logic and is always an option.\n", |
112 | 107 | "\n", |
113 | 108 | "### Key Features\n", |
114 | 109 | "\n", |
115 | | - "\"\\n\",\n", |
116 | | - " \"- **Picks up where it left off**: On retry, the scenario skips already-completed objectives and continues from the point of exception\\n\",\n", |
117 | | - " \"- **Broad exception handling**: Catches any exception during scenario execution (network issues, target failures, scoring errors, etc.)\\n\",\n", |
118 | | - " \"- **Configurable attempts**: Set `max_retries` to control how many additional attempts are allowed\\n\",\n", |
| 110 | + "- **Picks up where it left off**: On retry, the scenario skips already-completed objectives and continues from the point of exception\n", |
| 111 | + "- **Broad exception handling**: Catches any exception during scenario execution (network issues, target failures, scoring errors, etc.)\n", |
| 112 | + "- **Configurable attempts**: Set `max_retries` to control how many additional attempts are allowed\n", |
119 | 113 | "- **Progress tracking**: The `number_tries` field in `ScenarioResult` tracks total attempts\n", |
120 | 114 | "\n", |
121 | 115 | "### How It Works\n", |
122 | 116 | "\n", |
123 | 117 | "When you call `scenario.run_async()`, PyRIT:\n", |
124 | 118 | "\n", |
125 | | - "\"1. **Initial Attempt**: Executes all atomic attacks in sequence\\n\",\n", |
126 | | - " \"2. **On Exception**: If an exception occurs, checks if retries remain\\n\",\n", |
127 | | - " \"3. **Retry with Resume**: On retry, queries memory to identify completed objectives and skips them\\n\",\n", |
| 119 | + "1. **Initial Attempt**: Executes all atomic attacks in sequence\n", |
| 120 | + "2. **On Exception**: If an exception occurs, checks if retries remain\n", |
| 121 | + "3. **Retry with Resume**: On retry, queries memory to identify completed objectives and skips them\n", |
128 | 122 | "4. **Continue from Exception Point**: Executes only the remaining objectives\n", |
129 | 123 | "5. **Repeat**: Continues retrying until success or `max_retries` exhausted\n", |
130 | 124 | "\n", |
|
134 | 128 | { |
135 | 129 | "cell_type": "code", |
136 | 130 | "execution_count": null, |
137 | | - "id": "4", |
| 131 | + "id": "3", |
138 | 132 | "metadata": {}, |
139 | 133 | "outputs": [ |
140 | 134 | { |
141 | 135 | "data": { |
142 | 136 | "application/vnd.jupyter.widget-view+json": { |
143 | | - "model_id": "7e7ec69c6fe6410daa8f213e916da7ca", |
| 137 | + "model_id": "585bf11361ad4e14bcf466f6948d1756", |
144 | 138 | "version_major": 2, |
145 | 139 | "version_minor": 0 |
146 | 140 | }, |
147 | 141 | "text/plain": [ |
148 | | - "Executing Foundry Scenario: 0%| | 0/1 [00:00<?, ?attack/s]" |
| 142 | + "Executing Foundry Scenario: 0%| | 0/2 [00:00<?, ?attack/s]" |
149 | 143 | ] |
150 | 144 | }, |
151 | 145 | "metadata": {}, |
|
156 | 150 | "output_type": "stream", |
157 | 151 | "text": [ |
158 | 152 | "Scenario completed after 1 attempt(s)\n", |
159 | | - "Total results: 1\n" |
| 153 | + "Total results: 2\n" |
160 | 154 | ] |
161 | 155 | } |
162 | 156 | ], |
|
170 | 164 | "objective_target = OpenAIChatTarget(model_name=\"gpt-4o\")\n", |
171 | 165 | "\n", |
172 | 166 | "# Create a scenario with retry configuration\n", |
173 | | - "scenario = FoundryScenario(\n", |
| 167 | + "scenario = FoundryScenario()\n", |
| 168 | + "\n", |
| 169 | + "await scenario.initialize_async( # type: ignore\n", |
174 | 170 | " objective_target=objective_target,\n", |
175 | 171 | " max_concurrency=5,\n", |
176 | | - " max_retries=3, # Allow up to 3 retries (4 total attempts)\n", |
| 172 | + " max_retries=3,\n", |
177 | 173 | " scenario_strategies=[FoundryStrategy.Base64],\n", |
178 | 174 | ")\n", |
179 | 175 | "\n", |
180 | | - "await scenario.initialize_async() # type: ignore\n", |
181 | | - "\n", |
182 | 176 | "# Execute with automatic retry after exceptions\n", |
183 | 177 | "result = await scenario.run_async() # type: ignore\n", |
184 | 178 | "\n", |
|
188 | 182 | }, |
189 | 183 | { |
190 | 184 | "cell_type": "markdown", |
191 | | - "id": "5", |
| 185 | + "id": "4", |
192 | 186 | "metadata": {}, |
193 | 187 | "source": [ |
194 | 188 | "\n", |
|
206 | 200 | "\n", |
207 | 201 | "Use scenario-level retries when:\n", |
208 | 202 | "\n", |
209 | | - "\"- ✅ Running long-duration test campaigns that might encounter transient exceptions\\n\",\n", |
210 | | - " \"- ✅ Testing against unreliable targets or networks\\n\",\n", |
| 203 | + "- ✅ Running long-duration test campaigns that might encounter transient exceptions\n", |
| 204 | + "- ✅ Testing against unreliable targets or networks\n", |
211 | 205 | "- ✅ You want to ensure comprehensive test coverage despite intermittent issues\n", |
212 | 206 | "- ✅ You need workflow-level resilience (e.g., partial completion + retry)\n", |
213 | 207 | "\n", |
|
220 | 214 | }, |
221 | 215 | { |
222 | 216 | "cell_type": "markdown", |
223 | | - "id": "6", |
| 217 | + "id": "5", |
224 | 218 | "metadata": {}, |
225 | 219 | "source": [ |
226 | 220 | "### Manual Scenario Resumption\n", |
|
259 | 253 | }, |
260 | 254 | { |
261 | 255 | "cell_type": "markdown", |
262 | | - "id": "7", |
| 256 | + "id": "6", |
263 | 257 | "metadata": {}, |
264 | 258 | "source": [ |
265 | 259 | "### Resume from Partial Completion\n", |
|
287 | 281 | }, |
288 | 282 | { |
289 | 283 | "cell_type": "markdown", |
290 | | - "id": "8", |
| 284 | + "id": "7", |
291 | 285 | "metadata": {}, |
292 | 286 | "source": [ |
293 | 287 | "## Understanding the Retry Hierarchy\n", |
|
297 | 291 | "### Core Retry Mechanisms\n", |
298 | 292 | "\n", |
299 | 293 | "```\n", |
300 | | - "\"┌─────────────────────────────────────────────────────────────┐\\n\",\n", |
301 | | - " \"│ Scenario-Level Retry (max_retries) │\\n\",\n", |
302 | | - " \"│ • Handles ANY exception in the entire workflow │\\n\",\n", |
303 | | - " \"│ • Resumes from point of exception │\\n\",\n", |
| 294 | + "┌─────────────────────────────────────────────────────────────┐\n", |
| 295 | + "│ Scenario-Level Retry (max_retries) │\n", |
| 296 | + "│ • Handles ANY exception in the entire workflow │\n", |
| 297 | + "│ • Resumes from point of exception │\n", |
304 | 298 | "│ • Configurable per scenario │\n", |
305 | 299 | "│ │\n", |
306 | | - "│ ┌───────────────────────────────────────────────────────┐ │\n", |
307 | | - "│ │ AtomicAttack Execution │ │\n", |
308 | | - "│ │ │ │\n", |
309 | | - "│ │ ┌─────────────────────────────────────────────────┐ │ │\n", |
310 | | - "│ │ │ JSON-Level Retry (pyrit_json_retry) │ │ │\n", |
311 | | - "│ │ │ • Handles invalid JSON responses │ │ │\n", |
312 | | - "│ │ │ • No exponential backoff (immediate retry) │ │ │\n", |
313 | | - "│ │ │ • Uses target call, so includes target retry │ │ │\n", |
314 | | - "│ │ │ │ │ │\n", |
315 | | - "│ │ │ ┌──────────────────────────────────────────┐ │ │ │\n", |
316 | | - "│ │ │ │ Target-Level Retry (pyrit_target_retry) │ │ │ │\n", |
317 | | - "│ │ │ │ • Handles rate limits, empty responses │ │ │ │\n", |
318 | | - "│ │ │ │ • Exponential backoff │ │ │ │\n", |
319 | | - "│ │ │ │ • Configured via environment variables │ │ │ │\n", |
320 | | - "│ │ │ └──────────────────────────────────────────┘ │ │ │\n", |
321 | | - "│ │ └─────────────────────────────────────────────────┘ │ │\n", |
322 | | - "│ └───────────────────────────────────────────────────────┘ │\n", |
| 300 | + "│ ┌───────────────────────────────────────────────────────┐ │\n", |
| 301 | + "│ │ AtomicAttack Execution │ │\n", |
| 302 | + "│ │ │ │\n", |
| 303 | + "│ │ ┌─────────────────────────────────────────────────┐ │ │\n", |
| 304 | + "│ │ │ JSON-Level Retry (pyrit_json_retry) │ │ │\n", |
| 305 | + "│ │ │ • Handles invalid JSON responses │ │ │\n", |
| 306 | + "│ │ │ • No exponential backoff (immediate retry) │ │ │\n", |
| 307 | + "│ │ │ • Uses target call, so includes target retry │ │ │\n", |
| 308 | + "│ │ │ │ │ │\n", |
| 309 | + "│ │ │ ┌──────────────────────────────────────────┐ │ │ │\n", |
| 310 | + "│ │ │ │ Target-Level Retry (pyrit_target_retry) │ │ │ │\n", |
| 311 | + "│ │ │ │ • Handles rate limits, empty responses │ │ │ │\n", |
| 312 | + "│ │ │ │ • Exponential backoff │ │ │ │\n", |
| 313 | + "│ │ │ │ • Configured via environment variables │ │ │ │\n", |
| 314 | + "│ │ │ └──────────────────────────────────────────┘ │ │ │\n", |
| 315 | + "│ │ └─────────────────────────────────────────────────┘ │ │\n", |
| 316 | + "│ └───────────────────────────────────────────────────────┘ │\n", |
323 | 317 | "└─────────────────────────────────────────────────────────────┘\n", |
324 | 318 | "```\n", |
325 | 319 | "\n", |
|
336 | 330 | }, |
337 | 331 | { |
338 | 332 | "cell_type": "markdown", |
339 | | - "id": "9", |
| 333 | + "id": "8", |
340 | 334 | "metadata": {}, |
341 | 335 | "source": [ |
342 | 336 | "## Best Practices\n", |
|
0 commit comments