跳到主要内容

AI 的测试金字塔倒置:为什么单元测试是 LLM 功能的错误投资

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的团队上线了一个新的 LLM 功能。单元测试全部通过,CI 是绿色的,你部署了。然后用户开始反馈 AI "就是不好用"——回答格式奇怪,智能体选错了工具,在多步骤任务进行到一半时上下文丢失。你查看测试套件,它仍然是绿色的。每个测试都通过了。但这个功能是坏的。

这不是运气不好,而是当你把确定性测试哲学应用于概率性系统时必然发生的结果。经典测试金字塔——宽泛的单元测试底座、较小的集成测试中间层、狭窄的端到端测试顶端——建立在一个如此根本的假设之上,以至于没有人会把它写下来:代码每次都做同样的事情。LLM 在每个层面都违反了这个假设。建立在其上的测试策略需要从头重建。

为什么单元测试对 LLM 功能会带来虚假信心

测试金字塔之所以有效,是因为单元测试廉价、快速且精准。你调用一个函数,断言输出,完成。其隐含的契约是:如果所有单元测试通过,则逻辑是正确的。

当"逻辑"存在于语言模型内部时,这个契约立即失效。考虑一个分类功能的提示词级单元测试:你给模型一个输入,断言它返回 "positive"。测试通过了。你再次运行它,它返回 "positive"。你部署了。在生产环境中,在不同的批处理负载、不同的并发请求,以及困扰 GPU 推理的轻微浮点数不确定性下,同样的输入有 8% 的概率返回 "neutral"。你的测试从未捕获到这一点,因为它在 CI 中是确定性运行的。

LLM 中的非确定性比大多数团队意识到的更为普遍。即使在 temperature=0 时,现代推理服务器也会根据并发负载动态调整批处理大小,对于相同的输入,根据同时运行的其他内容产生不同的输出。测试环境总是"安静的"——没有其他任何东西与你的 CI 任务同时运行。生产环境从不安静。

除了非确定性之外,提示词级单元测试还存在一个更根本的问题:它们断言了错误的东西。一个检查 output == "I'd be happy to help with that" 的测试,是在测试精确的 token 序列,而不是语义内容。一个检查 "sorry" not in output 的测试,是在测试表面模式,而不是意图。当你完全模拟 LLM 响应时,你测试的是模型周围的管道,而对模型的实际行为一无所知。开发者记录了正是这种失败:AI 生成的测试在 CI 中干净地通过,同时却在断言错误的结构不变量——对序列化输出的相等性断言而非语义属性、硬编码的时间戳、掩盖真实时序和重试行为的 Mock。

提示词级单元测试最糟糕的结果不是它们失败——而是它们在隐藏真实失败的同时通过了。它们制造出一种正确性的虚假信号,推迟了问题的发现,直到用户发现它们为止。

故障实际发生在哪里:工具边界

如果单元测试无法捕获重要的故障,那些故障实际上在哪里发生?对于大多数 LLM 应用,答案是模型与外部世界之间的边界:工具调用、API 调用、检索结果、数据库写入。

这是集成层,也是 AI 系统中投入测试精力回报最高的地方。原因如下:LLM 是一个概率性决策者,它的大多数决策都体现为关于调用哪个工具以及传递什么参数的选择。当模型选择了错误的 API 端点、生成了格式错误的参数、幻觉出了 Schema 中不存在的字段名,或者调用了它从训练数据中记住的已弃用端点时——这些失败是可观察的、可测试的,并且是有实际影响的。

工具边界的集成测试能够捕获单元测试在结构上看不到的一类故障:

  • 当用户询问退货时,模型持续调用 search_orders 工具,但正确的工具是 search_return_requests——一种只有在两个工具同时出现在上下文中时才会显现的工具选择失败。
  • 当工具 Schema 要求 {"date": "2026-04-17"} 时,模型生成了 {"date": "April 17"}——一种格式不匹配,在测试中悄然无声,但在生产中会抛出 400 错误。
  • 模型链接了三个各自成功的工具调用,但以一种使第四次调用上下文损坏的方式积累了状态——一种对每次调用的单元测试不可见的顺序状态失败。

这里的实用方法是录制重放(record-and-replay):对真实 API 进行一次真实工具交互的捕获,然后在 CI 中确定性地重放它们。这消除了传统 AI 集成测试中的 Mock 与现实之间的差距。录制的响应是真实的,被测试的智能体行为是真实的,并且在初始录制之后,测试仍然是快速且免费的。当新的模型版本或提示词变更导致智能体对相同的录制输入进行不同的工具调用时,测试会捕获到它。

系统级行为评估:真正预测用户体验的信号

集成测试告诉你智能体是否正确执行。行为评估(Behavioral evals)告诉你智能体是否做了正确的事。这是两个不同的问题,只有第二个问题才能预测用户满意度。

