我在我的网站上运行了一个数字孪生(digital twin)。它负责回答关于我的生活和技能的问题。我给它定了一条严格的规则:绝不捏造事实。如果有人问及我不具备的技能,它必须承认自己不知道。几个月来,我一直以为这个系统运行良好。我偶尔会进行手动测试,答案看起来都很可靠。直到我构建了一个正式的评估框架(evaluation harness)。数据结果非常残酷:在 35 个问题中,有 9 个包含了彻头彻尾的谎言。在 8 个旨在设计为无法回答的问题中,模型仅拒绝了 4 个。我的防幻觉提示词(anti-hallucination prompt)大约有四分之一的时间会失效。我当时正在向用户交付一个会撒谎的产品。
一个极其简单的检索设置
我并没有部署 Pinecone 或任何重量级的向量数据库。整个设置都基于一个简单的 JSON 文件。我的代码将我的个人资料拆分为不同的章节。当问题到来时,系统会计算查询内容与每个文本块之间的余弦相似度(cosine similarity),选择最匹配的部分,并将它们作为上下文塞进提示词中。然后,模型严格根据在该窗口中看到的内容生成答案。
对于一个服务于狭窄事实集的个人小网站来说,这种方法速度极快,且几乎没有任何成本。没有前往远程向量存储的网络往返,没有索引开销,也没有复杂的编排。你读取文件,计算分块得分,构建提示词,然后搞定。但后端的简单并不能保证输出的诚实。当模型决定“即兴发挥”时,轻量级的流水线仍然会产生严重的问题。“这是上下文”与“这是我对此的回答”之间的鸿沟,正是幻觉滋生的地方。你可以给模型一段关于你工作经历的文字,但它仍然可能针对一种你从未接触过的编程语言,给出一个自信的编造答案。
击碎我信心的数字
几个月来,我一直认为手动抽检足以覆盖所有情况。我会打开聊天窗口,问一个我已经知道答案的问题,如果回答看起来正确,我就点点头。这就是我的测试策略。因为它是我亲自在使用界面,所以感觉很周全。但事实并非如此。
当我终于写出一个可以系统化运行的评估框架时,情况发生了变化。测试套件向这个数字孪生抛出了 35 个问题。其中 9 个答案包含谎言。我还加入了 8 个在我的个人资料中完全找不到答案的问题。模型本该拒绝所有这些问题,但它只拒绝了 4 个。我精心设计的防幻觉提示词——那个包含了“绝不捏造事实”等绝对化语言的提示词——大约有 25% 的失败率。四分之一。这可不是什么舍入误差,这是一个有缺陷的产品。
停止使用“友好型”问题进行测试
你无法通过仅仅使用自己的产品来发现 Bug。你必须通过尝试破坏它来发现 Bug。我的手动测试太“友好”了。我只问那些我知道确切答案的问题,这意味着我在潜意识里引导模型走向安全区域。我从未探测过边界。我从未问过我希望拥有但并不具备的技能,也从未问过从未发生过的经历。
真正的测试需要对抗性意图(adversarial intent)。你必须设计旨在让 AI 出错的问题。你希望它在实验室里出错,这样它才不会在访客面前出错。一个只能证实你既有信念的测试套件,不过是一个包装精美的演示(demo)。如果你没有主动制造边缘情况(edge cases)和陷阱问题,你就不算在测试,你只是在祈祷。
两个至关重要的错误
提示词并不能提供保证。 一段告诉 AI 不要产生幻觉的长篇详细指令,本质上只是一个伪装成命令的建议。模型可能在大多数时间里遵循它,但一旦统计压力将其推向其他方向,它就会忽略该指令。Temperature(温度)、Token 概率以及训练数据的分布,其权重都比系统提示词中的一句话要重得多。你必须用数据来衡量服从性,而不是靠希望。强有力的指令并不等同于经过验证的事实。它只是一种请求,而请求是可以被拒绝的。如果你的整个安全策略仅仅依赖于措辞严厉的提示词,那么你构建的护栏就像纸巾一样脆弱。你需要一个评估框架,去统计模型在什么条件下服从,以及在失败时为什么会失败。数字并不在意你的语气。
评估循环存在缺陷。 这是一个差点让我栽跟头的隐蔽陷阱。我最初的测试工具运行了两次检索过程。第一次运行获取上下文,用于与标准答案 (ground truth) 进行比对。第二次运行获取上下文,用于实际的答案生成。在实践中,这意味着评判者看到的文本块可能与模型看到的文本块不同。评判者是在根据 AI 可能从未接收过的数据来对答案进行评分。对错误的输入进行评估,比不进行评估更糟糕。它会给你一种虚假的安全感。你看着分数,看到很高的通过率,然后放松警惕。与此同时,你的用户正在
