- Published on
大模型有返回等于 Agent 成功运行吗?
- Authors

- Name
- hpoenixf
大模型有返回等于 Agent 成功运行吗?
前段时间在做 Agent 模型评测时,我遇到过一个挺别扭的问题。
服务端有候选答案,自动裁判也给了结果,运行状态甚至已经是 completed。如果只看评测报表,这次请求似乎已经结束了。
但继续往前端链路看,会发现事情没有这么简单:候选答案存在,不代表它真的被发送;服务端记录了发送,也不一定等于浏览器最终完整接收和展示。
这几个状态以前很容易被我下意识地当成一回事。
后来我越来越觉得,Agent 评测里最危险的不是某个模型分数低,而是系统把不同层级的“成功”压成了一个 Green。
一旦这样做,报告会越来越漂亮,问题却越来越难找。
这篇文章记录的是我们后来怎么拆这个问题。它不讨论“哪个模型最好”,而是先回答一个更基础的问题:
我们到底在评测什么?又凭什么说一次 Agent 请求真的成功了?
completed 只说明程序走到了某个状态
普通接口里,我们很习惯看一个请求是成功还是失败。
但 Agent 的链路要长很多。
一次看起来普通的问答,可能经历:
用户问题
→ 规划
→ 工具调用
→ 证据整理
→ 生成候选答案
→ 质量检查
→ 流式发送
→ 前端接收和展示
这些环节并不是同时成功或失败的。
比如:
- JSON 合法,但规划理解错了问题;
- 规划没问题,但需要的工具根本没调用;
- 工具调用成功,但证据已经过期;
- 候选答案写得很好,但发布校验没通过;
- 服务端开始发流了,但中间断掉;
- 服务端认为发送完成,客户端却没有足够证据证明完整渲染。
以前如果最后只留一个 success=true,这些差异就全没了。
所以我后来更愿意把一次评测拆成几个独立的问题:
| 层面 | 真正要确认的事情 |
|---|---|
| 协议 | 输出能不能被系统正常解析 |
| 规划 | 模型有没有理解任务和需要的证据 |
| 证据 | 需要的数据是不是真的拿到了 |
| 回答 | 最终内容是否正确、安全、和证据一致 |
| 交付 | 这份内容是否真的进入了用户侧链路 |
前一项通过,不能替后一项背书。
HTTP 200 不能证明回答正确,completed 也不能证明用户看到了答案。
这个区别看起来很基础,但我们后面不少评测问题,根源都在这里。
我不再急着算一个总分
一开始做模型比较时,很自然会想得到一个分数。
比如协议、规划、工具调用、回答质量各自打分,最后加权成 82 分、87 分、91 分。
问题是,有些东西其实不适合被平均。
如果一个模型语言质量很好,但关键证据没有拿到,我不希望“表达能力”把“证据缺失”抵消掉。
如果一次请求根本没有把答案送出去,也不应该因为服务端候选内容很好,就得到一个不错的综合分。
所以现在更接近这样的记录方式:
Protocol = pass
Planning = pass
Evidence = inconclusive
Answer = not_evaluated
Delivery = pass
这组结果没有一个特别漂亮的总分,但它更有用。
我一眼就知道:协议和规划没问题,交付链也跑了,但证据还没有闭合,所以这次结果不能拿去证明模型质量。
这里有个词我觉得很重要:inconclusive。
它不是“比较委婉的 failed”。
failed 表示我们已经有足够证据说明它没达到要求。
inconclusive 表示证据还不够,暂时不知道。
这两种状态后续动作完全不同。前者应该修,后者应该补证据。
如果把它们都算成 0 分,最后得到的模型排名会混入很多基础设施噪音。
比总分更容易骗人的,是分母
后来真正让我警惕的,是另一个更隐蔽的问题:评测分母会偷偷变小。
假设原本计划跑 30 题。
结果其中 20 题因为排队、超时或者工具问题,没有在观察窗口内完成。剩下 10 题里,有 9 题返回了非空内容。
这时候写:
已完成样本中,9/10 返回了答案。
没有问题。
但如果把它写成:
成功率 90%。
意思就完全变了。
因为那 20 个没跑完的题,很可能恰恰是长上下文、多工具、复杂纠错或者更容易暴露问题的题。
它们通常不是随机缺失。
如果只看已经跑完的样本,最后测到的其实是:
模型在“它成功完成的那些题”里的表现。
而不是它在原始题集上的表现。
这也是为什么后来我越来越坚持固定分母。
一次评测开始之前,就把下面这些东西固定下来:
{
"suite": "agent-cases-v1",
"sourceHash": "sha256:...",
"revision": "git-revision",
"modelId": "provider/model-version",
"profile": "live-read-only",
"attempt": 1
}
题集变了,算新窗口。
代码变了,算新窗口。
模型版本变了,也算新窗口。
可以做前后对比,但不能把不同条件下的结果直接拼在一起,再算一个更好看的百分比。
这条规则其实挺反人性的,因为它会让报表变难看。
但失败样本不应该因为“没有完成”就从统计里消失。
有时候,没完成的那些题才是最值钱的样本。
裁判看到的答案,不一定是用户拿到的答案
这可能是整套评测里我最在意的一点。
很多自动评测都发生在服务端。
模型生成一个候选对象,系统把它交给裁判,裁判开始检查事实、格式、引用、语气,然后给出一个 verdict。
技术上完全合理。
但如果真实产品是流式交付,就多了一层问题:
裁判评的那份内容,和用户实际收到的内容,是同一份吗?
它们可能不是。
一个候选答案可能已经完整生成,但在发布校验时被拒绝。
也可能开始发送,前半段到了,后半段因为超时没有出去。
甚至服务端已经记录了 send,但如果没有客户端 ACK、接收事件或者渲染侧证据,我们最多只能说:
服务端确认发送过这些内容。
不能再往前一步说:
用户一定完整看到了这些内容。
这个边界我觉得有必要说得很死。
所以评测里最好至少区分三层:
模型候选内容
↓
服务端实际发送内容
↓
客户端接收 / 展示证据
我们当前最容易可靠重建的是第二层:从真实发送事件按顺序拼出“服务端交付材料”。
如果客户端还有 ACK、消息序号或渲染回执,可以继续证明第三层。
如果没有,就不要为了让指标名字好听,把“已发送”写成“用户已看到”。
服务端交付材料怎么重建
对流式系统来说,答案不是一个最终字符串,而是一段事件历史。
例如:
run started
tool call
tool result
text delta #1
text delta #2
text delta #3
terminal
评测用的正文应该尽量从真实对外发送的 text delta 重建,而不是直接拿后台保存的候选答案。
这样做之后,一个很常见的假 Green 会自然消失:
裁判认为答案完整,但用户侧实际上只收到前半段。
如果流在中途断了,评测材料也只能包含已经真正发送出去的部分。
后台后来补写了一个更完整版本,也不能反过来修改那次运行的历史。
这一点让我觉得,Agent 评测和普通 benchmark 很不一样。
它评的不只是模型输出,还在评一个分布式系统的交付过程。
评测系统自己也会犯错
做到这里,还有一个问题绕不过去:
如果错的是评测器本身怎么办?
我们确实遇到过这种情况。
某一类输出因为夹具或者校验合同的问题,被机械地判成失败。后来修正评测逻辑后,同一批受影响样本的结果发生了明显变化,但仍然保留了一部分真实失败。
最简单的做法当然是直接改旧结果。
但这样一来,第一次为什么失败、后来为什么改判,就再也说不清了。
所以后来采用的思路更像账本:
原始评测结果不改
+
追加 correction
↓
生成新的修订视图
一条 correction 至少要知道:
- 它修改的是哪一次评测;
- 哪些 case 受影响;
- 原来的 verdict 是什么;
- 新 verdict 是什么;
- 为什么改;
- 它对应的原始输入和版本是不是同一份。
可以简单理解成:
RevisedView = RawLedger + ValidCorrections
这里的重点不是公式,而是 RawLedger 不被覆盖。
如果以后发现第二次修正也有问题,可以再追加一条,而不是继续篡改历史。
这对调模型也很重要。
否则几轮之后,你看到的是一张“修过很多次但不知道修过什么”的榜单,很难再判断提升到底来自模型、Runtime,还是评测器自己。
几个真实数字,为什么我现在会刻意少解释一点
在受控验证窗口里,我们曾经有过这样的结果:
- 一组离线评测是
583/583; - 另一组证据闭环测试是
59/59; - 更大的模型评测集合是
677/677。
以前看到这种数字,我可能会很兴奋地写“全部通过”。
现在反而会多补一句:
它们证明的是对应测试合同和当次本地逻辑通过,不证明某个候选模型已经在真实长期环境里更好。
这不是故意保守。
而是我们后来见过太多“数字是真的,结论说过头了”的情况。
一次受控批次可以证明协议能走通。
一组真实调用可以证明链路曾经工作。
一批自动裁判结果可以提供比较信号。
但长期质量、尾延迟、供应商稳定性、真实工具可用率,都需要自己的证据。
这些东西不能互相借。
最后,我们把发布模型结论变成了一个门槛问题
做到后面,我发现其实不需要设计一个特别复杂的总评分公式。
先问三个问题就够了。
1. 这次比较是不是同一场实验?
题集、代码、模型、配置、数据快照和分母是否对得上。
对不上,就不要硬比。
2. 有硬门没有通过吗?
比如协议、安全、隐私、关键证据、交付。
有任何一项明确失败,这个候选在当前角色下就不应该进入后面的排名。
3. 还有关键状态未知吗?
如果关键证据还是 inconclusive,那就保持 provisional。
先补证据,再谈冠军。
只有通过这几层之后,质量、稳定性、延迟和成本这些指标才适合真正拿来比较。
伪代码大概是:
if not same_experiment:
return inconclusive
if hard_gate_failed:
return not_eligible
if hard_gate_unknown:
return provisional
return compare_quality_reliability_latency_cost()
它看起来没有一个漂亮的“智能评分公式”。
但实际用下来,我反而更信任这种东西。
因为它不会让一个 95 分掩盖掉“答案根本没发出去”。
写在最后
这轮评测改造之后,我最大的变化不是更会测模型了,而是没那么相信一个绿色数字了。
completed、模型产出了答案、裁判给了 pass、服务端发送了正文、客户端实际展示成功,这些事情彼此有关,但不是同一件事。
同样,20 个样本没完成之后算出来的 9/10,也不是整套题的 90%。
Agent 的链路越复杂,越容易出现这种“每一小段都像成功,拼起来却不是成功”的情况。
所以现在再看评测,我会先问:
这个 Green 到底在证明哪件事?
能把这句话回答清楚,再去比较模型,排名才有意义。
否则我们很可能只是在非常精确地计算一个定义错了的问题。