一个客服 Agent 查了订单、看了物流、读了退款政策、创建了工单,还给用户回了一句礼貌的已处理。

九次工具调用,轨迹干净得像一次标准演示。

可数据库里,那个本该等待物流异常解除的工单,被它关成了已解决。用户真正的问题也没有被回答。

这不是我编的反面案例。Microsoft 和 Hugging Face 刚发布的 ThinkingBox-Bench 里,就有这样一道零售客服任务。它戳中的地方很刺眼。我们这半年做 Agent,太容易把「模型把活干完了」等同于「系统完成了正确的状态变化」。这两件事,差得远。

我今天想聊的不是又一个榜单,而是这套评测背后那个应该被工程团队拿走的判断。Agent 的最终回复只是自述,系统留下的状态才是证据。

一条看着顺的轨迹,为什么还是错了

做过 Java 或 Spring 服务的人,对这类错误其实不陌生。

一个支付接口返回 200,不代表账已经记对。一个事务没有抛异常,不代表库存、优惠券、订单状态都满足业务约束。你会查数据库,会看事件表,会核对幂等记录。没有人会因为日志里打印了「扣款成功」就直接给财务结账。

可轮到 Agent,很多团队突然变得很宽容。

我们看它有没有调用工具。看最后一句是否自然。再高级一点,看 trace 是否完整。评测过了,就准备接进工单、CRM、审批流。

坦率地讲,这套验收方式有点像只看 Controller 返回值,不看事务最终提交了什么。Demo 时很好看,上线后会很别扭。

ThinkingBox 的做法并不神秘。每一道任务都有初始后端状态、用户目标、可调用的 MCP 工具、领域规则和对终态的可执行断言。Agent 在隔离环境里工作,评测器不要求它走某一条固定路径,只问一件事,结束时留下的记录和副作用是不是对的。

比如该把工单改成 on hold,不能改成 solved。该创建退款记录时必须创建。不能多给一笔补偿。用户必须被告知某条政策不保证什么时,回复里也得有。

这套思路很像数据库测试里的 post-condition。过程可以不同,终态必须满足不变量。

这句话听上去朴素,落到 Agent 上却很容易被忽略。因为语言模型特别擅长把过程讲得像已经完成了。

507 个工作流,把「像完成」和「真完成」拆开了

ThinkingBox-Bench 一共放了 507 个有状态业务工作流,覆盖零售与电商、旅行与酒店、车险、数字银行支持、咨询公司的 IT 与 HR 支持。每次运行都从相同的干净后端状态开始,任务通过 MCP 工具与环境交互,最终由确定性的检查器核验数据库终态和副作用。

这里有两个设计我很喜欢。

第一个是隔离。两次尝试不共享数据库行,也不共享工具缓存。你今天偶然把状态弄对,不能给下一次留后门。

第二个是重复。每个任务不是跑一遍,而是独立跑 20 次。

这一刀很关键。

行业里常用的 pass@k 很适合回答「它能不能至少解出一次」。可如果你把 Agent 放在一个会写真实数据的流程里,用户更关心的是「它会不会这次正确、下次又变卦」。前者是能力上限,后者才接近可靠性。

论文给了两个很有杀伤力的数字。Claude Opus 5 的 pass@1 是 66.50%,20 次都通过的任务占比是 47.53%。Kimi-K3 的 pass@1 是 57.37%,对应的 20 次一致通过率只有 17.60%。

不是模型完全不会做。恰恰相反,它们经常能做对一次。

问题在于,偶尔找到正确路径和稳定地走完正确路径,中间隔着一整套工程问题。

Hugging Face 发布页把这个差异拆得更细。Kimi-K3 至少成功过一次的任务有 476 个,占 507 个任务的 93.89%。听起来很能打。但它 20 次全过的只有 68 个任务。Claude Opus 5 至少成功一次的覆盖面更低,却有 241 个任务连续 20 次通过。

你如果只看一眼排行榜,可能会选前者。你如果要让它修改客户账户、发起赔付、创建员工权限,答案未必一样。

