测试不可测之物:LLM 驱动 API 的集成契约
你的测试套件通过了。CI 是绿色的。你发布了新的 prompt。三天后,一个用户反馈你的 API 正在返回带有尾随逗号的 JSON——而你的下游解析器已经静默丢弃数据长达 72 小时。你从没为此写过测试,因为 LLM 在开发环境中"总是"返回合法的 JSON。
这就是毁掉 LLM 驱动产品的失败模式:不是灾难性的模型崩溃,而是确定性测试套件在结构上无法捕获的安静、间歇性的降级。根本原因不是懒惰——而是当你的系统产生非确定性的自然语言时,"期望 == 实际"的整个范式就失效了。
修复这个问题需要重新思考你在测试什么,以及对于 LLM 驱动的 API 而言"通过"究竟意味着什么。那些弄明白这一点的工程师并没有编写更聪明的相等性断言——他们编写的是根本上不同类型的测试。
为什么 Temperature=0 救不了你
标准建议是 将温度设为 0 以获得可重复的输出。这个建议在一些微妙但重要的方面是错误的。
2025 年的一项关于 LLM 非确定性的研究发现,在相同设置下运行之间的精度差异高达 15%,最佳和最差结果之间的性能差距达到 70%。罪魁祸首是 GPU 计算中的浮点非结合性:不同的硬件核心以不同的顺序执行操作,产生数值上不同(尽管语义上相似)的中间状态。当负载均衡将你的请求路由到不同的服务器时,输出就会改变。
OpenAI 的 seed 参数明确只保证"大致确定"的输出——而不是完全相同的输出——因为提供商保留了在调用之间更新基础设施的权利。如果你的测试套件今天运行一个 prompt 并记录精确输出,那么下周在没有你这边任何变化的情况下,该快照可能已经过时。
更深层的问题是,即使输出是真正相同的,精确匹配测试仍然是错误的方法。考虑一个摘要端点:"报告显示 Q3 收入下降"和"根据报告,Q3 收入有所下滑"是同一个正确答案,但字符串相等性会将其中一个标记为失败。自然语言没有规范形式。
不认识到这一点的工程师会写出同时过于脆弱(在有效的改述上失败)和过于宽松(在带有细微事实错误的输出上通过)的测试。你需要一个不同的测试模型。
反转原则:测试绝不能发生什么
对 LLM 系统最持久的测试不是关于输出必须说什么的断言——而是关于输出绝不能做什么的断言。这种反转是使 LLM 测试可处理的核心洞见。
一些 具体例子使这一点变得清晰:
- 客户支持机器人绝不能泄露内部系统提示内容
- 结构化数据提取端点绝不能返回无效的 JSON
- 摘要服务绝不能引入源文档中不存在的事实声明
- 代码生成工具绝不能包含明显的安全漏洞(SQL 注入、硬编码凭据)
- 分类端点绝不能返回定义枚举之外的标签
这些不变量易于指定、易于断言,不需要你预见每一个可能的有效输出。它们编码了实际导致生产事故的失败模式,而不是学术上的正确性问题。
实现方式因不变量类型而异。格式不变量是确定性的:解析 JSON、根据你的模式验证、检查枚举成员资格。语义不变量需要启发式方法(已知秘密模式的正则表达式、幻觉实体的 NER)或次级 LLM 评估。安全不变量通常两者都需要:轻量级分类器用于延迟敏感路径,加上针对精心策划的对抗性数据集的定期深度评估。
当你将这些作为 CI 门(而不仅仅是监控警报)连接时,你会在发布之前而不是影响用户数小时后捕获回归。
接口层的契约
对于任何消费或生成 LLM 内容的 API,你需要两层规范:结构契约和行为契约。大多数团队只构建第一层。
结构契约是你的 OpenAPI 规范或 JSON 模式。它定义了哪些字段存在、它们携带什么类型、什么枚举值有效。对于同步 LLM API,这一层应该在响应离开你的服务之前强制执行——不是通过希望模型遵循你的提示,而是通过解析和验证输出,如果不符合则明确重试或失败。如果你的 LLM 在你要求 JSON 时返回散 文,那是你在代码中处理的可恢复错误,不是你在 CI 中捕获的测试失败。
行为契约更难。它定义了对任何有效响应都必须成立的属性:答案必须基于提供的上下文、语气必须匹配定义的角色、长度必须在 UX 可接受的范围内。这些属性无法被静态验证——它们需要评估。
实际模式是将行为契约编码为你的 CI 评估器在推广新提示或模型版本之前检查的评分标准。PromptFoo 和 DeepEval 等工具允许你分别以 YAML 配置或 Python 断言的形式表达这些,然后针对固定的代表性输入数据集运行它们。构建失败不是因为输出与快照不匹配,而是因为它在你明确定义的属性上得分低于阈值。
这种方法让你能够以与有回归测试套件的代码变更相同的信心来发布模型更新和提示更改——但没有精确匹配断言的脆弱性。
基于属性的测试实践
基于属性的测试(PBT)是为确定性系统开发的——给我一个输入生成器,我会找到违反你声明属性的输入。对于 LLM,这个想法适用但执行方式显著改变。
要测试的有用属性分为几类:
