Repository navigation
Conversation
|
@strandly-the-agent review the changes |
strandly-the-agent
left a comment
There was a problem hiding this comment.
Approve (reviewed only the top commit 5f06cb0, i.e. pr-415..pr-426). The move of agent_factory= to the run methods is correct and well-tested; two 🟡 doc-level items, nothing blocking.
🟡 SKILL.md still teaches the deprecated constructor path. SKILL.md:397-404 builds RedTeamExperiment(cases=cases, agent=target, ...) + experiment.run_evaluations(), and SKILL.md:408 says "for parallel runs pass agent_factory= instead of agent=" — both now warn / are wrong as of this commit, and this file is what an agent reads first. (Not in the diff, so no inline comment — suggested text below.)
🟡 Deprecation message steers sequential users onto lambda: agent, which loses the state rewind — inline on experiment.py:30.
✅ Verified, Questions, suggested SKILL.md text, Appendix
Verified at 5f06cb0
python -m pytest -q tests/strands_evals/experimental/redteam -W error::DeprecationWarning:strands_evals→ 367 passed (supported path emits no DeprecationWarning; the deprecated-path tests usepytest.warns).ruff check/ruff format --checkclean on changed files;mypy -p srcclean for the redteam package (only unrelated optional-depimport-not-found). CI Lint green.- Precedence (
experiment.py:243-246→task.py:56): run-time factory > ctor factory > ctor agent; ctoragent=+max_workers>1still fails fast with theTypeErrorfromtask.py:169, as before. stacklevel=2on all four warning sites attributes to the caller's line (checked empirically by the reviewer pass).from_file/from_dictnever warns spuriously: basefrom_dictis driven withcases=[], real cases areRedTeamCase.model_validate-d (experiment.py:310-314).- The kept
from_evaluation_reportrebuild is load-bearing: replacingexperiment.py:235withreturn reportfailstest_overall_score_excludes_errored_attacks;calculate_overall_score(scores, detailed_results)can't do it because error rows havedetailed_results=[], same as a successful evaluator with no outputs. - Overrides stay substitutable with base
run_evaluations/run_evaluations_async(only widentaskto| None, add keyword-onlyagent_factory), so dropping# type: ignore[override]is right. Class docstring example matchesAdversarialCaseGenerator.generate_cases(*, agent=...).
Questions (non-blocking)
- Sequential run with a factory: should
_build_per_case_task_fnsnapshot each freshly built target before the attack soreset()always has a baseline, or is "new object per call" the contract you want to enforce (e.g. warn whenmake_target()returns the sameAgent/MultiAgentBasetwice)? - Is the
report_cls = RedTeamReport+ rebuild double-build a stop-gap until the base passesreasonstocalculate_overall_score? A one-line note onreport_clspointing at the rebuild would stop someone "simplifying" it away. - The plain-
Casedeprecation is a tag-along to anagent_factoryPR — intended here, or for Part 2 (#416)?
Suggested SKILL.md:397-408
experiment = RedTeamExperiment(
cases=cases,
attack_strategies=[CrescendoStrategy(max_turns=10), PairStrategy(max_turns=8)],
evaluators=[AttackSuccessEvaluator(model=judge_model, pass_threshold=0.3)],
model=judge_model,
)
report = experiment.run_evaluations(agent_factory=lambda: Agent(model=..., system_prompt=..., tools=[...]))
report.display()Targets accepted: ... Pass
agent_factory=torun_evaluations()/run_evaluations_async(); it must build a new target per call (Strands clients carry non-deepcopyable state, so the experiment never copies one). The constructor'sagent=/agent_factory=are deprecated.
Reading order: experiment.py (_default_task, run methods, ctor warnings) → test_experiment.py new tests → README/AGENTS.md.
Appendix — non-blocking (3)
experiment.py:246— error says "passed to run_evaluations()" even when raised fromrun_evaluations_async(); "to the run method" fits both.experiment.py:43— "The experiment holds no live target" is the post-deprecation end state; todayagent=still parks one onself._agent.test_experiment.py:301asserts the setters warn and round-trip, but no test runs via a setter-assigned target (the README's "still works" claim). Low risk — same_agent/_agent_factorythe covered ctor paths use.
Pre-existing, not for this PR: from_evaluation_report drops diagnoses/recommendations (a no-op here since the ctor never forwards diagnosis_config).
| _AGENT_DEPRECATION = ( | ||
| "Attaching `{name}` to RedTeamExperiment is deprecated and will be removed in a future release. " | ||
| "Pass `agent_factory=` to `run_evaluations()` / `run_evaluations_async()` instead." | ||
| ) |
There was a problem hiding this comment.
🟡 The obvious migration, agent_factory=lambda: agent, silently loses the per-case state rewind. Any non-None factory routes to _build_per_case_task_fn (task.py:56), which builds the session with baseline=None, so reset() is just self._agent.messages.clear() (target_session.py:177-180). The deprecated agent= path restored a take_snapshot(preset="session") per case — state, conversation-manager state, interrupts included. A case-1 attack that flips agent.state now leaks into case 2 with no error; same holds for exp.agent_factory = lambda: agent today, but this message is what sends every sequential user there, and the PR body notes the snapshot machinery goes away with the deprecated path.
Suggestion: say what the factory must do (the README's "builds a fresh target each time" line doesn't reach users who only see the warning):
| _AGENT_DEPRECATION = ( | |
| "Attaching `{name}` to RedTeamExperiment is deprecated and will be removed in a future release. " | |
| "Pass `agent_factory=` to `run_evaluations()` / `run_evaluations_async()` instead." | |
| ) | |
| _AGENT_DEPRECATION = ( | |
| "Attaching `{name}` to RedTeamExperiment is deprecated and will be removed in a future release. " | |
| "Pass `agent_factory=` to `run_evaluations()` / `run_evaluations_async()` instead. The factory must build a " | |
| "new target on every call; returning the same instance only clears its messages between cases, not its state." | |
| ) |
Description
DO NOT MERGE
Based on Part 1 (#415). This branch is stacked on
handle_run_data, so until #415 merges, the diff also shows its commits. Only the top commit belongs to this PR. I'll rebase ontomainonce #415 lands.Today,
RedTeamExperimentkeeps the live target (agent/agent_factory) on the experiment. No otherExperimentholds a runtime object, and it can't be serialized, so it has to be reattached afterfrom_file. This PR movesagent_factoryto the run methods, so the experiment holds only cases, strategies and evaluators. A reloaded suite runs without reattaching anything:Changes
run_evaluations()/run_evaluations_async()take a keyword-onlyagent_factory=. Passing bothtaskandagent_factoryraises aValueError. Keyword-only stops a factory passed by position from being silently treated as a task, since both are callables.RedTeamExperimentbindsExperiment[InputT, OutputT, RedTeamReport]and setsreport_cls = RedTeamReport, so the run methods no longer need# type: ignore[override].RedTeamReport.from_evaluation_report(report)call has to stay. The basecalculate_overall_scorenever seesreasons, and error rows have emptydetailed_results, so without that call errored attacks would count as 0.0 inoverall_score. That would partly undo fix(redteam): separate errored attacks from breaches in ASR #296. A new test covers this.DeprecationWarningfor one minor version, still working:agent=/agent_factory=and theexp.agent/exp.agent_factorysetters. A run-time factory takes precedence over both. The sharedagent=target only ever worked for sequential runs, and it's the only reason the task builder takes baseline snapshots.Caseinputs, in favour ofRedTeamCase.Follow-up after the deprecation cycle: remove the constructor arguments and setters, type
casesaslist[RedTeamCase], and drop theisinstancecheck arounditem.strategyfrom Part 2 (#416).Related Issues
overall_scorerule for errored attacks that this PR keepsDocumentation PR
N/A. The Strands docs red-teaming guide may still show
RedTeamExperiment(agent=...); I'll open a docs PR if it does.Type of Change
New feature. It also deprecates the constructor's
agent=/agent_factory=and plainCaseinputs. Nothing breaks in this release; removing them later will be the breaking change.Testing
New tests in
test_experiment.py:task+agent_factoryis rejected;DeprecationWarning;report_clsisRedTeamReport;overall_score.Tests that weren't about the old path now pass the factory to the run method. That includes
test_redteam_e2e.py.I ran
hatch run prepareChecklist
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.