你构建了一个内部工具,让团队可以在不调用模型 API 的情况下,对一个由 LLM 驱动的功能运行 28 个单元测试。你通过将模型封装在一个可模拟(fakeable)的接口中,并添加了确定性、启发式和基于 LLM 的三层评估来实现这一点。
标准的断言在 LLM 生成文本时就会失效。同一个提示词(prompt)在每次运行时都可能产生不同的句子,因此即使模型表现正确,assertEqual(output, expected) 也会标记为失败。大多数工程团队要么在没有任何验证的情况下发布功能,要么试图测试模型本身,将一个不断变化的目标视为静态库来对待。
为什么这个问题很重要
LLM 现在已深入到面向客户的工作流中——邮件外联、支持回复、内容生成。一个幻觉事实或泄露的标识符就可能损害品牌声誉、暴露私密数据或触发合规性违规。如果没有可靠的测试策略,团队就会在追逐不稳定的失败(flaky failures)上浪费时间,或者发布仅在生产环境中才会暴露的 Bug。
方法:缩小模型的职责范围
第一步是限制 LLM 实际执行的操作。在作者的系统中,模型仅负责起草外联消息。所有的路由逻辑、状态管理和安全检查都保留在普通代码中。通过将模型限制在单一且定义明确的输出中,周围的系统可以保持确定性和可测试性。
为了实现这一点,LLM 位于一个提供者接口(provider interface)之后,允许在测试中使用模拟版本(fake version)。在生产环境中,实现调用外部 API;在测试套件中,一个轻量级的模拟对象(fake)会返回预设的响应。由于其余代码仅与接口交互,整个工作流都可以通过从不接触网络的单元测试来进行演练。其结果是一个可预测的核心,可以通过这 28 个测试进行验证。
一个诚实的评估框架
即使缩小了范围,模型的输出仍然具有非确定性。因此,作者构建了一个三层评估框架,每一层处理不同类型的风险。
第 1 层 – 确定性检查 简单的正则表达式规则可以捕捉具体的错误,例如错误的建筑 ID 或禁止使用的标记(tokens)。这些检查速度很快,并提供二元的通过/失败结果。
第 2 层 – 启发式检查 脚本会寻找幻觉数字或日期,标记明显的虚假事实。它们会漏掉缺乏数字线索的虚假声明,作者也公开承认了这一局限性。
第 3 层 – LLM 裁判 第二个模型会对语气和专业性进行评分。由于这一步依赖于另一个概率系统,因此它仅用于确定性规则无法实现的感性/主观方面。
评估框架的关键在于用于评估的数据集。作者编码了已知的失败模式——特定的陷阱和领域知识——因此该框架测试的正是实践中出现过的错误。它不是一个神奇的“万能工具”,而是一个有针对性的安全网。
这对团队意味着什么
- 保持 LLM 的职责精简。 职责越少,隔离和测试就越容易。
- 将路由、状态和安全逻辑放在代码中。 传统的逻辑保持确定性且完全可测试。
- 通过可模拟的接口暴露模型。 单元测试无需外部调用即可运行,保持测试套件快速且可靠。
- 分层进行评估。 从确定性规则开始,针对已知的幻觉添加启发式检查,并将 LLM 裁判保留用于主观质量检查。
- 明确局限性。 没有哪一层能保证完美;该框架只能捕捉你明确编程要求它检测的内容。
反方观点:你仍然无法对模型本身进行单元测试
作者承认模型是一个移动的目标。即使是 LLM 裁判层也继承了它试图评估的同一种非确定性。因此,该系统永远无法保证在发布前能捕捉到每一个幻觉或策略违规。这种方法是降低风险,而非消除风险,它依赖于团队随着新失败模式出现而保持评估数据更新的能力。
总结
你无法编写一个断言 LLM 精确输出的经典单元测试,但你可以构建一个系统,使模型的影响力受限、接口可替换,并且其输出通过分层的、透明的检查进行筛选。这种组合将一个原本不稳定的组件转变为大型、可测试应用程序中可预测的一部分。