行为评估在系统级别运行:给定一个真实的用户输入,完整的系统——模型、工具、检索、编排——是否产生了合适的输出?"合适"有意不是二元意义上的"正确"。对于"总结这份合同并标记不寻常的条款",没有唯一正确的答案。有许多可接受的答案和许多不可接受的答案。行为评估衡量的是你的系统属于哪个类别,针对一个具有代表性的输入样本。

这就是 LLM-as-judge 评估发挥作用的地方。你使用第二个模型来评估第一个模型的输出,根据一个评分标准打分:响应是否解决了用户的实际目标?它是否包含幻觉事实?工具选择序列是否合理?运行多轮评分可以平滑评审员自身的非确定性。结果是你的评估集上质量分数的分布——不是二元的通过/失败,而是一个比率。

模型级评估和系统级评估之间的区别在操作上很重要。如果你的系统级分数下降,组件分数会揭示退化发生在哪里:检索质量下降、工具选择准确率下降,还是输出格式化中断。没有系统级评估,你就没有可以触发告警的基线。有了它们,行为分数下降 3 点就是一个告警条件,而不是"嗯,感觉不对"的观察。

行为评估也是模型升级后的主要回归检测机制。当你的 LLM 提供商悄悄更新底层模型——他们确实会这样做,通常不事先通知——你的集成测试很可能仍然通过(API 接口没有改变),但行为质量会发生变化。只有针对一组精心策划的代表性任务持续运行的系统级评估才能捕获到这一点。

倒置的分配:分布应该是什么样的

经典测试金字塔将精力分配为一个三角形:许多单元测试、较少的集成测试、最少的端到端测试。对于 LLM 功能,分配向顶端倒置。以下是一个实用的分解:

确定性技术测试(底层——每次提交时运行,成本最低): 覆盖 LLM 周围的脚手架:重试逻辑、超时处理、工具输出的 Schema 验证、提示词模板渲染、Token 预算执行、输出解析。这些测试完全模拟 LLM,因为它们不是在测试模型行为——它们是在测试包装模型的代码。它们应该快速且廉价,并且应该存在。但它们无法告诉你这个功能是否真正有效。

录制重放集成测试(中间层——在提示词或工具变更时运行,中等成本): 使用录制的真实 API 响应覆盖智能体与外部系统的交互。当任何可能影响工具选择、参数生成或多步骤序列的内容发生变化时运行:提示词编辑、模型升级、Schema 变更、新工具添加。这些能够捕获破坏真实工作流的一类故障,而无需在 CI 中进行实时 API 调用。

行为评估(顶层——针对生产流量样本持续运行,成本较高): 覆盖针对真实输入的端到端任务完成情况。对 1-5% 的生产流量持续运行,并在每次部署时针对精心策划的黄金集运行。这是信号。当它们下降时,无论下层报告什么,功能都出了问题。

与经典金字塔的转变不在于写更少的单元测试——而在于认识到单元测试只属于脚手架层,而不属于模型行为层。将提示词级单元测试写得好像在测试纯函数的团队,是在把精力花在无法捕获用户实际会遇到的故障的测试上。

这带来的组织摩擦

即使对于 LLM 功能,团队默认使用单元测试是有原因的:单元测试熟悉、写起来快,CI 集成也很简单。行为评估需要新的基础设施——评估数据集、评审提示词、评分流水线、阈值跟踪。许多团队把这个基础设施当作奢侈品,把它留到"v1 上线之后"再建。

这恰恰是本末倒置的。在功能上线之前建立的评估确立了基线。在回归之后建立的评估是在追赶已知的失败。那些在生产中报告 AI 功能最稳定的团队,是那些将评估基础设施视为基础性的团队——不是因为评估在哲学上重要,而是因为没有它们,团队根本无法知道他们的变更是否让功能变得更好或更差。

对于新 LLM 功能的实际最低要求:50-100 个具有人工验证预期输出的代表性任务输入、一个根据定义标准对输出打分的评审提示词,以及一个在行为分数低于阈值时使构建失败的 CI 门控。这不需要专门的 ML 平台,它需要的是团队已经应用于集成测试的同等严格性——应用于 LLM 故障实际发生的层面。

这在实践中意味着什么

停止编写断言精确模型输出的测试。它们不会持续通过,当它们通过时,也是在断言错误的东西。只为包装模型的代码路径编写确定性测试:重试处理器、Schema 验证器、Token 计数器。

大量投资于工具边界。录制真实的 API 交互并在 CI 中重放它们。这是智能体系统中回报最高的测试层,也是大多数重要故障出现的地方。

持续运行行为评估。将系统级质量分数的下降视为部署阻塞。在上线功能之前建立评估数据集,而不是在第一次用户投诉之后。

经典测试金字塔描述了如何测试确定性软件。LLM 功能不是确定性软件。测试分配策略必须反映故障实际所在的位置——而不是金字塔的底部。


实际要点:如果你的 LLM 功能有 200 个单元测试和 5 个评估,你的分配是反的。把它翻转过来。

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