昨天我发现我的应用程序中有一半模块基于的是编造的数据。不是因为某个迷糊的初级开发者,而是我的AI。

最糟糕的不是它编造了内容。最糟糕的是所有代码都能编译,90个测试全部通过

连贯的虚构

我正在构建BFClaude-9000,这是一个macOS菜单栏应用,用于监控Claude Max的配额。该功能的一部分需要通过调用claude.ai的API来区分Claude账户是付费还是免费。

我让Claude Code实现了检测功能。它实现了。它交付给我:

  1. 一个包含activeFlags: [String]字段的OrganizationInfo DTO
  2. 一个检查activeFlags是否非空的计算属性isPaid
  3. 一个将组织分类为付费和免费的枚举OrganizationSelection
  4. 带有验证所有功能正常工作的测试数据的测试

漂亮。简洁。结构良好。全是编造的。

active_flags字段在Claude的真实API中根本不存在。或者如果存在,也不像代码假设的那样工作。当我用付费账户登录时,应用告诉我我的账户是免费的。

纸牌屋模式

阴险的不是它撒谎说API有某个字段。而是它围绕这个谎言构建的完整系统

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 带有编造字段的DTO
struct OrganizationInfo: Decodable {
    let uuid: String
    let name: String
    let activeFlags: [String]  // ← 这个不存在

    var isPaid: Bool { !activeFlags.isEmpty }
}

// 依赖编造字段的逻辑
enum OrganizationSelection {
    case paid(id: String, name: String)
    case noPaidOrg    // ← 这个状态不应该存在
    case noOrgs
}

// 用验证编造内容的测试数据进行测试
let paidOrg = """
{"uuid": "abc", "name": "Acme", "active_flags": ["pro"]}
"""
// 测试通过 ✅ — 但这是在验证虚构对虚构

你看到了吗?这不是一个字段放错了位置。这是一个纸牌屋:DTO定义了一个虚假字段,逻辑依赖这个字段,测试验证逻辑使用同样虚假的测试数据正常工作。每个部分都在确认其他部分。一切都说得通。没有什么是真实的。

IEEE Spectrum为此有一个名称:静默失败。代码不会崩溃,不会抛出错误,不会触发警报。它只是静默地做错误的事情。

这不是孤立案例

事实证明,社区已经有了一个名称来描述LLM编造包和依赖项的情况:包幻觉Snyk的一项研究发现,主要LLM推荐的包中有5%到20%是编造的。不存在的包,被推荐使用。

但包的情况是简单的情况。你运行npm install invented-package,失败了,你就知道了。在DTO中编造一个字段来解析带有try?和优雅降级的JSON…那不会失败。它会工作。返回nil或空数组。你的代码继续运行,操作幽灵数据。

Anthropic本身在关于减少幻觉的文档中毫不含糊地说:

“Claude有时会生成包含编造信息的响应…以自信、权威的方式呈现。”

以"权威"方式呈现。这是关键。它不是怀疑和犯错。而是以完全的确定性断言刚刚编造的东西。

为什么测试救不了你

这里是痛点。我有测试。好的测试。12个测试套件中的90个测试。全部绿色。那又怎样?

问题是测试验证的是内部一致性,而不是与现实的对应。如果DTO说字段叫active_flags,测试数据有active_flags,测试检查DTO解析测试数据…全部通过。虚构对虚构。荧光绿。

这就像学生编造一个物理公式,基于这个公式写考试,然后给自己打10分。每一步都是内部连贯的。结果与现实毫无接触。

现实:    API中不存在字段X
   ↓ (不可见)
DTO:     定义字段X         ← 编造的
测试数据: 包含字段X        ← 为验证DTO而编造的
测试:    测试数据解析正常    ← 验证编造对编造
结果:    ✅ 全部绿色      ← 连贯的虚构

在这个链条中没有任何点检查真实的API。这就是漏洞。

所有当前措施都是预防性的

如果你寻找可以做什么来避免这种情况,文献和经验会给你一个措施列表。全都是预防性的

措施类型问题
CLAUDE.md中的指令:“不要编造”预防性由同一个说谎的代理执行
思维链:“引用你的来源”预防性可能引用编造的来源
低温度预防性减少创造性,不消除编造
用文档接地预防性只有在你有正确文档时才有效
明确禁止预防性LLM可能"合理化"例外
RAG (检索增强生成)预防性依赖于数据库的完整性

