FEAT text adaptive scenario#1760
Merged
hannahwestra25 merged 53 commits intoJun 3, 2026
Merged
Conversation
…ra/text_adaptive_scenario
…ra/text_adaptive_scenario
hannahwestra25
commented
May 19, 2026
hannahwestra25
commented
May 19, 2026
rlundeen2
reviewed
May 19, 2026
rlundeen2
reviewed
May 19, 2026
rlundeen2
reviewed
May 19, 2026
rlundeen2
reviewed
May 19, 2026
rlundeen2
reviewed
May 19, 2026
rlundeen2
reviewed
May 19, 2026
rlundeen2
reviewed
May 19, 2026
rlundeen2
reviewed
May 19, 2026
…ra/text_adaptive_scenario
…-text-adaptive-scenario
- Remove prompt_sending from adaptive pool; enable baseline comparison - Expose max_attempts_per_objective via supported_parameters() (scam.py pattern) - Rename AdaptiveTechniqueSelector -> EpsilonGreedyTechniqueSelector - Extract TechniqueSelector Protocol; accept custom selector via kwarg - Per-decision RNG derivation (SHA-256) for resume reproducibility - Drop uuid.uuid4() fallback for objective IDs - Per-dataset atomic attacks (one AtomicAttack per dataset, not per objective) - AdaptiveDispatchParams with per-call seed_group and compatibility filtering - Context extraction moved to dispatcher - Rehydration uses get_attack_results with attribution_data filtering - Split selector.py into selectors/ folder (protocol.py + epsilon_greedy.py) - Update notebooks for new API patterns Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
hannahwestra25
commented
May 21, 2026
…-text-adaptive-scenario
added 2 commits
June 1, 2026 18:33
f0db060 to
929d398
Compare
…Pydantic identifiers) Four related fixes needed to unblock CI after the upstream merge that brought in PR microsoft#1881 (Refactoring Identifiers to be Pydantic classes): 1. `pyrit/models/identifiers/evaluation_identifier.py`: The merge hoisted `from pyrit.executor.attack.core.attack_strategy import AttackStrategy` to module level, forming a cycle through `pyrit.executor.attack` -> `pyrit.message_normalizer` -> `pyrit.common.data_url_converter` -> `pyrit.models`. Move it back inside `if TYPE_CHECKING:` (`from __future__ import annotations` is already enabled, so the string annotation `attack: AttackStrategy` still resolves at type-check time). 2. `tests/unit/models/identifiers/test_evaluation_identifier.py`: Add missing blank lines between top-level classes (ruff format) and replace four `from pyrit.identifiers import ...` lines with `from pyrit.models.identifiers import ...` (the former is now a deprecation shim that the static-scan deprecation test forbids internal callers from using). 3. `pyrit/scenario/scenarios/adaptive/adaptive_scenario.py`: Mark the three classmethod stubs as `@abstractmethod` so `inspect.isabstract(AdaptiveScenario)` returns `True` and the scenario registry's auto-discovery skips it (otherwise the registry tries to instantiate the abstract base and raises `NotImplementedError`, breaking `test_load_default_datasets`). 4. `pyrit/scenario/scenarios/adaptive/adaptive_scenario.py` and `pyrit/scenario/scenarios/adaptive/text_adaptive.py`: Move `from pyrit.setup.initializers.components.scenario_techniques import build_scenario_technique_factories` from module level back into the function body. `scenario_techniques` imports `pyrit.scenario.core`, which transitively re-imports the adaptive package during `pyrit.scenario` initialization, so a top-level import forms a cycle. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f1b6373 to
77a3704
Compare
…n SequentialAttack
Replaces the previous AdaptiveDispatchAttack (an AttackStrategy subclass that
delegated to an internal SequentialAttack) with a slim factory + subclass:
- AdaptiveTechniqueDispatcher: plain class, not an AttackStrategy. Exposes
compatible_techniques(seed_group=...) and async build_attack_async(seed_group=...).
Selects techniques up-front per objective via TechniqueSelector and returns
a fully-wired AdaptiveSequentialAttack.
- AdaptiveSequentialAttack: ~15-LoC SequentialAttack subclass. Adds a
technique_labels constructor argument and stamps adaptive_attempts
metadata onto the envelope returned by super()._perform_async, then
delegates everything else to the framework.
Atomic-attack shape:
- One AtomicAttack per (dataset, seed_group) instead of one per dataset.
- atomic_attack_name = `{prefix}_{dataset}::{objective_sha[:12]}` so each
objective gets its own deterministic, hash-disambiguated identifier.
- display_group = dataset_name preserves the grouping for reporting.
- All per-dataset dispatchers share the same TechniqueSelector instance so
learning still accumulates globally.
Rationale: eliminates the dispatcher's coupling to SequentialAttack's private
lifecycle internals (no more manual `_perform_async` orchestration, no more
duplicating envelope/metadata wiring) and removes a layer of indirection that
was making the call graph hard to reason about. The dispatcher is now an
`envelope factory'', not an attack.
All 56 adaptive unit tests pass (verified via docker devcontainer pytest run).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ra/text_adaptive_scenario
rlundeen2
reviewed
Jun 2, 2026
rlundeen2
reviewed
Jun 2, 2026
rlundeen2
approved these changes
Jun 3, 2026
added 2 commits
June 3, 2026 14:40
romanlutz
added a commit
to romanlutz/PyRIT
that referenced
this pull request
Jun 4, 2026
Merge 26 commits from main, including: - MAINT Breaking: Convert ScenarioResult to Pydantic (microsoft#1908) - MAINT: Migrating Seed classes to Pydantic (microsoft#1898) - MAINT: Migrating AttackResult to Pydantic (microsoft#1899) - MAINT: Bump ty-pre-commit v0.0.32 -> 0.0.43 (microsoft#1919) - FEAT: Realtime streaming session support and server-side barge-in attack (microsoft#1766) - FEAT text adaptive scenario (microsoft#1760) - FIX: Integration Test Fixes (microsoft#1907) - DOC: Scoring Docs Refactor (microsoft#1892) - Various dependency bumps Conflicts (15 files) resolved by taking main's version + re-running ruff --fix to re-apply PEP 604 typing modernization on the incoming code (177 violations auto-fixed). All resolved files re-staged. Local verification: - ruff check: All checks passed - ruff format: clean - pytest tests/unit -n 8: 9550 passed, 6 skipped Known issue (pre-existing on main, not caused by this merge): - ty 0.0.43 enabled missing-override-decorator rule, which flags hundreds of pre-existing methods across the codebase. Main's own CI is currently failing on this. Our PR will inherit the same failure since touched files come into pre-commit scope. Fixing this rule globally is a separate, large mechanical change orthogonal to typing modernization. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add Adaptive Scenario Framework with
TextAdaptiveSummary
Adds an adaptive scenario framework that picks attack techniques per-objective using an epsilon-greedy bandit informed by observed success rates, instead of running every technique against every objective. Concentrates spend on what works against the target and stops on first success —
O(max_attempts × objectives)instead ofO(techniques × objectives).Includes:
AdaptiveScenario(modality-agnostic base) +TextAdaptive(text subclass)TechniqueSelectorProtocol +EpsilonGreedyTechniqueSelector(Laplace-smoothed, pooled cold-start backoff, thread-safe)AdaptiveTechniqueDispatcher— a thin factory (not anAttackStrategy) that selects techniques up-front per objective and returns a ready-to-runSequentialAttack(FIRST_SUCCESS)How it works
For each objective:
(s+1)/(n+1)estimates; cells with fewer thanpool_thresholdobservations back off to the technique's pooled rate. Each decision derives a per-decision RNG fromSHA-256(seed|context|decision_key)for resume-safe reproducibility.max_attempts_per_objectivetechniques become children of oneSequentialAttack(FIRST_SUCCESS), wrapped in oneAtomicAttack. Selection happens once atinitialize_async; execution is plain framework code.(context, technique) → (s, n)from persisted child rows after the run.Resumable via
scenario_result_id="..."— prior dispatch trails replay into the selector before the remainder runs.Notes
AtomicAttackper objective —atomic_attack_name = "{prefix}_{dataset}::{objective_sha[:12]}",display_group = dataset_namepreserves dataset grouping for reporting.BASELINE_ATTACK_POLICY = Enabled—prompt_sendingruns as the baseline comparison rather than as an adaptive technique.epsilon,pool_threshold,random_seed) live on the selector;max_attempts_per_objectiveis a scenario parameter.Updates since initial review
AttackStrategy) — execution and persistence are entirely the executor's responsibility viaSequentialAttack(depends on FEAT add StrategySequenceAttack compound attack primitive #1819). No custom envelope subclass, norun_asyncoverride.atomic_attack_identifier; the notebook readschild.get_attack_strategy_identifier().unique_namedirectly. NewMemoryInterface.get_attack_results(atomic_attack_eval_hashes=...)kwarg +compute_inner_attack_eval_hashhelper (regression-tested so predictor and executor never diverge).AdaptiveScenarioconstructs each factory's narrowedAttackScoringConfigsubtype (e.g.TAPAttackScoringConfig); incompatible techniques are skipped with a warning instead of failing the run.SeedAttackGroup.with_technique— deep-copies merged seeds so reusedseed_techniques don't leakprompt_group_idmutations.--request-timeout; poll endpoint usesread=Noneso a busy server doesn't fail the run;httpx.ReadTimeoutnow points at the flag instead of a bareError:.Testing
pytest tests/unit/{scenario/scenarios/adaptive,analytics,identifiers,memory,cli}— all green.