跳到主要内容

评估通过,但工具全是 Mock 的:为什么你的 Agent 最棘手的生产故障从未进入测试框架

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的智能体在评估测试集上达到了 94% 的准确率。然而你的轮值告警却响个不停。房间里没人撒谎,这两个数字都是真实的。实际情况是,测试框架(harness)在测试提示词(prompt),而生产环境在测试智能体(agent),这是两个完全不同的产物,只是恰好共享了权重。

Mock 工具的评估通常是产生这种差距的原因。你用预设的 JSON 存根(stub)了 search_orderscharge_cardsend_email,给模型输入一个用户回合,并对最终响应进行断言。这种运行方式成本低、具有确定性且可复现——这些都是 CI 系统喜欢的特性。但它对工具选择、延迟、速率限制(rate limits)、部分失败和重试行为保持沉默,也就是说,它忽略了那些在事故回顾中占主导地位的失败因素。

这并不是在反对 Mock 评估。对于提示词级别的断言,它们是正确的工具,且永远如此。我想表达的是,一个完全由 Mock 工具组成的测试套件,从未真正见过你的智能体。下面我们将探讨这种差距的具体形态、为什么经济引力不断将团队推向这种做法,以及能够真正捕捉到那些频发事故的分层方法。

Mock 工具评估暗含的三个假设

每一个 Mock 的工具响应都包含了测试框架永远不会检查的三个假设:

  • 智能体选择了正确的工具来调用。
  • 它以正确的格式传递了正确的参数。
  • 它在第一次尝试时就收到了格式良好的响应。

在生产环境中,这三点经常失效。当用户询问订单状态时,智能体可能会调用 search_products,因为这两个工具的描述中有一个名词重叠了。它们可能会在 Schema 预期 Epoch 秒数时传递一个字符串格式的时间戳。它们会遇到下游 API 的 429 错误,而模型的下一轮回应是“抱歉造成困扰,让我再试一次”——然后进入一个以同样方式失败的相同调用。Mock 评估对这三种模式都是盲目的,因为 Mock 接受任何参数形状,总是在第一次尝试时返回,并且隐式地为测试框架硬编码的工具选择背书。

一项针对生产环境智能体事故的从业者调查发现了大约四种反复出现的典型类型:缺乏依据的过早行动、捏造缺失实体的过度热情、干扰项引起的上下文污染,以及负载下的脆弱执行。这四种都是工具交互失败。在 Mock 工具套件中,这四种情况都不会可靠地出现,因为 Mock 避开了它们赖以生存的执行闭环。

为什么团队首先会选择 Mock

经济因素至关重要,因为对大多数团队来说,“只使用真实工具”并不是一个现实的选项。

使用真实工具的评估比 Mock 评估贵 10 到 50 倍。你需要为智能体的每一轮 LLM Token 付费,加上工具调用的下游 API 费用,再加上必须存在并保持干净的共享固件(测试账户、沙盒数据库、种子数据)。一个在 Mock 环境下运行只需 90 秒的 200 例评估套件,在真实工具上可能需要 40 分钟,而这还只是理想情况——还没考虑到第三方的不稳定性、沙盒速率限制,以及“一个测试留下的残余数据破坏了下一个测试”的问题。

不稳定性是另一半代价。一个 96% 的时间显示为绿色的真实工具测试套件其实并不合格——它只是噪音。开发者会开始不再信任它,不再在本地运行它,也不再让它在 CI 中起到拦截作用。不到一个季度,这个套件就会变成参考性的,然后被忽视,最后被删除,团队最终回到了原点:只有 Mock 和满腹牢骚。

错误并不在于 Mock 本身。而在于将 Mock 套件视为智能体行为的权威测试(test of record),而它实际上只是提示词行为的权威测试。

Mock 套件隐藏的具体失败情况

值得具体列出这些情况,因为“工具交互失败”这个抽象概念会让团队只是随声附和,而不会改变他们的测试方式。

工具选择错误首当其冲。你的智能体有 12 个工具,而模型选错了。Mock 无法捕获这一点,因为测试框架通常硬编码了哪个 Mock 做出响应。即使你设置了路由,Mock 也不会惩罚选择了一个在现实中响应毫无用处的工具的行为——因为虚假的响应总是看起来很合理。

参数形状错误排在第二位。模型输出了 {"user_id": "12345"},而工具期望的是一个整数。在生产环境中,API 会返回 400 错误;但在 Mock 中,你可能因为测试不关心而放宽了 Schema 的编写,导致智能体径直进入下一个“成功”的回合。

延迟引起的超时是最让人头疼的。工具的 p99 延迟超过了智能体的每轮预算,编排器(orchestrator)开始中途杀掉该回合。当工具调用在中途断掉时,模型会做什么——重试?放弃?幻觉出一个结果?——这完全取决于你的测试框架连接方式,而在瞬时返回的 Mock 环境下是测试不到的。

速率限制级联。智能体遇到了 429 错误,决定重试,再次遇到错误,由于循环中没有计数机制,智能体在重复调用同一个失败的请求中耗尽了所有的 Token 预算。这种模式只在能访问真实工具并施加真实背压(backpressure)时才会存在。Mock 没有 429 状态。

