0x00 前言
“这是《AI应用测试方法》系列的第四篇。前三篇分别讲了通用断言体系、评分机制、以及 RAG 评测,本篇接着讲另一种主流的 LLM 应用形态(Agent 系统)的评测。
Agent 是现在 LLM 应用里增长最快的一类形态,从代码 Agent(Cursor、Cline、Devin),到运维 Agent、客服 Agent、研究 Agent,再到 multi-agent 工作流编排(LangGraph、AutoGen、CrewAI)。它和普通问答、RAG 的最大区别是,Agent 除了产生最终答案,还产生一连串动作。
这个区别直接改变了评测对象。前两类系统看最终输出就够了,答得对 / 答得不对、检索到 / 没检索到。Agent 系统看最终输出远远不够。模型可能把任务"做对了",但中间调了 18 次工具、绕了 3 个圈、烧了 100 倍的 token;也可能"做错了",但其实只差最后一步、前面整条思路是对的。只看终点,这两种情况都没法定位。
所以 Agent 评测的核心动作变成了评测整条执行轨迹。这一篇先讲清楚从评输出到评轨迹意味着什么,然后拆开工具调用、执行轨迹、任务完成度三个核心维度,看 LangSmith / DeepEval Agentic / Mosaic AI / promptfoo 各家怎么落地,最后过一遍常见的翻车模式(tool hallucination、死循环、上下文漂移、工具组合错误),看每种故障对应哪个指标的下降。
本文基于对 LangSmith、DeepEval Agentic Metrics、Databricks Mosaic AI Agent Evaluation、promptfoo 这四个主流 Agent 评测框架的文档与源码分析,结合 AgentBench(2023)、ToolBench(2023)、ToolEmu(2023)、SWE-bench(2024)、τ-bench(2024)等 Agent 评测基准的论文整理而来。
0x01 Agent 评测和 RAG 评测的根本差别
普通 QA 评测看一条 (question, answer) 对。RAG 评测看一组 (question, retrieved_context, answer) 三元组。Agent 评测看的是一棵决策树。
任务: 帮我订一张明天上海到北京的高铁
├── 步骤 1: 调 search_train(from=上海, to=北京, date=2026-05-29)
│ └── 返回 18 个班次
├── 步骤 2: 调 filter_by_time(arrival_before=18:00)
│ └── 返回 7 个班次
├── 步骤 3: 调 get_user_preference(user_id=...)
│ └── 返回 "靠窗座位"
├── 步骤 4: 调 book_train(train_id=G2, seat=window)
│ └── 成功,返回订单号
└── 输出: "已为你预订 G2 班次,10:00-14:30,靠窗座位"
这棵树上要评的事,普通 QA 和 RAG 评测都不覆盖。
1. 工具选对了吗?
模型应该调 search_train,但它调了 search_flight,任务方向就错了。模型应该调 filter_by_time,但它跳过这步直接订了第一个班次,满足度就低。
2. 工具参数填对了吗?
模型调 search_train(from=上海, to=北京) 是对的。如果填成 search_train(from=北京, to=上海),参数错了,结果对了反而更危险(用户拿到了反向的票)。
3. 调用顺序对吗?
正确流程是 search → filter → preference → book。如果模型先 book 再 search 就乱套,book 时用的车次根本没经过过滤。
4. 中间步骤的中间结果用上了吗?
get_user_preference 返回了"靠窗",book_train 应该把 seat=window 作为参数传进去。如果模型获取了偏好但没传到下游,信息断链,最终结果可能不符合用户预期。
5. 最终任务完成了吗?
订单到底有没有成功创建?这件事不能只看最后一句"已为你预订 G2 班次",模型可能在没真正调 book_train 的情况下编出这句话。
这五个问题 RAG 评测的四个 RAGAS 指标全部覆盖不到。Agent 评测要拆出独立的指标体系。
0x02 Agent 评测的三个核心维度
主流 Agent 评测框架(LangSmith、DeepEval、Mosaic AI、promptfoo)虽然 API 各不相同,但分解的维度高度收敛,都能归到三个大类。
下面拆开讲。
Tool Correctness(工具调用正确性)
这是最基础,也是通常最先考虑的指标。它分三层。
第一层:工具名(tool name)
模型应该调 book_train,实际调了 search_train,这就是 tool name 错。这一层的判定很简单,把模型实际调用的工具名和期望的工具名对比,要么匹配、要么不匹配。
工程上有个细节,期望工具不一定是单一的。一个任务可能有多种合法路径,比如"查询订单"既可以调 get_order_by_id 也可以调 search_orders(filter=user),两条路径都是对的。所以 tool name 评测通常允许"集合匹配",只要实际调用的工具属于预期工具集中的某一个就算通过。
第二层:工具参数(tool arguments)
这一层最容易被忽略也最容易出问题。模型调 book_train(train_id=G2, seat=window),工具名对,但参数 seat 应该是 aisle(用户偏好),那也是错。
参数评测有几种做法。
完全匹配:实际参数和期望参数 dict 完全相同。最严格但实操中误判多,参数顺序、可选字段、空字段都会让评测炸掉。 关键字段匹配:只比对几个核心字段(如 train_id、date),其他可选字段不管。需要为每个工具配置"必检字段清单"。 LLM judge 判定:让另一个 LLM 看用户意图、看模型实际填的参数,判断"参数填得合理吗"。最灵活但最贵。
DeepEval 的 ToolCorrectnessMetric 默认做部分匹配(按期望工具的命中比例打分,参数 dict 递归部分比对),should_exact_match 开启后才切换为完全匹配;should_consider_ordering 和 evaluation_params 分别控制顺序敏感度和附加比对字段。LangSmith 的 trajectory evaluator 倾向于让用户自定义 judge prompt。
第三层:调用次数(call count)
同一个工具可能被多次调用,一个翻译任务可能调 10 次 translate_segment。这层关注的是"调用次数是否合理"。次数过多说明 Agent 在重复劳动 / 死循环;次数过少说明有步骤跳跃的问题。
Trajectory Quality(轨迹质量)
工具调用单点都对,不代表整条轨迹对。轨迹评测看的是调用序列在更高层级是否合理。
1. 顺序正确性
期望顺序是 [search, filter, get_pref, book],实际顺序是 [search, book, filter, get_pref],四个工具都调了,但顺序乱了,结果就不可信。
判定方式:用编辑距离(edit distance)或最长公共子序列(LCS)算实际轨迹和期望轨迹的接近度,归一化到 [0, 1]。
2. 步骤完整性
期望的步骤是 4 步,实际只走了 2 步(漏了 filter 和 get_pref,直接 search → book),任务可能也"完成"了,但跳过了关键校验步骤。
3. 没有冗余 / 没有死循环
实际轨迹是 [search, search, search, search, book],重复调用 search 4 次,要么是 prompt 没收敛要么是模型在打转。这种情况要在评测里专门标记。
4. 信息流贯通
A 步骤的输出是不是在 B 步骤被用上了?这需要把 trace 解析成一张数据流图,然后判断"step 3 的 train_id 参数是不是来自 step 1 的输出"。这个评测维度很多框架还没标准化,通常需要团队自己实现一层 trace 解析。
Task Completion(任务完成度)
最终用户的目标达成了没有,这是 Agent 评测最终要回答的问题,但它的定义和判定都比看起来难。
判定方式 1:终态检查(state-based)
如果 Agent 操作的是有状态系统(数据库、文件系统、API),可以直接查终态。比如让 Agent "把所有订单状态从 pending 改为 confirmed",评测时跑 SELECT count(*) FROM orders WHERE status='confirmed' 即可判断。
这种方式最可靠,但只适用于"操作真实环境"的 Agent。在沙箱里运行的代码 Agent、纯对话 Agent 用不了。
判定方式 2:LLM judge 终态判定
让另一个 LLM 看任务描述 + 完整 trajectory + Agent 的最终输出,判断"任务完成了吗"。这是目前最通用的做法。但 judge prompt 要写得很具体,只问"完成了吗"经常拿到含糊的 yes,要列出具体的判定 checklist。
DeepEval 的 TaskCompletionMetric 用的就是 judge 模式,prompt 模板里强制要求列出"用户的具体诉求"和"Agent 是否逐一回应"。
判定方式 3:单元测试式判定
代码 Agent 评测的金标准(SWE-bench、HumanEval 都是这种)。任务有明确的可执行测试用例,Agent 的输出(生成的代码 / 修改的文件)跑测试,pass = 完成、fail = 没完成。这种方式的客观性最高,但只能用在能写测试的领域。
0x03 工具调用评测的细节
工具调用评测看起来简单,比对名字和参数,但实操里有很多坑。这一节展开几个最常见的细节。
多路径任务的处理
很多任务有多条合法路径。比如"今天我能买到上海到北京的高铁吗",模型可以走两条路。
路径 A:调 search_train(from=上海, to=北京, date=今天),看返回结果。路径 B:调 get_route(上海→北京)+ 调check_availability(date=今天),组合判断。
两条路径都对。但单一"期望工具列表"会把路径 B 判错。
工程做法是把“期望工具”写成 OR 关系的集合,例如 expected_tools = {{search_train}} OR {{get_route, check_availability}}。promptfoo 的内置断言 trajectory:tool-used 直接支持这种集合匹配。如果框架不支持,团队可以在 judge prompt 里把多条合法路径列出来,让 LLM 判定。
参数语义匹配 vs 字面匹配
模型调 search_train(from="上海", to="北京") 和 search_train(from="Shanghai", to="Beijing"),字面不同,语义相同。
字面匹配会把后者判错。这种情况下用 LLM judge 做语义匹配更稳,judge prompt 里强调"参数语义是否一致",让 judge 自己处理别名 / 翻译 / 缩写问题。
用 LLM judge 做参数判定时,一定要把工具的 schema 也喂给 judge。否则 judge 不知道哪些参数是必填、哪些是默认值。一个常见的翻车场景是模型漏填了一个有默认值的可选参数,LLM judge 不知道这个参数有默认值,判错了。
时序参数的特殊处理
参数里带时间的工具调用(search_train(date=2026-05-29))特别容易出问题。
用户说"明天",期望参数是 2026-05-29,模型实际填了2026-05-30,时区错了用户说"下周三",期望是 2026-06-03,模型填了2026-06-04,日期算错了用户没说日期,模型自己默认填了今天,不一定是用户想要的
时序参数要在 judge prompt 里专门提醒"判断日期/时间参数时考虑相对时间的解析"。这是 Agent 评测里 false negative 最高的一类。
0x04 轨迹评测:从 trajectory match 到 step-wise reward
轨迹评测的难度比工具调用评测高一个量级。
Trajectory Match:最直接的做法
把期望轨迹和实际轨迹都表示成工具调用序列。
期望: [search_train, filter_by_time, get_user_preference, book_train]
实际: [search_train, get_user_preference, filter_by_time, book_train]
算两个序列的相似度。常见的算法有四种。
完全匹配:序列完全一致才算对。最严格,但 Agent 几乎从不能 100% 复刻期望路径。 集合匹配:只看用了哪些工具,不看顺序。最宽松,遗漏关键步骤检测不出来。 编辑距离:算把实际序列变成期望序列需要多少次插入/删除/替换。归一化到 [0, 1]。 LCS:算两个序列的最长公共子序列长度,再除以期望序列长度。
工程上推荐 LCS,它对"顺序略有不同但关键步骤都在"的情况打分合理,对"漏关键步骤"惩罚到位。
Step-wise Reward:更细粒度的做法
trajectory match 的问题是它把整条轨迹打一个分。如果要训练 / 调优 Agent,更有用的是给每一步单独打分。
步骤 1 (search_train): reward = 1.0 // 正确
步骤 2 (get_user_preference): reward = 0.3 // 顺序提前了,但工具本身是对的
步骤 3 (filter_by_time): reward = 0.8 // 应该早调,但调了就比不调好
步骤 4 (book_train): reward = 1.0 // 最终订成了
step-wise reward 的设计很难,简单的 0/1 容易但信号弱,复杂的连续打分需要 reward model 或 LLM judge 介入,工程负担很重。学术上 Process Reward Models (PRM) 这一方向的研究就是为了解决这个问题,但目前工业落地还少。
实操上,日常回归用 trajectory match 就够了,step-wise reward 留给训练 / RL fine-tune 阶段。
死循环检测
Agent 死循环是线上最容易出事的一类故障,它不一定输出错误,但消耗的 token 是正常情况的几十倍。检测死循环有一个简单做法(通用规则化检测,不依赖任何评测框架)。
def detect_loop(trajectory, max_repeat=3):
"""检测是否有同一个工具+参数组合连续重复 max_repeat 次以上"""
last_call = None
repeat_count = 0
for call in trajectory:
if call == last_call:
repeat_count += 1
if repeat_count >= max_repeat:
return True
else:
repeat_count = 0
last_call = call
return False
trajectory 是统一格式的步骤列表,call == last_call 比较的是工具名 + 参数 dict 是否完全相同。这段逻辑可以塞到任何评测框架的前置 hook 里,promptfoo 的 transform、DeepEval 的自定义 metric、LangSmith 的 evaluator 都能调用。
这个检测应该作为所有 Agent 评测的硬约束,一旦触发,整个 case 标记为 fail,不管最终输出是什么。
0x05 主流框架怎么落地
三个核心指标在主流框架里的实现各有取舍。
LangSmith Agent Evaluation
LangSmith 是 LangChain 团队的官方评测平台,对 LangGraph Agent 的支持最完整。
LangSmith 写法(自定义评估器):
from langsmith.evaluation import evaluate
def trajectory_evaluator(run, example):
actual_tools = [s.name for s in run.outputs["trajectory"]]
expected_tools = example.outputs["expected_tools"]
return {
"key": "trajectory_match",
"score": lcs_similarity(actual_tools, expected_tools),
}
results = evaluate(
target_agent,
data="dataset_name",
evaluators=[trajectory_evaluator, tool_correctness_evaluator],
)
字段含义:
run:本次 Agent 实际跑出来的运行记录;run.outputs里能拿到 trajectory、final answer、token 用量、耗时等。example:评测集中的某条样本,example.inputs是任务输入,example.outputs是期望结果(包括期望工具集)。评估器的返回值必须是 {"key": <指标名>, "score": <0~1>};可以同时返回多个 key,最后在 LangSmith 报告里分别展示。evaluate(...)的data参数指向 LangSmith 上的数据集名;evaluators是一组评估器函数,会对每条样本依次调用。
LangSmith 的特点是把 Agent 的 trace 和评测高度绑定,每次 Agent 运行的完整 trace(每一步的输入/输出/工具调用/中间状态)都会自动被记录,评估器可以直接消费这些 trace 字段。这对调试体验非常友好,评测报告里可以直接点进去看某条 fail case 的完整执行过程。
但它的局限也明显,紧耦合 LangChain / LangGraph 框架。如果团队 Agent 不是用 LangChain 写的,要把 trace 转成 LangSmith 能消费的格式,工作量不小。
DeepEval 的 Agentic Metrics
DeepEval 提供了一组开箱即用的 Agent 指标。
DeepEval 写法:
from deepeval.metrics import (
ToolCorrectnessMetric,
TaskCompletionMetric,
)
from deepeval.test_case import LLMTestCase, ToolCall, ToolCallParams
test_case = LLMTestCase(
input="帮我订一张明天上海到北京的高铁",
actual_output="已为你预订 G2 班次...",
tools_called=[
ToolCall(name="search_train", input_parameters={"from": "上海", "to": "北京"}),
ToolCall(name="book_train", input_parameters={"train_id": "G2", "seat": "window"}),
],
expected_tools=[
ToolCall(name="search_train"),
ToolCall(name="filter_by_time"),
ToolCall(name="book_train"),
],
)
tool_correctness = ToolCorrectnessMetric(
threshold=0.8,
evaluation_params=[ToolCallParams.INPUT_PARAMETERS],
should_consider_ordering=True,
)
tool_correctness.measure(test_case)
task_completion = TaskCompletionMetric(threshold=0.9)
task_completion.measure(test_case)
字段含义:
LLMTestCase.tools_called:Agent 实际调用过的工具列表,ToolCall(name, input_parameters)。LLMTestCase.expected_tools:期望调用的工具列表(参数可选)。ToolCorrectnessMetric.evaluation_params:决定除了工具名之外,还要附加比对哪些字段,ToolCallParams只有两个枚举值INPUT_PARAMETERS(比对参数)和OUTPUT(比对返回值);不传时只比工具名。ToolCorrectnessMetric.should_consider_ordering:开启后顺序错也算错;默认 False(顺序不计较)。ToolCorrectnessMetric.should_exact_match:开启后实际工具序列必须和期望工具完全一一对应(含次数和顺序),最严格的模式。TaskCompletionMetric内部基于 LLM judge 看 trace 整体是否完成了原始任务,不需要额外的期望字段,但任务描述要写清楚(放在input里)。
DeepEval 的优势是框架无关,不管 Agent 是用 LangChain、CrewAI、自研框架写的,只要能输出 tool calls 列表,就能跑。劣势是它的 trajectory 评估只到工具调用层,不会深入到中间数据流。
Databricks Mosaic AI Agent Evaluation
Mosaic AI 是 Databricks 的 Agent 评测套件,特别强调生产场景的可观测性。
Mosaic AI 写法(基于 MLflow):
import mlflow
import pandas as pd
result = mlflow.evaluate(
data=eval_df, # 评测集 DataFrame
model=logged_model.model_uri, # 可选;不传则直接评估 data 里的 response 列
model_type="databricks-agent", # 启用 Agent Evaluation
evaluator_config={
"databricks-agent": {
"global_guidelines": {"english": ["The response must be in English"]}
}
},
)
字段含义:
data:评测集 DataFrame,必备列是request(用户输入,推荐 messages 格式)和response(Agent 输出);可选列expected_response(参考答案,有了才评 correctness)和retrieved_context(检索上下文,有了才评 groundedness 和检索相关指标)。model:可选,logged model 的 URI。传了 MLflow 先调模型生成 response 再评估;不传则直接评估data里已有的response。model_type="databricks-agent":启用 Agent Evaluation 流水线(区别于"text"/"classifier"等)。evaluator_config:配置内置 judge;global_guidelines定义全局业务规则(如"回复必须是英文"),对应 guideline_adherence 判定。
内置 judge(correctness、groundedness、safety、relevance_to_query、guideline_adherence 等)不需要显式列出,按数据列自动启用;cost 和 latency 从 MLflow trace 里自动提取。
它的差异化在两点。
集成到 MLflow:评测结果天然进入实验跟踪系统,可以做 A/B 对比、版本回滚。 预置了一组针对 RAG-Agent 混合场景的指标:很多生产 Agent 都带 RAG(先检索再决策),Mosaic AI 把 RAG 评测和 Agent 评测的指标合在一起跑。
promptfoo 的 trajectory 断言
promptfoo 在 2026 年 3 月(0.121+,PR #8040)加入了一组内置的 trajectory 断言,直接从 OpenTelemetry trace 里读取工具调用序列。
promptfoo 写法:
tests:
- vars:
task: "查询昨天的订单状态"
assert:
- type: is-valid-openai-tools-call
- type: trajectory:tool-used
value: ['search_orders', 'format_response']
- type: trajectory:tool-sequence
value:
mode: in_order
steps: ['search_orders', 'format_response']
- type: trajectory:goal-success
value: "正确查到昨天的订单状态并告知用户"
字段含义:
is-valid-openai-tools-call:内置断言,校验 provider 返回的tool_calls字段是不是合法的 OpenAI function-calling 结构(工具名存在、参数符合 schema)。trajectory:tool-used:集合匹配,列出的工具都必须在轨迹里出现过,不看顺序;value 也支持对象形式(pattern用 glob 匹配工具名、min/max约束调用次数)。trajectory:tool-sequence:顺序匹配;mode: in_order允许中间穿插其他步骤,mode: exact要求严格一致。trajectory:goal-success:用 LLM judge 对照整条轨迹判断目标是否达成,适合"路径有多种、规则难硬编码"的场景;目标描述越具体,judge 误差越小。
同族还有 trajectory:tool-args-match(比对工具参数,支持 defaults / ignore 容错)和 trajectory:step-count(约束步数上下限,可以直接拿来卡死循环)。这组 trajectory: 断言的前提是开了 tracing(tracing.enabled: true)并把 Agent 的 OTel trace 接进来;不接 trace 的老办法是自己写 type: javascript 断言,读 provider 注入到 output.trajectory 的执行步骤数组。
promptfoo 的特点是把 Agent 评估融入它原本的断言系统,配置式声明、低门槛,但深度不如 LangSmith / DeepEval。
0x06 Agent 翻车模式诊断
把Tool Correctness、Trajectory Quality、Task Completion 三个维度放回到实际故障里看,能形成一份诊断手册。下面五种是工业界 Agent 系统最常见的翻车模式。
翻车模式 1:工具幻觉(Tool Hallucination)
症状:模型调用了一个根本不存在的工具,search_user_data、get_my_account,但这些函数在 tool registry 里完全不存在。或者调了存在的工具,但参数里塞了一个不存在的字段。
触发场景:
system prompt 里给的 tool 列表太长,模型记不住 tool 命名不规范(有的下划线有的驼峰),模型自己"修正" 多 agent 系统里,A agent 想调 B agent 才有的工具
诊断:Tool Correctness 直接 0 分。这个故障在 trace 解析阶段就能抓到,只要校验工具名是否在 registry 里就够了,不需要 LLM judge。
应对:
tool 注册时强制 schema 验证,不在 registry 里的调用直接拦截、返回错误给模型 system prompt 里用 OpenAI function calling 这种结构化 tool 描述,避免模型自由发挥 评测里把"工具是否在 registry"作为前置硬约束
翻车模式 2:参数错误但调用成功(Silent Argument Drift)
症状:工具被成功调用,结果也返回了,但参数不是用户期望的。比如用户说"上海到北京",模型填成 "Shanghai" → "Beijing",工具能识别,但和后续的中文步骤接不上。或者用户说"明天",模型填了"今天"。
触发场景:
多轮对话里,时间相关参数没正确解析("昨天" / "上周" / "下个月" 都是高发区) 多语言场景下参数格式不一致 用户用代词("那个订单" / "刚才那家店"),模型解析失败但不报错
诊断:Tool Correctness 的参数层评测会抓到,但纯字面匹配会漏判,必须用 LLM judge 做语义匹配,并把工具 schema 喂进去。
应对:
参数评测必须包含语义层面的判定,不能只比对字面值 高风险参数(金额、日期、收件人)单独做"二次确认",Agent 调高风险工具前必须把参数复述给用户 在 trace 里记录"参数推断的依据",比如 date=2026-05-29是从用户的"明天"推断的,记下来便于复盘
翻车模式 3:死循环 / 重复调用(Infinite Loop)
症状:Agent 卡在某一步反复调同一个工具(search → search → search → ...),直到达到步数上限或 token 上限才停止。
触发场景:
工具返回的结果不符合模型预期,模型选择"再调一次试试" 模型把上一步的输出错误地解析为"任务还没完成" 反思类提示词("如果不满意请再次尝试")没有终止条件
诊断:trace 解析阶段加死循环检测器(连续相同调用 ≥ N 次),命中即标记 fail。
应对:
给所有 Agent 加硬性的步数上限(比如 max_steps=20) 给关键工具加"幂等性提示",告诉模型"这个查询已经做过了,不要重复" 反思类 prompt 必须配明确的终止条件
翻车模式 4:上下文漂移(Context Drift)
症状:长对话或长任务里,Agent 慢慢"忘了"用户最初的意图。第 1 步还在执行任务 A,到第 8 步已经开始做和 A 无关的事情。
触发场景:
中间某一步工具返回了一个 distractor(比如搜索结果里出现了相关但不对题的内容) 模型在多步推理中"自我说服",基于自己的中间假设继续推理,越走越偏 上下文窗口快满了,早期的用户意图被截断
诊断:Task Completion 低,但 Tool Correctness 可能各步都是对的,单看每一步都没问题,整体就是不对。这种 case 必须用整体 judge 评估。
应对:
长任务里在每 N 步插入一次"原始任务回顾",把用户最初的需求重复进 prompt 关键决策点加 grounding,让模型显式回答"我现在做的事和原任务是什么关系" 评测里专门设计"长任务"测试集,故意混入 distractor 看模型能不能 stick to task
翻车模式 5:工具组合错误(Tool Composition Bug)
症状:每个工具的选择和参数值都对,但组合起来错了,A 工具的输出格式和 B 工具的输入预期不匹配。
举个具体例子:search_train 返回的出发时间是字符串 "14:30",book_train 期望的是 ISO-8601 时间戳 "2026-05-29T14:30:00+08:00"。模型把 "14:30" 原样透传,工具内部解析失败抛出错误,错误信息又被模型当成"成功"解析。
触发场景:
工具间有数据类型 / 单位 / 编码 / 时区 / 分隔符的不一致 工具返回值里有嵌套结构,模型只取了顶层字段 多 agent 系统里 A agent 输出的 schema 和 B agent 期望的 schema 不一致
诊断:这是最难抓的一类,因为失败点落在三个核心指标共同的盲区里。按默认配置,Tool Correctness 只评模型的选择行为——工具名和参数值,这个例子里两者都对(出发时间就是上游返回的真实值),所以单步确实全对;Trajectory Quality 看顺序、完整性和信息流贯通,也挑不出毛病;Task Completion 的 judge 看到轨迹里确实调过 book_train、最终输出写着"已预订成功",如果 judge prompt 没有显式要求核对工具执行状态,很容易被自信的最终陈述带偏而判完成。真正的失败发生在执行层——工具返回了 error,而这个信号默认不进入三个指标中的任何一个。
抓这类故障必须把执行层状态显式纳入评测:trace 里给每次工具调用记录 tool_status,出现 error / exception 立即标记 fail,和死循环检测同级,作为硬约束而不是打分项;评测框架支持的话(如 DeepEval 把 OUTPUT 加入 evaluation_params),把工具返回值也纳入比对范围。
还要注意这类故障的成立前提:如果工具的 schema 如实声明了参数类型,schema 校验在调用边界就能拦下类型错误,故障根本进不了执行阶段。它能漏到执行层,恰恰说明工具间接口约定松散、转换逻辑藏在工具内部——所以它的根治在工具设计(schema 规范化),评测里的 tool_status 是兜底。
应对:
工具返回值必须有结构化的成功 / 失败标记,不能依赖模型从 free text 里推断 tool 设计阶段就规范化输入输出 schema,跨工具的关键字段要约定一致(比如所有工具用 ISO-8601 处理时间) 评测里把"trace 中是否出现工具错误"作为独立的失败信号,而不是只看最终输出
把这五种翻车模式合起来看,Agent 评测的核心难点就清楚了,同一个"任务失败",根因可以散布在五个完全不同的环节。三个核心维度(Tool Correctness、Trajectory Quality、Task Completion)合在一起才能把这五种故障拆开来定位。
0x07 工程化的几个关键决策
评测预算的分配
Agent 评测比 RAG 评测贵得多,一条 case 可能涉及 10+ 次 LLM 调用 + 5+ 次 judge 调用。10000 条回归集跑一遍轻松上百美金。建议按三层分配预算。
快速冒烟集(50-200 条 case):每次 PR / commit 跑。只评 Tool Correctness(不需要 LLM judge)+ 死循环检测。秒级反馈。 核心回归集(500-2000 条 case):每天 / 每个版本跑。三个维度都评,但 judge 用 cheap 模型。 完整集(5000-20000 条 case):周级 / 月级跑。judge 用 GPT-5 级别模型,覆盖长尾场景。
多路径 ground truth 的维护
工具调用的"标准答案"很难写,一个任务往往有多条合法路径。如果只写一条,评测会大量误判。
实操上可以用 LLM 辅助生成 ground truth,人写一个标准路径,让 LLM 生成 N 条等价路径,再人工 review。这种方式比纯人工写的 ground truth 覆盖率高一个数量级。
Trace 结构的标准化
不同框架的 trace 格式各不相同。如果团队同时跑多个 Agent(比如 LangChain Agent 和自研 Agent),评测前要先把所有 trace 转成统一格式。建议的最小结构(自定义中间表示,不绑定任何评测框架)如下。
{
"task_id": "xxx",
"user_input": "...",
"steps": [
{
"step_id": 1,
"type": "tool_call",
"tool_name": "search_train",
"tool_args": {...},
"tool_result": {...},
"tool_status": "success"
},
...
],
"final_output": "..."
}
字段含义:
task_id/user_input:任务唯一 ID 和原始用户输入,用于 trace 索引和评测时的 grounding。steps[].type:步骤类型(tool_call/llm_call/reflection等),让评测器能区别处理。steps[].tool_args、tool_result:工具调用的输入和返回值;评测参数语义匹配、信息流贯通时都要读这两个字段。steps[].tool_status:成功 / 失败标记;翻车模式 5(工具组合错误)就是靠这个字段来抓的。final_output:Agent 最终给用户的回复;任务完成度评测时和user_input一起喂给 judge。
有了这层抽象,任何评测器都可以消费这个统一格式,不绑定特定框架。
Sandbox 隔离
很多 Agent 评测会真实调用工具,book_train 真的会订票、send_email 真的会发邮件。评测环境必须做 sandbox 隔离,否则评测一跑就是"自动给所有用户发垃圾邮件"。
常见做法有三种。
工具层加 sandbox_mode参数,评测时所有工具自动走 mock 实现评测专用的测试账号 / 测试数据库,和生产环境完全隔离 任何"不可逆操作"(删数据、转账、发邮件)在评测里强制 mock,不允许真实执行
0x08 最差实践
1. 只评最终输出,不评轨迹
最常见的错误。Agent 输出"任务已完成",judge 判通过,但根本没人查 Agent 是不是真做了。这种评测只测试了模型的语言能力,没测试 Agent 的工具能力。
修法是让 tool_status 字段进入评测信号,最终判定不能只看模型自述。
2. Tool Correctness 用纯字面匹配
如前文所述,"上海" vs "Shanghai" 会被判错。修法是用 LLM judge 做语义匹配,并把 tool schema 喂给 judge。
3. 没有死循环检测
线上 Agent 死循环最容易出事,它一边烧钱一边什么也做不出来。所有 Agent 评测必须有硬性步数上限 + 重复调用检测,作为评估前置约束。
4. 评测用真实工具
评测环境里 Agent 真的调了 delete_user / send_payment / post_to_twitter,下一秒就是事故。评测必须 sandbox。
5. 单 Agent ground truth 拿来评 multi-agent
多 agent 系统的轨迹是一棵树(不是一条线),评测时单 agent 的线性 trajectory match 算法用不上。multi-agent 评测要单独设计,每个 sub-agent 的 trajectory 分开评,agent 间的消息传递和整体协调质量也各要有自己的指标。
6. 把"模型自我报告"当成真信号
让 Agent 评估自己"任务完成度如何",它会几乎总是说"完成度很高"。Agent 自我评估的可信度极低。task completion 必须用外部 judge 或外部状态检查。
0x09 总结
Agent 的输出除了最终答案,还有一连串动作。把这串动作拆开来评,故障才能定位到具体环节。下面是这一篇的要点。
Agent 评测三维度:Tool Correctness、Trajectory Quality、Task Completion,各自覆盖不同的故障源。三个都跑,才有完整的故障地图。 死循环检测必须是硬约束:它是 fail/pass 的前置条件,不参与打分。 参数评测必须有语义层:纯字面匹配漏掉的故障最多。 Sandbox 隔离:生产 Agent 评测一旦真调工具,下一秒就是事故。 trace 结构先标准化:评测器和 Agent 框架解耦,未来换框架不用重写所有评测。 Agent 自我报告不可信:任务完成度永远要用外部判定。
0x0A 参考资料
Agent 评测论文与基准
Liu X. et al. AgentBench: Evaluating LLMs as Agents. ICLR 2024. arXiv:2308.03688. https://arxiv.org/abs/2308.03688 Qin Y. et al. ToolLLM: Facilitating Large Language Models to Master 16000+ Real-world APIs. ICLR 2024. arXiv:2307.16789. https://arxiv.org/abs/2307.16789 Ruan Y. et al. ToolEmu: Identifying the Risks of LM Agents with an LM-Emulated Sandbox. ICLR 2024. arXiv:2309.15817. https://arxiv.org/abs/2309.15817 Jimenez C. et al. SWE-bench: Can Language Models Resolve Real-World GitHub Issues? ICLR 2024. arXiv:2310.06770. https://arxiv.org/abs/2310.06770 Yao S. et al. τ-bench: A Benchmark for Tool-Agent-User Interaction in Real-World Domains. arXiv:2406.12045, 2024. https://arxiv.org/abs/2406.12045 Lightman H. et al. Let's Verify Step by Step (Process Reward Models). arXiv:2305.20050, 2023. https://arxiv.org/abs/2305.20050
工程化文档
LangSmith Agent Evaluation. https://docs.smith.langchain.com/evaluation DeepEval Agentic Metrics - Tool Correctness / Task Completion. https://docs.confident-ai.com/docs/metrics-tool-correctness Databricks Mosaic AI Agent Evaluation. https://docs.databricks.com/en/generative-ai/agent-evaluation/index.html promptfoo trajectory assertions. https://www.promptfoo.dev/docs/configuration/expected-outputs/deterministic/
本文作者:唐银@涂鸦智能安全实验室
漏洞悬赏计划:涂鸦智能安全响应中心(https://src.tuya.com)欢迎白帽子来探索。