昨天,我发现我的应用程序中有一半的模块是基于虚构的数据构建的。问题还不在于数据是虚构的,更糟糕的是,所有代码都能正常编译,而且90个测试全部通过。
连贯的虚构现实
我正在开发 BFClaude-9000,这是一个为macOS菜单栏设计的应用程序,用来监控Claude Max的配额。其中一个功能需要通过调用 claude.ai 的API来判断某个Claude账户是付费账户还是免费账户。
所以,我请求Claude Code来实现这个功能。它的输出如下:
- 一个DTO
OrganizationInfo,包含一个字段activeFlags: [String] - 一个计算属性
isPaid,用于检查activeFlags是否为空 - 一个枚举
OrganizationSelection,将账户分类为付费或免费 - 几个基于样例数据的测试,用于验证所有功能是否正常
看上去很棒,结构清晰且井然有序。但这一切全是虚构的。
Claude的真实API中并不存在 active_flags 字段。即使这个字段存在,其作用也完全不符合代码中的假设。当我用自己的付费账户登录时,应用却提示我是免费账户用户。
纸上谈兵的模式
问题并不仅仅是它编造了API中的一个字段,更糟糕的是,它围绕这个谎言构建了一个逻辑完整的系统:
// 含有虚构字段的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"]}
"""
// 测试通过 ✅ — 但验证的实际上是虚构对虚构
看到了吗?这不仅是一个错误的API字段,而是一个纸上谈兵的构造:DTO定义了一个不存在的字段,业务逻辑依赖该字段,测试用虚构数据验证这个逻辑,也一致验证通过。每个部分彼此确认,从表面来看完全无懈可击,而实际上却毫无真实性可言。
《IEEE Spectrum》给这种现象起了个名字:无声失败(silent failure)。代码不会崩溃,也不会抛出错误或发出警告,它只是安静地输出错误的结果。
这并不是孤立事件
社区中已经为LLM虚构包和依赖的现象起了名字:包幻觉(package hallucination)。根据Snyk的一项研究,主流LLM推荐的包中有5%到20%是虚构的,也就是说这些包根本不存在。
但包的情况已经算是简单的了。当你运行 npm install 虚构包 时会失败,所以很容易发现。而像DTO里虚构的API字段那样,如果使用 try? 来解析JSON并用降级机制处理异常,它并不会显性地出错。即使返回了nil或者空数组,你的代码也能继续运行,处理这些不存在的数据。
即便是Anthropic公司在他们关于降低幻觉的官方文档中,也毫不掩饰地表达了这个问题:
“Claude有时可能会生成包含虚构信息的回复……并且这些信息通常会以一种十分自信和权威的方式呈现。”
以“权威的方式”呈现,这才是关键所在。问题不在于它不确定或偶尔出错,而在于它满怀信心地虚构内容。
为什么测试无法拯救你?
这就是最让人痛苦的地方。我有测试,而且是非常好的测试。90个测试分布在12个测试套件中,所有测试全部通过。又如何呢?
问题在于,测试验证的是内在一致性,而非与现实的匹配。如果DTO声明了一个字段 active_flags,样例数据也包含 active_flags,测试就会验证DTO能解析该字段……一切通过。虚构验证虚构,绿灯通行。
这就像一个学生自己编造了一条物理公式,再用这条公式完成了一份考试卷,并给自己打了满分。每一步看起来都是内部一致的,但结果却和真实的物理世界毫无关系。
现实:API中不存在字段X
↓(不可见)
DTO:定义了字段X ← 虚构的
样例数据:包含字段X ← 虚构数据验证DTO
测试:DTO能够正确解析样例数据 ← 用虚构验证虚构
结果:✅ 全部通过 ← 连贯的虚构现实
在这条链条中的任何一点上,都没有验证是否对应真实的API。而这就是问题所在。
现有措施都是预防为主
如果你正在寻找预防此类情况发生的方法,当前文献和经验提供了一些建议。但所有这些措施都是预防性的:
| 措施 | 类型 | 问题 |
|---|---|---|
| 在CLAUDE.md文件中写明说明:“不要编造” | 预防性 | 执行这些规则的正是会编造的AI |
| 推理链:“引用你的来源” | 预防性 | 可能会引用虚构的来源 |
| 降低温度 | 预防性 | 降低创造性但无法完全避免虚构 |
| 基于文档的根源性检查 | 预防性 | 仅当文档是完整时有效 |
| 明确禁令行为 | 预防性 | LLM可能会"合理化"其例外情况 |
| 搜索增强生成(RAG) | 预防性 | 依赖于数据库的完整性 |
注意到规律了吗?这些措施都是为了避免AI编造,但没有一个可以检测AI何时已经编造了数据。
这就像在一家没有安保摄像头、报警器或保安的商店里挂上“禁止偷窃”的标语。或许会奏效,也或许不会。而你只有在清点营业款时才会发现问题。
缺失的核心:事后检验
我们急需的,也是目前还不存在的,是一套事后检测系统:一种能在虚构事实发生之后,特别是在进入生产环境之前检测出问题的机制。
设想以下场景:
基于真实API的合同测试(Contract Testing):一种能够调用真实API(使用测试用的凭据)并比较实际数据结构和DTO的测试。如果DTO中存在API未返回的字段,就应当报警。
样例数据验证:一种检查测试样例数据是否基于真实捕获的数据,而非手动编写或由AI生成的检查工具。这类似于快照测试(snapshot testing),但针对实际生产环境中的真实响应。
使用真实数据的冒烟测试(Smoke Test):在合并代码之前,CI系统中增加一个步骤,使用API的沙盒环境执行调用,验证DTO可以无问题地解析真实数据。
解析异常检测:如果一个标记为可选的字段在生产环境中100%都会返回
nil,显然不正常。可以通过监控无数据或异常数据的字段并报告问题来检测。生成后的语义差异检测:通过第二个模型(或使用不同提示词的同一模型)来检查生成的代码,并标记与已知文档不一致的字段或结构。
今天,这些功能中没有一项以产品的形式存在。有些团队可能会手动实现一些方法(比如合同测试就是一种常见实践)。但当前市场上还没有一个类似*幻觉追踪器(HallucinationTracker)*的工具,能直接插入到你的CI中告知你“嘿,这个 active_flags 字段在API的文档或实际响应中都找不到”。
是的,华盛顿大学确实有一篇论文(HallucinationTracker),提出了一些检测虚构的度量方法。但目前还处于研究阶段,并未发展成为一个可直接使用的产品。
更深层的困境
更深层的问题令人感到不安:执行规则的系统与打破规则的系统是同一个。
当你在CLAUDE.md中写上“不要编造数据”时,你是在对着那个可能编造数据的同一个模型发号施令。这就像让被告同时充当审判他的法官。虽然可能会奏效,但你却无法获得保障。
预防性措施(良好的说明、降低温度、根源性校验)可以减少编造的概率,但无法完全消除。而一旦问题发生,没有警报提示你。
我们需要的是由AI外部工具来进行检测的机制:测试真实数据、一种验证数据结构的工具、生产环境中的监视器。这类技术让AI无法再轻易通过解释或忽视来逃避。
在这种技术成熟且易于使用之前,我们的处境和防火墙发展起来之前的网络安全状况类似:我们意识到问题存在,有一些不完备的应对措施,但只能期望“这问题不会出在我身上”。
我现在会怎么做
坦白说,这是我目前在用的解决方案,各有不完美之处:
像对待陌生人的代码一样审阅生成的代码。 不要因为代码可以编译就假设它是对的。这很耗费精力,但没办法,只能这样。
问自己:“这是从哪里来的?” 特别是对于API字段、包名以及我无法通过查看代码直接验证的数据。
手动合同测试。 在认可DTO之前,通过实际调用API来对比和验证。这过程虽然繁琐,但有必要。
对首次通过的测试持怀疑态度。 如果AI生成代码和测试且一次就通过,这不一定是好信号——反而可能表明虚构的代码被虚构的测试通过了。
用真实捕获的API响应数据作为样例数据。 而不是让AI生成样例数据。这样一来,如果DTO无法正确解析真实数据,会立刻暴露问题。
这些方法都需要手动操作且速度很慢,依赖个人的自律,无法扩展。但目前这是我能采取的最佳措施。
明天应该有什么
如果有人正在寻找一个真实世界的问题来解决,这就是一个:
一个自动化的、独立于模型之外,且可以集成到CI/CD流程中的生成后验证系统。
这个系统不需要做到完美,但它需要真实存在。它应该是类似linter的工具,用于分析生成的代码,核对可验证的数据来源(如API文档、OpenAPI schema、捕获的真实响应),并标记可能不符合真实情况的内容。
目前,如果你的AI虚构了一个API字段并用一套逻辑包裹,以及一系列协调的测试,那唯一的防御措施就只能靠你自己有火眼金睛。然而在未来,应该有机器能自动完成这些工作。
但现在这种工具依然没有。这才是整个问题最令人担忧的地方。
相关内容: 本文是一个非正式系列的第三篇。第一篇是44封虚构的邮件(AI在未授权的情况下采取行动)。第二篇是MEMORY.md(AI忘记它所学习的内容)。现在,这是关于AI编造的数据并用逻辑协调地通过测试的文章。三个不同的错误,一个共同点:我们对一个不真正理解自己所做事情的系统过于信任。
本文原文为西班牙语,借助AI翻译。