部分失败是最后一种重要的情况。一个并行工具调用返回了两个结果和一个超时。智能体如何处理超时?诚实地报告?静默忽略?假设成功?每个团队都是在生产环境中才发现问题的,因为 Mock 测试只是派发了一个单一的合成字典,然后就继续运行了。

评估通过率与事故发生率的背离信号

这是一个典型的组织架构信号,表明团队正处于一个纯模拟(Mocked-only)的评估世界中:评估通过率持续攀升,而生产环境的事故率却拒绝随之下降。季度复季度,测试套件的绿灯越来越多,但故障报警却从未减少。工程主管询问原因,没人能给出清晰的答案,因为这两个数字衡量的是不同的对象。

实际观察到的差距往往很大。在一个公开案例中,91% 的评估得分与 68% 的生产成功率之间存在 23 个百分点的差距;而在另一个案例中,组件级的理论成功率与系统级的实际成功率之间有 8 个百分点的差距,这完全是由工具间的协作失败导致的。当评估测试的是提示词(Prompt),而事故追踪器追踪的是智能体(Agent)时,你就会看到这样的数字。

当这种背离出现时,可行的方法并不是进一步收紧模拟测试套件,而是构建第二个评估维度,用以观察模拟套件在结构上无法发现的失败。

真正能捕获事故的混合阶梯

在实践中,有效的方法是将工具交互视为一个阶梯式的评估问题,每一级阶梯测试不同的层面,且成本各异。请同时运行这三个级别,不要试图用其中一个来代替另一个。

第一级:模拟单元评估(Mocked unit evals)。 快速、廉价、具有确定性。这些评估针对提示词层面的行为进行断言——语气、拒绝回复、结构化输出形状、个人隐私信息(PII)脱敏以及基础推理。它们在每次 PR(拉取请求)时运行。这里的逻辑是,提示词行为是一个稳定的层面,模型很难在没人察觉的情况下发生回退。对参数形状(Argument shape)保持严格的模拟,这样参数形状的回退就会在这里显现,而不是在生产环境中。

第二级:录制回放评估(Recorded-cassette evals)。 借鉴自 VCR 风格的 HTTP 录制回放测试,这类评估会捕捉一次真实的交互——真实的模型、真实的工具调用、真实的响应——并在此后确定性地回放它们。如果智能体的下一次运行产生了相同的工具调用序列和相同的参数,磁带(Cassette)就会回放,测试既快又免费。如果序列发生偏离,测试会立即报错,并强制人工进行重新录制或拒绝更改。这是工具形状回退显现的阶段:下游 API 的 Schema 变更、改变模型选择的工具描述抖动、参数格式漂移。请在每次合并到主分支(Main branch)时运行这些测试,而不是在每次 PR 时。

第三级:实机工具冒烟测试(Live-tool smoke suite)。 一组经过人工精选的小规模场景——5 到 20 个,而不是 200 个——定期(每小时、每晚或在金丝雀发布时)在真实沙盒中针对真实工具运行。这是延迟、频率限制和部分失败行为显现的地方。刻意保持其规模较小;该套件的任务不是覆盖率,而是告诉你“当网络环境真实时,智能体依然可以工作”。一个你信任的小型套件,远胜过一个你最终会选择忽略的大型套件。

这三级阶梯涵盖了不同的失败模式,且成本差异巨大。一个健康的团队会同时运行这三者,分别进行监控,并将各级之间的差距作为诊断工具。当模拟套件为绿灯而录制套件为红灯时,说明下游 API 改变了形状。当录制套件为绿灯而实机冒烟测试为红灯时,说明延迟或频率限制发生了变化。当三者均为绿灯而生产环境依然告急时,说明你的评估对该失败无能为力,你需要向第三级添加一种新的事故类型。

周一该做什么

实际的起步动作可以很小:在模拟测试和生产环境之间增加一个录制回放层。你不需要新的框架。pytest-recordingvcr.py 以及各语言的等效工具可以将 HTTP 请求和响应记录到 YAML 文件中;录制模式会访问一次真实 API,而回放模式则屏蔽所有出站调用并与磁带进行匹配。LangChain 和类似的智能体框架现在都明确记录了这种模式,因为它以合适的成本解决了“我的评估在撒谎”的问题。

一旦拥有了磁带,失败模式就会发生变化。与其说是“我们不知道 Schema 发生了变化”,你会得到一个带有清晰差异(Diff)的失败 PR:智能体在周一调用 search_orders 时参数是 limit: 50,而今天它调用 search_orders 时参数变成了字符串 limit: "50"。你可能仍然不知道模型为什么发生了变化,但你知道它确实变了,并且你在用户发现之前就捕获了它。

更大的转变在于心态。一个从不接触真实工具的评估套件是对提示词的测试,而不是对智能体的测试。请在你的头脑中以及团队的仪表盘上据此进行命名。将模拟套件称为“提示词评估(Prompt evals)”,将录制套件称为“工具交互评估(Tool-interaction evals)”,将实机冒烟测试称为“智能体评估(Agent evals)”。三个名称对应三个层面,每个层面都在其所处的位置进行测试。事故率将追随真正衡量它的指标,而差距最终将会缩小。

References:Let's stay in touch and Follow me for more thoughts and updates