注意到模式了吗?所有这些都试图防止AI编造。没有一个检测它何时已经这样做了。

这就像在没有摄像头、没有警报、没有保安的商店里贴"禁止偷窃"标志。可能有用。可能没用。直到你清点收银台才能知道。

缺失的:反应性检测

我们需要的和今天不存在的反应性措施:在编造发生之后检测它的系统,理想情况下在它到达生产环境之前。

想象一下:

  • 针对真实API的契约测试:调用真实API(使用测试凭据)并将真实schema与DTO进行比较的测试。如果DTO有API不返回的字段,就报警。

  • 测试数据验证:检查测试测试数据对应于捕获的真实数据而不是手写(或AI生成)数据的linter。类似快照测试但针对生产环境的真实响应。

  • 使用真实数据的冒烟测试:在合并之前,CI步骤对API的沙箱执行调用,验证DTO解析真实数据而不发生静默丢失。

  • 解析中的异常检测:如果可选字段在生产环境中100%返回nil,就有问题。监控器检测总是nil的字段并将它们报告为可疑编造。

  • 生成后语义差异:第二个模型(或使用不同提示的同一模型)审查生成的代码,指出它无法对照已知文档验证的字段或结构。

这些今天都不作为产品存在。一些团队手动实现片段(契约测试是已知实践,例如)。但没有可以插入CI并告诉你"嘿,这个active_flags字段在任何文档或API真实响应中都没有出现"的HallucinationTracker

是的,有华盛顿大学的论文(HallucinationTracker)提出检测虚构的指标。但它在研究阶段,不是你可以brew install的东西。

根本问题

根本问题是深度令人不安的:规则由违反规则的同一系统执行

当你在CLAUDE.md中写"不要编造数据"时,你是在告诉即将编造数据的同一模型。这就像要求被告也做法官。可能有用,但你没有保证

预防性措施(好的指令、低温度、接地)减少了编造的概率。但不能消除它。当它发生时,没有警报响起。

我们需要的是由模型外部的东西进行检测:针对真实数据的测试、schema linter、生产环境监控。模型无法合理化或规避的东西,因为不是模型在执行它。

直到这作为成熟且易于使用的东西存在,我们处在与防火墙之前的信息安全相同的情况:我们知道有问题,我们有部分措施,我们相信"不会发生在我身上"。

与此同时我在做什么

诚实地说,这些是今天对我有效的措施。没有一个是完美的:

  1. 像对待陌生人的代码一样阅读生成的代码。 不要因为能编译就假设它是正确的。这很累,但这是现状。

  2. 问"你从哪里得到这个?" 特别是对于API字段、包名称和任何我无法通过查看代码验证的数据。

  3. 手动契约测试。 在认为DTO正确之前,对API进行真实调用并比较。很繁琐。很必要。

  4. 对一次性通过的测试保持怀疑。 如果AI生成代码和测试,一切都一次性通过,那不是好迹象——那表明它可能验证了虚构对虚构。

  5. 捕获真实响应作为测试数据。 而不是让AI写测试数据,保存API的真实响应并将它们用作测试数据。如果DTO不解析真实响应,立即就会失败。

这些措施是手动的、缓慢的,依赖于我的纪律。不能扩展。但今天它们是我最好的选择。

明天应该存在什么

如果有人在寻找要解决的真实问题,这里有一个:

一个外部于模型、自动化、集成到CI/CD中的后生成验证系统。

不需要完美。需要存在。需要有人构建幻觉的linter等价物:分析生成代码,与可验证来源(API文档、OpenAPI schema、捕获的响应)交叉检查,指出不匹配的内容。

今天,如果你的AI编造一个API字段并用连贯测试包装它,唯一的防御就是你用临床眼光阅读代码。明天,应该有机器为你做这件事。

但今天没有。这是最令人担忧的一切。


相关: 这篇文章是非自愿系列的第三章。首先是44封编造的邮件(未经许可行动的AI)。然后是MEMORY.md(忘记所学内容的AI)。现在,编造数据并将它们包装在通过测试的虚构中的AI。三种不同的失败,一个共同点:我们过度信任一个不理解自己在做什么的系统。