真正麻烦的失败,不会报错

我原先也以为,Agent 的大头失败应该是推理走偏,或者工具调用格式错了。ThinkingBox 给出的证据没这么简单。

在一组包含 121,680 次有效尝试、覆盖 12 个模型的分析里,有 79,853 次没有通过可执行检查。更让人头大的是,其中 67.24% 的失败尝试是正常结束的,调用过会改状态的工具,最后也没有工具报错。

它们看起来都像成功。

但终态检查发现,77.61% 存在错误字段值,43.30% 有不该发生的额外副作用,25.36% 漏掉了必须发生的副作用。这几个比例会重叠,它们不是三类互斥错误。

这就解释了很多团队遇到的怪事。测试同学看 trace 会说「它明明查到了」。研发看日志会说「接口全通」。业务同学最后打开后台,却发现单子被关了、金额不对、权限多开了一层。

模型不是没努力,它只是没有把工作闭合到业务终态。

这话听着有点刺耳,但 Agent 的问题并不总在「聪不聪明」。更多时候,它卡在工具失败后的恢复、前置条件没有满足、查询为空时该不该继续、多个写操作该按什么顺序提交。

论文作者按可观察的轨迹给失败归因,工具使用问题占约 79.9%,错误状态更新占 10.3%,没有完整解决用户诉求占 7.0%,完全没有状态变更只占 2.9%。这不是严格的因果解释,却给了工程上很实在的优先级。

别急着换更大模型。先把工具协议、错误语义、重试边界和终态校验做好。

把 Agent 当成一个不稳定的分布式参与者

我觉得这里可以借一点分布式系统的视角。

传统服务调用里,我们不会相信一次 RPC 返回 success 就代表业务完成。网络可能超时,消费者可能重复,数据库可能局部提交,异步消息可能还在路上。所以才会有 outbox、幂等键、补偿事务、状态机和对账任务。

Agent 接入工具以后,也进入了同一个世界,只是失败形态更丰富。

它会读取到过期信息。会把自然语言里的约束漏掉一半。会在工具返回空结果时脑补原因。会在第七步忘记第三步拿到的用户偏好。甚至工具都调用成功了,参数组合也可能违反业务规则。

所以别把 Agent 编排理解成给模型接一串 function calling。它更像把一个不完全可靠、会解释自己行为的外部参与者,放进了你的业务状态机。

这个参与者有两个特点。

它不保证确定性。

它会写一段非常有说服力的执行总结。

后者尤其危险。人类对一段顺畅的解释有天然信任,日志和自然语言又常常互相印证,大家就不再追问那个最笨的问题,最终那一行记录到底是什么。

ThinkingBox 的价值,就在于把这个问题变成机器可判定的断言。

给 LangGraph 或 Spring 工作流补一条验收链

这篇论文不是让每个团队都去搬 507 道题回来跑满 20 次。它真正值得抄的,是评测边界。

如果你正在用 LangGraph 编排 Agent,我建议从三个很小的改动开始。

把最终状态写进图,而不是藏在回答里

不要只让最后一个节点输出自然语言答案。给每一个能改变外部系统的任务定义一个结构化 outcome,例如 ticket_status、refund_id、approval_state、side_effects。最终节点先读回目标系统,再生成给用户的文字。

顺序别反。

用户看到的句子应该是状态校验后的解释,不应该是状态校验的替代品。

在 LangGraph 里,这通常可以是一个专门的 verify 节点。它拿到预期状态、工具调用返回和数据库读模型。如果状态不一致,图回到 recover 或 human_review,而不是直接 END。

1
2
3
4
plan -> execute_tools -> read_back_state -> verify
| |
| +-> respond
+-> recover or human_review

这类图看起来多了一步,却会把最贵的事故挡在真正写入用户数据之后。

为每一个写操作定义不变量

别只写「创建工单成功」这种技术性断言。把业务的不变量写出来。

例如物流异常未关闭的订单,工单状态必须是 on_hold。补偿只能在客户分层和延误天数同时满足时创建。撤销权限后,权限表和审计表必须都出现对应记录。退款成功后,订单、支付、库存三处状态必须可对账。

