第 13 章 StrandsEvals评测

第 13 章:评测 Agent

第 13 章:评测 Agent

在 YouTube 观看

关于本课

本课程的视频是当时的一个快照。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)

📂 customer_service_eval.py

评测类型

类型 检查什么
输出评测 最终回答的质量、语气、正确性
轨迹评测 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 测试验证韧性
  • 跑红队实验来验证防护栏

参考资源