|
9 | 9 |
|
10 | 10 | import asyncio |
11 | 11 | from collections.abc import Iterator, Sequence |
12 | | -from dataclasses import dataclass |
| 12 | +from dataclasses import dataclass, field |
13 | 13 | from typing import ( |
14 | 14 | TYPE_CHECKING, |
15 | 15 | Any, |
@@ -56,6 +56,13 @@ class AttackExecutorResult(Generic[AttackResultT]): |
56 | 56 |
|
57 | 57 | completed_results: list[AttackResultT] |
58 | 58 | incomplete_objectives: list[tuple[str, BaseException]] |
| 59 | + input_indices: list[int] = field(default_factory=list) |
| 60 | + """Maps each completed result to its position in the original input sequence. |
| 61 | +
|
| 62 | + ``input_indices[i]`` is the index in the original objectives/seed_groups/params |
| 63 | + list that produced ``completed_results[i]``. When some inputs fail, this lets |
| 64 | + callers correlate results back to the specific input that produced them. |
| 65 | + """ |
59 | 66 |
|
60 | 67 | def __iter__(self) -> Iterator[AttackResultT]: |
61 | 68 | """ |
@@ -331,16 +338,19 @@ def _process_execution_results( |
331 | 338 | """ |
332 | 339 | completed: list[AttackStrategyResultT] = [] |
333 | 340 | incomplete: list[tuple[str, BaseException]] = [] |
| 341 | + completed_indices: list[int] = [] |
334 | 342 |
|
335 | | - for objective, result in zip(objectives, results_or_exceptions, strict=False): |
| 343 | + for i, (objective, result) in enumerate(zip(objectives, results_or_exceptions, strict=False)): |
336 | 344 | if isinstance(result, BaseException): |
337 | 345 | incomplete.append((objective, result)) |
338 | 346 | else: |
339 | 347 | completed.append(result) |
| 348 | + completed_indices.append(i) |
340 | 349 |
|
341 | 350 | executor_result: AttackExecutorResult[AttackStrategyResultT] = AttackExecutorResult( |
342 | 351 | completed_results=completed, |
343 | 352 | incomplete_objectives=incomplete, |
| 353 | + input_indices=completed_indices, |
344 | 354 | ) |
345 | 355 |
|
346 | 356 | if not return_partial_on_failure: |
|
0 commit comments