第 13 章:评测 Agent
第 13 章:评测 Agent
关于本课
本课程的视频是当时的一个快照。Strands 一直在积极开发,所以本页展示的代码反映的是最新的写法,但视频里讲的概念依然成立。有疑问时,以代码为准。
本课代码:samples/13-evals
为什么传统测试不管用了
Agent 是非确定性的。同样的输入,可能给出不同(但同样合理)的回答、不同的 Tool 调用顺序,或者不同的详细程度。assert output == expected 这种写法行不通。你需要的是 LLM-as-a-judge 打分、轨迹校验、多轮模拟,再加上确定性的断言,把它们组合成能抓住回归的 eval 套件。
Strands Evals 是一个独立的包:
pip install strands-agents-evals评测的基本结构
一次评测由三部分组成:cases(输入加期望)、evaluators(怎么打分),以及一个跑 Agent 并返回评测器所需数据的 task 函数。下面这次评测针对前几课里的客服 Agent,既看最终输出,也看它调用了哪些 Tool、顺序如何:
from strands import Agent, AgentSkills
from strands_evals import Case, Experiment
from strands_evals.evaluators import OutputEvaluator, TrajectoryEvaluator
from strands_evals.extractors import tools_use_extractor
from strands_evals.types import TaskOutput
def create_agent():
return Agent(
tools=[lookup_customer, get_order_history, process_refund],
plugins=[AgentSkills(skills=["./skills"]), RefundWorkflowHandler()],
system_prompt=SYSTEM_PROMPT,
callback_handler=None,
)
# Trajectory evaluator: validates tool call ordering
trajectory_evaluator = TrajectoryEvaluator(
rubric="Score 1.0 if workflow sequence matches expected trajectory...",
include_inputs=True,
)
# Output evaluator: scores response quality
output_evaluator = OutputEvaluator(
rubric="Score 1.0 if professional, empathetic, addresses the concern...",
)
def get_response(case: Case) -> TaskOutput:
agent = create_agent()
response = agent(case.input)
trajectory = tools_use_extractor.extract_agent_tools_used_from_messages(agent.messages)
return TaskOutput(output=str(response), trajectory=trajectory)
cases = [
Case(
name="refund-full-workflow",
input="I'm customer C-1001. Refund my headphones order ORD-5521 please.",
expected_output="Refund processed with 3-5 day timeline.",
expected_trajectory=["lookup_customer", "get_order_history", "process_refund"],
),
Case(
name="missing-customer-id",
input="I want a refund.",
expected_output="Ask for customer ID before proceeding.",
expected_trajectory=["lookup_customer"],
),
]
experiment = Experiment(
cases=cases,
evaluators=[trajectory_evaluator, output_evaluator],
)
report = experiment.run_evaluations(task=get_response)
评测类型
| 类型 | 检查什么 |
|---|---|
| 输出评测 | 最终回答的质量、语气、正确性 |
| 轨迹评测 | Agent 是否按正确的 Tool 顺序执行 |
| 模拟 | 跨多轮的持续压力测试 |
| 确定性 | JSON schema、回答长度、格式。不需要 LLM |
| Chaos 测试 | Tool 失败时 Agent 能不能恢复? |
| 红队测试 | Agent 能不能扛住对抗性攻击? |
| Experiment Generator | 根据 Agent 的能力自动生成测试用例 |
Chaos 测试
生产环境不是理想状态。Tool 会超时、API 会挂掉、返回结果会被截断。Chaos 测试就是验证你的 Agent 能不能优雅地处理这些失败。ChaosPlugin 会拦截 Tool 调用,注入可配置的故障。
下面这段评测的是一个 Pokemon 队伍顾问 Agent(完整 Agent 在 Strands samples 仓库里):
from strands import Agent
from strands_evals.chaos import ChaosCase, ChaosExperiment, ChaosPlugin
from strands_evals.chaos.effects import Timeout, NetworkError, TruncateFields
from strands_evals.evaluators.deterministic import Contains
chaos = ChaosPlugin()
agent = Agent(
tools=[get_pokemon, get_move],
context_manager="auto",
plugins=[chaos],
)
# Define failure scenarios
effect_maps = {
"api_timeout": {"tool_effects": {"get_move": [Timeout()]}},
"api_down": {"tool_effects": {"get_pokemon": [NetworkError()]}},
"partial_response": {"tool_effects": {"get_pokemon": [TruncateFields(max_length=200)]}},
}
# Expand cases across all failure modes (+ a no-failure baseline)
chaos_cases = ChaosCase.expand(
[Case(name="earthquake_ice_beam", input="Which Pokemon learns both Earthquake and Ice Beam with the highest Attack?")],
effect_maps,
include_no_effect_baseline=True,
)
experiment = ChaosExperiment(
cases=chaos_cases,
evaluators=[Contains(value="rampardos", case_sensitive=False, name="correct_answer")],
)
report = experiment.run_evaluations(task=lambda case: {"output": str(agent(case.input))})📂 pokemon-team-advisor(完整 Agent 在 Strands samples 仓库里)
get_move 超时的时候,Agent 还能恢复吗?get_pokemon 返回网络错误的时候呢?Chaos 测试让你在开发阶段就搞清楚,而不是等客户先撞上。
红队测试
红队测试检验的是你的 Agent 能不能顶住对抗性压力:jailbreak、数据外泄尝试、越权行为。生成器会读你的 Agent 有哪些 Tool、system prompt 写了什么,据此产出有针对性的攻击;strategy 则决定这些攻击在多轮对话里怎么逐步升级。
from strands_evals.experimental.redteam import RedTeamExperiment
from strands_evals.experimental.redteam.generators.adversarial import AdversarialCaseGenerator
from strands_evals.experimental.redteam.strategies import CrescendoStrategy
cases = AdversarialCaseGenerator().generate_cases(
agent=agent,
risk_categories=["data_exfiltration", "excessive_agency"],
num_cases=3,
)
experiment = RedTeamExperiment(
cases=cases,
agent_factory=create_agent, # fresh agent per case so attacks don't bleed across runs
attack_strategies=[CrescendoStrategy()],
)
report = experiment.run_evaluations()红队测试确认你的防护栏在对抗条件下依然有效,而不只是在一切顺利的输入下有效。
最佳实践
- 评测多轮交互,而不只是单轮
- 把 LLM 评委和确定性断言结合起来
- 把 eval 接进 CI/CD,自动抓回归
- 出现新的失败模式时,同步扩充你的 eval 套件
- 评测用的 Model 和生成用的 Model 分开
- 上线前用 chaos 测试验证韧性
- 跑红队实验来验证防护栏