@@ -622,16 +622,23 @@ async def _build_groups_by_dataset_async(self) -> tuple[dict[str, list[SeedAttac
622622 )
623623 return groups_by_dataset , resolved
624624
625- async def get_seed_attack_groups_async (self ) -> list [SeedAttackGroup ]:
625+ async def get_seed_attack_groups_async (self , * , apply_sampling : bool = True ) -> list [SeedAttackGroup ]:
626626 """
627627 Resolve the configured dataset into a flat ``list[SeedAttackGroup]``.
628628
629629 Builds attack groups (inline or from memory, auto-fetching missing datasets),
630630 validates the full resolved seed set, then samples ``max_dataset_size`` globally
631631 over all built groups.
632632
633+ Args:
634+ apply_sampling (bool): When True (default), apply ``max_dataset_size`` sampling.
635+ Pass False to resolve the full, deterministic dataset with no ``random.sample``
636+ draw -- used on resume so the persisted objective subset can be reconstructed
637+ exactly rather than intersected against a fresh (divergent) sample.
638+
633639 Returns:
634- list[SeedAttackGroup]: The validated, sampled attack groups.
640+ list[SeedAttackGroup]: The validated attack groups (sampled when ``apply_sampling``
641+ is True, otherwise the full resolved set).
635642
636643 Raises:
637644 DatasetConstraintError: If a configured dataset yields no seeds, the resolved
@@ -640,13 +647,16 @@ async def get_seed_attack_groups_async(self) -> list[SeedAttackGroup]:
640647 groups_by_dataset , resolved = await self ._build_groups_by_dataset_async ()
641648 self .validate (resolved )
642649 groups = [group for groups in groups_by_dataset .values () for group in groups ]
643- groups = self ._apply_max_dataset_size (groups )
650+ if apply_sampling :
651+ groups = self ._apply_max_dataset_size (groups )
644652 if not groups :
645653 names = ", " .join (self ._dataset_names ) if self ._dataset_names else "<inline>"
646654 raise DatasetConstraintError (f"Resolved attack-group dataset is empty (datasets: { names } )." )
647655 return groups
648656
649- async def get_attack_groups_by_dataset_async (self ) -> dict [str , list [SeedAttackGroup ]]:
657+ async def get_attack_groups_by_dataset_async (
658+ self , * , apply_sampling : bool = True
659+ ) -> dict [str , list [SeedAttackGroup ]]:
650660 """
651661 Resolve attack groups keyed by dataset name, globally sampled.
652662
@@ -656,16 +666,24 @@ async def get_attack_groups_by_dataset_async(self) -> dict[str, list[SeedAttackG
656666 keyed by their originating dataset. For an independent budget per dataset, compose
657667 ``CompoundDatasetAttackConfiguration.per_dataset(...)`` instead.
658668
669+ Args:
670+ apply_sampling (bool): When True (default), apply ``max_dataset_size`` sampling.
671+ Pass False to resolve the full, deterministic dataset with no ``random.sample``
672+ draw -- used on resume so the persisted objective subset can be reconstructed
673+ exactly rather than intersected against a fresh (divergent) sample.
674+
659675 Returns:
660- dict[str, list[SeedAttackGroup]]: Dataset name -> sampled attack groups.
676+ dict[str, list[SeedAttackGroup]]: Dataset name -> attack groups (sampled when
677+ ``apply_sampling`` is True, otherwise the full resolved set).
661678
662679 Raises:
663680 DatasetConstraintError: If a configured dataset yields no seeds, the resolved
664681 dataset fails validation, or no attack groups could be built.
665682 """
666683 groups_by_dataset , resolved = await self ._build_groups_by_dataset_async ()
667684 self .validate (resolved )
668- result = {name : groups for name , groups in self ._sample_groups_by_dataset (groups_by_dataset ).items () if groups }
685+ sampled = self ._sample_groups_by_dataset (groups_by_dataset ) if apply_sampling else groups_by_dataset
686+ result = {name : groups for name , groups in sampled .items () if groups }
669687 if not result :
670688 names = ", " .join (self ._dataset_names ) if self ._dataset_names else "<inline>"
671689 raise DatasetConstraintError (f"Resolved attack-group dataset is empty (datasets: { names } )." )
@@ -819,29 +837,42 @@ def update_filters(self, *, filters: dict[str, list[str]]) -> None:
819837 for child in self ._configurations :
820838 child .update_filters (filters = filters )
821839
822- async def get_seed_attack_groups_async (self ) -> list [SeedAttackGroup ]:
840+ async def get_seed_attack_groups_async (self , * , apply_sampling : bool = True ) -> list [SeedAttackGroup ]:
823841 """
824842 Concatenate every child's flat result, then validate and apply the global cap.
825843
826844 Each child validates and samples itself; the combined result is validated against this
827845 compound's validators and capped by an optional compound ``max_dataset_size``.
828846
847+ Args:
848+ apply_sampling (bool): When True (default), sample both each child and the combined
849+ result under ``max_dataset_size``. Pass False to resolve the full, deterministic
850+ dataset with no sampling at any level -- used on resume (propagated to children).
851+
829852 Returns:
830- list[SeedAttackGroup]: The combined, validated, capped attack groups.
853+ list[SeedAttackGroup]: The combined, validated attack groups (capped when
854+ ``apply_sampling`` is True).
831855
832856 Raises:
833857 DatasetConstraintError: If a child yields nothing, or the combined result fails validation.
834858 """
835859 groups : list [SeedAttackGroup ] = []
836860 for child in self ._configurations :
837- groups .extend (await child .get_seed_attack_groups_async ())
861+ groups .extend (await child .get_seed_attack_groups_async (apply_sampling = apply_sampling ))
838862 self .validate (self ._resolved_from_groups (groups ))
839- return self ._apply_max_dataset_size (groups )
863+ return self ._apply_max_dataset_size (groups ) if apply_sampling else groups
840864
841- async def get_attack_groups_by_dataset_async (self ) -> dict [str , list [SeedAttackGroup ]]:
865+ async def get_attack_groups_by_dataset_async (
866+ self , * , apply_sampling : bool = True
867+ ) -> dict [str , list [SeedAttackGroup ]]:
842868 """
843869 Merge each child's by-dataset result, validate, then apply the global cap across the union.
844870
871+ Args:
872+ apply_sampling (bool): When True (default), sample both each child and the merged
873+ union under ``max_dataset_size``. Pass False to resolve the full, deterministic
874+ dataset with no sampling at any level -- used on resume (propagated to children).
875+
845876 Returns:
846877 dict[str, list[SeedAttackGroup]]: Combined groups keyed by dataset name.
847878
@@ -850,10 +881,11 @@ async def get_attack_groups_by_dataset_async(self) -> dict[str, list[SeedAttackG
850881 """
851882 merged : dict [str , list [SeedAttackGroup ]] = {}
852883 for child in self ._configurations :
853- for name , groups in (await child .get_attack_groups_by_dataset_async ()).items ():
884+ child_groups = await child .get_attack_groups_by_dataset_async (apply_sampling = apply_sampling )
885+ for name , groups in child_groups .items ():
854886 merged .setdefault (name , []).extend (groups )
855887 self .validate (self ._resolved_from_groups ([group for groups in merged .values () for group in groups ]))
856- return self ._sample_groups_by_dataset (merged )
888+ return self ._sample_groups_by_dataset (merged ) if apply_sampling else merged
857889
858890 def _resolved_from_groups (self , groups : list [SeedAttackGroup ]) -> ResolvedDataset :
859891 """
0 commit comments