Spring 的团队可以把它做成 service 层的后置校验,或在 Saga 的每个状态迁移后发出校验事件。Agent 只是调用者,不应该成为规则的唯一解释器。

我自己的感受是,能被代码表达的业务规则,就不要只留在 Prompt 里。Prompt 是沟通协议,不是数据库约束。

区分可重试、需补偿和必须停下的错误

很多 Agent 的重试策略只有一种,失败就再试一次。这个事儿我也踩过坑。网络超时可以重试,工具返回空列表也许应该追问用户,已经写入一半的数据则可能需要补偿。把三类情况混成统一 retry,往往会制造重复扣款、重复建单或更难查的脏数据。

一个实用的分类可以是这样。

情况 Agent 可以做什么 系统要留什么
连接超时且没有写入证据 有上限地重试 请求幂等键和尝试次数
工具返回业务前置条件不满足 停止写入并说明原因 失败原因和待补信息
已发生部分副作用 进入补偿或人工审核 状态快照与补偿任务
终态和目标不一致 禁止对用户宣称完成 校验失败证据与 trace

你想想看,这张表其实不是 AI 专属。它是成熟业务系统早就有的故障模型。Agent 的出现,只是逼着我们别再把它们藏在人工操作和经验里。

20 次重复,不该变成一项昂贵的仪式

看到 20 次,有人可能会觉得成本要爆炸。这个担心完全合理。

论文自己也没有把 20 当成神奇数字。它报告的是观测到的 20 次全过,不是统计学承诺。生产里该跑几次,取决于失败代价和任务频率。

给内部知识库整理标签,跑三次或五次就够发现大多数波动。给客户退款、改合同权限、触发生产部署,一次错误的代价高得多,20 次甚至只是最低限度的预演。还有一些不可逆操作,正确答案不是把评测次数加到 100,而是保持人审。

Hugging Face 的成本分析也给了一个现实提醒。单次成功最便宜的模型,未必是得到一个连续 20 次都可靠任务的最便宜选择。把成本分母从「成功一次」换成「稳定成功」,模型排序会变化。

这也是为什么我不太喜欢只讨论每百万 token 单价。对 Agent 来说,真正该算的是每个可验收业务结果的成本,再加上失败之后的补偿、排障和信誉成本。

一次看似便宜的错误,常常是最贵的路径。

ThinkingBox 也不是生产系统的免责卡

这套基准有边界,得说清楚。

它的任务是对真实企业模式的合成重建,客户和业务数据不是真实生产数据。覆盖了五类业务领域,不代表能代替你的支付链路、风控规则、权限模型。基准中的可执行断言写得再好,也不会自动发现你没写进断言的新风险。

而且状态正确不等于所有事情都正确。隐私泄露、越权读取、糟糕的用户沟通、延迟和成本,都可能在终态正确时依然造成伤害。论文也为其中 30 个任务额外加了回复语义检查,剩余 477 个主要检查状态。这是一种有取舍的设计,不是一把万能尺。

不是说状态评测不够,而是它终于把最容易被漂亮回答掩盖的那一层补上了。

真正的生产验收,应该至少有四层。权限边界、过程审计、终态断言、用户可理解的结果说明。ThinkingBox 把第三层做得很扎实,正好适合成为我们现有监控和 eval 的中间骨架。

不要再问 Agent 做没做,要问系统留下了什么

回到那张被错误关掉的客服工单。

它之所以让人不舒服,不是因为 Agent 犯了一个低级错误,而是因为每一个表面信号都在替它作证。工具调通了,日志正常,回复也像样。只有数据库里的那个字段没有配合演戏。

这很像老派软件工程里最朴素的经验。不要迷信过程承诺,要验证可观察结果。

Agent 让这句话重新变得昂贵,也重新变得重要。

下次你验收一个能写真实数据的 Agent,可以先别问它「你完成了吗」。给它一个更难逃的问题。

最终状态是什么,证据在哪里。

参考资料