Strands Evals 深度解析:给 Agent 上评测,从打分到定位失败根因
Strands Evals 深度解析:给 Agent 上评测,从打分到定位失败根因
Agent 上线前最该补的一课,不是再加一个工具,而是先能证明它行。
给 Agent 做评测比给模型做评测麻烦得多。模型的评测是"输入一句话,看输出对不对",Agent 是"输入一个目标,它调了六个工具、跑了十二轮、中间还改过文件,最后交付了什么"。前者可以拿测试集打分,后者连"分"该怎么打都不直观——一个输出正确但绕了十圈的 Agent,和一个输出错误但过程漂亮得无可挑剔的 Agent,谁更值得上线?
Strands Evals 是 Strands 团队对这个问题的答案:上线前测量它,上线后观察它——给输出和轨迹打分,检测并诊断失败,探测不安全行为,还要模拟它在生产里会遇到的人和工具。
这篇不是 API 手册的搬运。我把官方文档里"为什么这么设计"的部分挑出来讲,重点放在三件事:四块拼图怎么拼、评测层级怎么选错就白干、以及从"0.3 分"到"哪个 span 坏了"这段路怎么走完。
本文提纲
- 一个评测由哪四块拼成
- 四个评测层级:选错层级,评测白做
- 评测器怎么选:六组 + 确定性检查
- Detectors:从"得了 0.3 分"到"为什么错"
- Simulators 与 Chaos testing:多轮对话与工具故障
- Red teaming:安全方向的对抗测试
- CLI:把评测塞进 CI
- 成本、缓存与失效边界
一个评测由哪四块拼成
Strands Evals 的骨架固定由四块组成,记住它们,剩下每一页文档都只是其中一块的细节。
- Experiment:你要运行的单元,持有一批 case 和要施加在它们身上的 evaluators。
- Case:一个输入,配上你期望拿回什么。它带着要发送的 prompt、可选的
expected_output,以及之后用来过滤和分组的metadata。 - Task:把一个 case 变成一个 result。它用 case 的输入跑你的 agent,返回 output,并且可选地返回 trajectory——模型响应与工具调用的有序记录。
- Evaluator:给一个 result 打分,返回分数、通过与否,以及做出这个判断的理由。
再加一块反向的拼图:Detector 不按标准打分,而是读一次失败的运行,告诉你哪里坏了、为什么。
最短的完整例子长这样:
from strands import Agent
from strands_evals import eval_task, Case, Experiment
from strands_evals.evaluators import OutputEvaluator
@eval_task()
def get_response():
return Agent(system_prompt="Answer accurately and concisely.")
cases = [
Case[str, str](
name="capital",
input="What is the capital of France?",
expected_output="Paris",
)
]
evaluator = OutputEvaluator(rubric="Score 1.0 if the answer is correct, else 0.0.")
report = Experiment[str, str](cases=cases, evaluators=[evaluator]).run_evaluations(get_response)
report.run_display()@eval_task 这个装饰器负责的是样板:telemetry 初始化、session 映射、结果归一化。你的函数可以什么都不接,也可以接一个 Case 参数做逐用例定制(比如某个 case 才挂 notebook 工具);返回 Agent 时装饰器自动用 case.input 调用它,返回 str 就直接当输出,返回 dict 则原样透传(至少要有一个 output 键)。
这层封装看着不起眼,却是评测能不能长期维护的分水岭:把"怎么跑 agent"和"怎么评"解耦之后,评测器才能单独迭代——后面讲结果缓存时会看到,这个解耦还能直接省钱。
四个评测层级:选错层级,评测白做
评测器之间最大的区别不是"检查什么",而是"能看到多少"。Strands Evals 把粒度分成四级,映射到 SDK 的 EvaluationLevel 枚举:
| 层级 | 范围 | 回答的问题 |
|---|---|---|
OUTPUT_LEVEL |
单条响应 | 这一个回答好不好? |
TOOL_LEVEL |
单次工具调用 | 工具选对了、参数对了吗? |
TRACE_LEVEL |
单轮对话 | 这一轮对不对、跑没跑题、安不安全? |
SESSION_LEVEL |
整段会话 | Agent 端到端达成用户目标了吗? |
为什么这件事值得单独拎出来讲?因为层级选错会得到看起来合理、实际上没用的结论。用输出级检查去评一个多轮任务,你只能知道最后那句话像不像样,永远不知道中间有没有跳过关键步骤;反过来,用会话级检查去评单轮问答,你付出的是完整轨迹的采集成本,换回来的信息量和输出级差不多。
会话级评测器(比如 GoalSuccessRateEvaluator)依赖完整 trajectory,这就是为什么说"轨迹感知的 task 要收集 spans"。而 OutputEvaluator、多模态和 skill 评测器、TrajectoryEvaluator、InteractionsEvaluator 这些不设 evaluation_level——它们直接拿到 agent 的输出或完整结果,文档里标的层级是它们的概念范围。
把不同层级混在一个 experiment 里,通常是故意的:一条便宜的输出检查,加一条读完整运行的轨迹检查。
评测器怎么选:六组 + 确定性检查
内置评测器按"你想检查什么"分成六组:
- Quality:helpfulness、faithfulness、correctness、coherence、relevance——回答是否有用、准确、连贯、切题,以及多轮轨迹是否站得住。
- Safety:有害内容、不恰当的拒答、刻板印象。
- Multimodal:判断"基于一张图片或一份文档"的回答。
- Agentic:工具使用、目标完成、失败后的行为——包括指令遵循、失败沟通、部分完成、恢复策略、工具选择准确率、工具参数准确率。
- Skill:装备了 skill 的 agent,有没有选对 skill、有没有按步骤走。这里有个细节:被拒绝的 skill load 不算"调用过",因为 agent 压根没拿到它。
- Deterministic:纯代码检查,链路里没有模型。
确定性评测器是这套体系里最容易被低估的一组。它们没有 LLM judge、快、免费、同输入必得同分,天然适合回归测试和 CI 门禁:
from strands_evals.evaluators import Equals, Contains, StartsWith, ToolCalled
Equals(value="Paris") # 等于期望值
Contains(value="Paris", case_sensitive=False) # 包含子串
StartsWith(value="The capital", case_sensitive=False)
ToolCalled(tool_name="calculator") # 轨迹里调用过某个工具Equals() 不传值时,会自动拿 case 的 expected_output 来比——这就是"先把确定性检查铺满,再用 LLM judge 补语义"的实用路径。
至于 LLM judge:默认用 Amazon Bedrock 上的 Claude,需要先配好 AWS 凭据和模型访问权限;judge 模型可以换成任意 Model 实例或模型 ID 字符串。
Detectors:从"得了 0.3 分"到"为什么错"
这是我认为 Strands Evals 里最有意思的部分,也是大多数评测框架缺失的一环。
只用评测器的世界是这样的:某个 case 拿了 0.3 分,然后你打开 trace,一行一行翻,试图找出是哪一步崩的。Detectors 把这个动作自动化了,而且明确区分了两件事——
- Evaluator 告诉你分数。
- Detector 告诉你诊断。
三个可用的检测器:
detect_failures:在执行的 trace 里找出语义层面的失败,输出失败所在的 span、类别、置信度和证据。类别大约二十种,涵盖幻觉、执行错误、工具误用、重复行为、编排错误等。analyze_root_cause:判断失败的根本原因,输出因果分析、传播影响、修复类型和建议。它对大会话有一个三层的降级策略(direct / pruned / chunked),避免一次性把整条会话塞给分析模型。diagnose_session:把失败检测和根因分析串成一条流水线跑完。
它们吃的是 Session 对象——和基于 trace 的评测器同一套格式,所以不需要额外的数据改造。输出也很具体:不是"你的 agent 有幻觉",而是"这个 span 是主要失败,后面那个是它的次生影响,建议改这段工具描述"。
对应的 CLI 子命令是 strands-evals diagnose,可以直接对着一份 Session JSON 跑。
Simulators 与 Chaos testing:多轮对话与工具故障
静态评测器的根本局限在于:一个 prompt 只给你一轮。真实 agent 面对的是来回拉扯的对话。
ActorSimulator 扮演模拟用户(或任何对话参与者),ToolSimulator 顶替 agent 调用的工具。两个模拟器合起来,让你在没有真人、也没有真实 API 的情况下评测多轮行为和目标达成情况——而且不需要预先写好剧本,模拟器会根据 agent 的实际表现动态调整回应。
Chaos testing 则是另一个方向的"不友好输入":注入工具故障。ChaosPlugin、ChaosCase、ChaosExperiment 三个组件可以在不改 agent 代码的前提下,注入工具超时、网络错误、校验错误,以及篡改响应(截断字段、删掉数据、污染取值)。
它能回答的那些问题,恰好是生产事故复盘时最想知道的:
- 工具挂了,agent 会老实告诉用户,还是编一个结果?
- 部分工具失败时,它还能完成多少目标?
- 哪些工具是单点故障,哪些它有本事绕过去?
"哪些工具是单点故障"这个问题我特别喜欢——常规评测永远测不出来,因为你造不出工具失败的场景。
Red teaming:安全方向的对抗测试
评测器测的是"输入友好时它做得对不对",红队测的是"输入充满敌意时它还安不安全":会不会泄露 system prompt、会不会把该保密的数据吐出来、会不会生成有害内容、会不会越权调用工具。一次攻破(breach)就是一次失败。
用法是把对抗用例和一种或多种攻击策略组装进 RedTeamExperiment,跑完从报告里读攻破记录。每个攻击由 AttackStrategy 驱动——技术取自已发表的越狱研究——再由 LLM judge 判定是否得手。
但这里有三条必须说清楚的前提,文档本身标得很重:
- **它还在
strands_evals.experimental.redteam下面,API 仍在演进。**import 路径、分数、报告结构都可能在版本之间变化。 - **别把它的绝对数字当成硬门禁。**文档的说法是"把绝对数字当方向性参考,如果要用它卡 CI,就把 SDK 版本钉死"。
- **只在你被授权测试的系统上跑。**这些策略生成的是诱导模型越界的对抗 prompt,而且攻破记录里可能包含被诱导出来的内容,报告要按敏感材料处理。
CLI:把评测塞进 CI
装 strands-agents-evals 的时候会一起装上 strands-evals 这个命令行工具。它是公开 Python API 的一层薄封装,官方强调的重点是"CI 里的行为和你在 Python 脚本里得到的一致"。六个子命令:
| 命令 | 用途 |
|---|---|
strands-evals run |
跑一个 Experiment(对着 --agent 工厂或 --task 可调用对象),或者用 --input + 评测参数跑单个临时用例 |
strands-evals validate |
对序列化后的 Experiment JSON 做 schema 校验,适合当作 run 之前的 CI 门禁 |
strands-evals report |
用 Rich 渲染已有的报告 JSON,或者直接输出 JSON |
strands-evals diagnose |
对一份 Session JSON 跑失败检测、根因分析或完整诊断流水线 |
strands-evals generate |
用 ExperimentGenerator 从自由文本 --context 或已有 experiment 文件合成一个实验 |
strands-evals fetch |
从 cloudwatch、langfuse、opensearch 拉取某个会话的 trace,产出可直接管道给 diagnose 的 Session JSON |
最省事的冒烟检查是一行,连实验文件都不用写:
pip install strands-agents-evals
strands-evals run \
--input "What is the capital of France?" \
--expected-output "Paris" \
--agent my_agent:build_agentrun 有两条互斥的模式:experiment 文件模式,和上面这种 ad-hoc 模式(--input 加至少一个 --evaluator / --expected-output / --rubric)。argparse 会直接拒绝把两者混用。
有一个设计约束值得单独记:--agent 要的是一个"每次调用都返回全新 agent"的工厂,形如 my_pkg/agents.py:build_agent。如果你传一个已经构造好的 agent 实例或 Agent 子类,CLI 会拒绝——因为会话状态会跨用例泄漏,前一个 case 的上下文会污染后一个的评测结果。这个坑自己写评测脚本时一样会踩,只是不会有人拦你。
工厂还可以接收一个 Case 参数做逐用例定制;如果任务形态不标准(多轮循环、自定义 session 映射),--task 是逃生口,接收 Callable[[Case], dict|str],代价是 agent 实例化由你自己负责,--trace-attributes 会变成空操作并打一条警告。
批量造测试用例走 generate:
strands-evals generate \
--context "$(cat tools.txt)" \
--task-description "Calculation and time-aware assistant" \
--num-cases 10 \
--num-topics 3 \
--evaluator TrajectoryEvaluator \
-o experiments/generated.json--num-cases 默认 5,--evaluator 在 context 模式下三选一(OutputEvaluator、TrajectoryEvaluator、InteractionsEvaluator),不传则生成一个带占位评测器的实验。--num-topics 是最容易被忽略但很实际的一个开关:它把生成拆到 N 个主题专属的 prompt 上,让用例分散在 agent 真正会做的事情上,而不是全挤在同一个场景里。
--agent、--task、--evaluator、--custom-evaluator 统一用 MODULE:ATTR 引用约定,和 pytest --pyargs、gunicorn、inspect-ai 一致;当前工作目录会被加进 sys.path,所以同目录下的 agent.py 直接写 agent:build_agent 就行,不需要 PYTHONPATH=.。
成本、缓存与失效边界
评测最贵的部分永远是跑 agent,不是打分。Strands Evals 的对策是结果缓存:
from strands_evals import Case, Experiment, LocalFileTaskResultStore
store = LocalFileTaskResultStore("./cached_results")
experiment = Experiment(cases=cases, evaluators=[OutputEvaluator(rubric="Score 1.0 if correct.")])
# 第一次:真的跑 agent,并缓存结果
report = await experiment.run_evaluations_async(my_task, evaluation_data_store=store)
# 第二次:直接读缓存,跳过 agent 执行
report = await experiment.run_evaluations_async(my_task, evaluation_data_store=store)机制是按 case.name 做键查缓存:命中就用缓存的 EvaluationData,不命中才跑任务并把结果写回 store,评测器始终对着(缓存或新鲜的)数据进行。**所以你可以反复调 rubric、换 judge 模型,而不必每次都烧一遍 agent 的 token。**前提是所有 case 的 name 必须唯一且非 None——实验会在执行前校验这一点,否则缓存会串味。这一点在自己搭评测框架时经常被忽略,尤其是用 Case(name=...) 随手起名"test"的时候。
几条失效边界,按踩坑概率排序:
- 缓存只有在你相信 agent 的确定性时才安全。任务本来就不确定的场景,缓存下来的是一次抽样结果,反复用它迭代评测器会让你对分布的判断越来越偏。文档把适用场景讲得很直白:agent 调用贵、慢、不确定的时候用它——正是这三件事同时成立时,缓存的价值最大,风险也最大。
- 单轮评测器只看到最近一轮。
HelpfulnessEvaluator是TRACE_LEVEL,评的是最近一次 agent 回复及其上下文,不要拿它替代会话级的目标达成检查。 - **默认 judge 依赖 Bedrock。**没配好 AWS 凭据或没开模型访问权限,第一步就卡住。
- **实验性 API 不适合当硬门禁。**红队那部分尤其如此。
- **确定性检查铺得越多,CI 越稳。**LLM judge 会有波动,
Equals/Contains/ToolCalled不会。
参考文档与链接
- Strands Evals 官方总览 — 本文主要来源
- Quickstart — 安装、凭据配置与第一个实验
- How evaluation works — 四块拼图与四个评测层级的原始定义
- Evaluators 总览 — 六组评测器与
EvaluationLevel枚举 - Deterministic evaluators —
Equals/Contains/ToolCalled等纯代码检查 - Detectors —
detect_failures、analyze_root_cause、diagnose_session - Simulators —
ActorSimulator与ToolSimulator - Chaos testing — 用
ChaosPlugin注入工具故障 - Red teaming(实验性) —
RedTeamExperiment与授权提醒 - Task decorator —
@eval_task的四种返回形态与逐用例定制 - Result caching —
LocalFileTaskResultStore与缓存语义 - Evaluate with AI(Eval SOP) — 让 AI 助手驱动 Plan/Data/Eval/Report 四阶段
- CLI 总览 / run / generate — 六个子命令与
MODULE:ATTR约定 - GitHub: strands-agents/evals — 223 stars,Python 实现的评测框架
- 上一篇:Strands harness 深度解析 — harness 负责稳,Evals 负责证明它稳
你的 Agent 现在靠什么判断"这版比上版好"?评论区聊聊你的评测方式。觉得有用点个赞,让更多在上线前卡住的人看到。
作者: itech001 来源: 公众号:AI人工智能时代(the-ai-era) 网站: https://www.theaiera.top/ 关注每日最新AI新闻和技术博客,主页有更多的文章的AI 技术参考:https://www.theaiera.top
本文首发于 AI人工智能时代,转载请注明出处。