阅读,与值得关注的内容Readance

AI应用测试方法(四):Agent 评测

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)
模型选了哪个工具、传了什么参数
工具名错、参数错、应该调没调
轨迹质量(Trajectory Quality)
整条调用序列
顺序错、绕远路、死循环、漏步骤
任务完成度(Task Completion)
最终是否达成用户目标
自称完成但没做、做了但结果不对

下面拆开讲。

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_dataget_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_argstool_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 的输出除了最终答案,还有一连串动作。把这串动作拆开来评,故障才能定位到具体环节。下面是这一篇的要点。

  1. Agent 评测三维度:Tool Correctness、Trajectory Quality、Task Completion,各自覆盖不同的故障源。三个都跑,才有完整的故障地图。
  2. 死循环检测必须是硬约束:它是 fail/pass 的前置条件,不参与打分。
  3. 参数评测必须有语义层:纯字面匹配漏掉的故障最多。
  4. Sandbox 隔离:生产 Agent 评测一旦真调工具,下一秒就是事故。
  5. trace 结构先标准化:评测器和 Agent 框架解耦,未来换框架不用重写所有评测。
  6. Agent 自我报告不可信:任务完成度永远要用外部判定。

0x0A 参考资料

Agent 评测论文与基准

  1. Liu X. et al. AgentBench: Evaluating LLMs as Agents. ICLR 2024. arXiv:2308.03688. https://arxiv.org/abs/2308.03688
  2. 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
  3. 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
  4. 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
  5. 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
  6. Lightman H. et al. Let's Verify Step by Step (Process Reward Models). arXiv:2305.20050, 2023. https://arxiv.org/abs/2305.20050

工程化文档

  1. LangSmith Agent Evaluation. https://docs.smith.langchain.com/evaluation
  2. DeepEval Agentic Metrics - Tool Correctness / Task Completion. https://docs.confident-ai.com/docs/metrics-tool-correctness
  3. Databricks Mosaic AI Agent Evaluation. https://docs.databricks.com/en/generative-ai/agent-evaluation/index.html
  4. promptfoo trajectory assertions. https://www.promptfoo.dev/docs/configuration/expected-outputs/deterministic/

本文作者:唐银@涂鸦智能安全实验室 

漏洞悬赏计划:涂鸦智能安全响应中心(https://src.tuya.com)欢迎白帽子来探索。

前往微信阅读全文

内容来自公众号,可前往微信查看原文。

查看作者的更多文章 →