RAG 幻觉护栏:五层设计

五层 RAG 幻觉护栏:检索前拒答、相似度下限、系统提示词、句子闸门与口吻规则,外加调用前的支出检查。

检索增强生成(RAG)并不能阻止模型编造答案,薄弱的上下文甚至可能让它更自信,而不是更谨慎。RAG 的幻觉护栏,大部分应该落在模型周围的代码里。把它们按固定顺序搭成五层,也就是本站助手的做法:

  1. 助手绝不该回答的问题,在检索之前就拒答。

  2. 只把经得起核对的证据交给模型:超过相似度下限的段落,或者在回答关于整体工作的问题时,本站自己的记录。两者都没有时,不调用模型就拒答。

  3. 把系统提示词当作模型唯一可以无视的一层。

  4. 只有通过确定性检查之后,才把回答一句一句放行。

  5. 绝不让助手以它背后那个人的身份说话。

再加上模型调用之前的一道支出检查,这套设计才算完整。

本站和它的助手都由我构建并维护。我不为它公布任何准确率数字,原因写在后面讲衡量的那一节里。

为什么 RAG 仍然会产生幻觉

在 RAG 系统里,检索器从语料库中返回若干段落,语言模型再依据这些段落写出回答。所谓幻觉,就是把虚假或没有依据的陈述,当作事实说出来。

检索会收窄模型可能说出的内容,但并不能给它划出边界。

检索可能让模型更自信,而不是更正确

Google 的研究者把两种情况分开来看:检索到的上下文足以回答问题,以及不足以回答问题。作者在总结这篇发表于 ICLR 2025 的论文时说得很直接:“虽然 RAG 总体上提升了表现,但它自相矛盾地削弱了模型在该放弃作答时放弃作答的能力。”在其中一个例子里,Gemma 在完全没有上下文时给出错误回答的比例是 10.2%,而在上下文不充分时升到了 66.1%。

知道什么时候不该回答是有回报的:作者提出的选择性生成方法,把模型作答时回答正确的比例提高了 2–10%,在 Gemini、GPT 和 Gemma 上都是如此。

top-k 检索器总会返回 k 个段落,不管其中有没有一个是相关的。如果只由模型自己判断这些段落够不够,那么“要不要放弃作答”这件事,就落在了这项研究显示经常判断失误的那个环节上。

模型因为猜测而获得奖励

Kalai 和同事的论点是:“语言模型之所以产生幻觉,是因为训练和评估流程奖励猜测,而不是奖励承认不确定。”当评分不给“我不知道”任何分数时,猜测就能提高成绩。要求模型承认不确定的指令,是在跟这套激励对着干。它有帮助,但不是一项控制措施。

商用法律工具同样出现了幻觉

一些法律研究工具的厂商曾把 RAG 宣传成可以“消除”幻觉。首个针对 AI 法律研究工具的预注册评估发现,LexisNexis 和 Thomson Reuters 的产品出现幻觉的比例各自在 17%–33% 之间。这对法律类产品意味着什么,见法律 AI 幻觉:该构建什么。

五层护栏一览

一个问题依次经过各层护栏:检索之前的拒答模式;检索;相似度下限,低于它时与我无关的问题不调用模型就被拒答;本站带日期的记录,不打分,提供给关于整体工作的问题和低于下限的、关于我的问题(对于来源中没有提到任何职位的其他关于我的问题,则提供我当前的职位);调用前的支出检查;系统提示词;以及逐句放行或中止流式输出的句子闸门。模型在来源中找不到答案时,会用一个固定 token 作答,它会变成固定拒答文案。口吻规则在第 1、3、4 层生效。

层

拦住什么

跑在哪里

触发时会怎样

1. 拒答模式

助手绝不该给出的回答

代码

检索之前返回固定拒答文案

2. 相似度下限

建立在薄弱证据上的回答

代码

固定拒答文案,不调用模型

3. 系统提示词

违规行为,前提是模型愿意配合

模型本身

没有任何东西能独立保证它被执行

4. 句子闸门

无来源的论断、编造的数值、第一人称

代码

流式输出中止,其余内容不再发出

5. 口吻规则

以别人的名义作出的承诺

代码与提示词

通过第 1、3、4 层生效

顺序和层本身同样要紧。两道拒答层都在语言模型被调用之前运行;而唯一依赖模型配合的那一层,也就是提示词,后面紧跟着一道确定性检查。位于下限与提示词之间的支出检查并不能阻止编造,但它属于同一类失败。

第一层:在检索之前先拒答

第一种失败不是缺了一个回答,而是出现了一个根本不该存在的回答。

语料能回答、但助手不该回答的问题

在本站,一个关于费用的问题会检索到服务页面。这段文本确实相关,而依据它作答恰恰就是那个错误:模型从那个页面里拼出的任何数字、日期或承诺,都是一项没有人作出过的表示。相关不等于获准。

所以有些问题在做任何嵌入或检索之前就被拒绝:

  • 合作条件:费用、档期和聘用。

  • 本站刻意不公开的私人信息,例如电话号码、住址、婚姻或兵役状况、出生日期。

  • 臆测:预测,以及与指名道姓的人做比较。

  • 元请求,也就是试图套取或覆盖助手指令的请求。

  • 任务请求:让助手写代码或文字、翻译或解答某样东西,而不是询问已发表的工作。

拒答文案是固定的,不是模型的输出:由模型生成的拒答,模型自己也能反过来把它推翻。嵌入模型(把文本变成向量的那个模型)和语言模型的客户端都是惰性构建的,所以这一层的拒答不会产生任何外部调用。

给你自己的系统列一份清单:哪些问题无论语料里有什么,正确答案都是“不该由这个助手来回答”。

用模式匹配,而不是分类器

拒答集对英语和波斯语是一组正则表达式,对本站其余八种语言是短语清单,全都跑在做过匹配折叠的问题上:转成小写,把阿拉伯字母形式折叠成波斯字母形式,把波斯文和阿拉伯文中的数字折叠成拉丁数字,去掉阿拉伯语的短元音符号,并把波斯语词内的零宽不连字符换成空格。问题还会把我的名字当作代词再试一遍,所以“可以雇佣穆罕默德·帕纳希吗?”会和“可以雇佣他吗?”一样被拒答;追问则会连同前一个问题一起检查。

它不是分类器。一个大多数时候判断正确的分类器,仍然意味着一个本站无法给出的保证:它每判错一次,都是以某个人的名义作出的承诺。

局限很清楚:模式匹配抓不住换个说法的同一个问题。波斯语的拼写变体倒不在这些缺口里。因为折叠会把零宽不连字符变成空格,标准正字法连写的词到达模式匹配时是分开的两段,而每一条波斯语模式都同时接受这种分写形式和不加空格的连写形式,所以 چقدر می‌گیرد 和 چقدر میگیرد 都会被拒答。用清单之外的措辞提出的问题依然会通过,后面几层就是为它准备的:提示词禁止谈费用和档期,闸门会拦住任何含有它认得的第一人称词、或含有来源里没有的数字的句子。这能收窄缺口,但不能堵死它。英语和波斯语以外各语言的清单更短,换个说法的问题在这些语言里更容易漏过去。

第二层:跳过模型调用的相似度下限

检索之后,助手取问题与准备交给模型的那些段落之间的最高相似度。低于下限时,与我无关的问题会被用固定文案拒答,语言模型自始至终不会被调用。这个拒答并非零成本,因为在生产环境里,托管的 API 已经把问题嵌入过一次了;但它省掉了生成调用,于是薄弱的证据永远不会变成一段流畅的回答。关于我的问题走另一条路,下文会讲,因为相似度分数分不清一个关于某人工作的宽泛问题和一个离题问题。

为什么不能给倒数排名融合的分数设阈值

这个检索器是混合式的:稠密向量检索加整词关键词检索,再由倒数排名融合(reciprocal rank fusion,RRF)合并。这套做法如何同时应付波斯语和英语,见波斯语 RAG 的混合检索。

RRF 是合并排名的好办法,却是一个糟糕的阈值对象。在 Cormack、Clarke 和 Büttcher 的定义里,一个文档的分数是它在各个排名列表上 1/(k + r(d)) 的总和,其中 r(d) 是它在每个列表里的名次,而 k = 60 “是在一次先导实验中定下的”。这个分数只由位置构成,所以哪怕语料库全是噪声,也照样有一个排在第一的文档,拿到现有最高的融合分数。给这个数字设下限,衡量的是名次,不是相关性。

下限实际比较的是什么

每个能给出有界相似度的检索器都会报告一个:

  • 向量检索报告余弦相似度,并把它截断到 0 到 1 的范围内。下限用的是与原样提问的相似度;如果是追问,还包括把前一个问题和这个问题连在一起之后的相似度。如果追问的措辞是清单中的某个位置,比如“第二个”,还会把它所指的那一项一并连上:位置从问题中读出,清单从上一个回答中读出,依据本站自己的标题和名称,或回答中加了引号的内容,并告诉模型指的是哪一项。只有位置和清单都能毫无疑问地读出时,才连上那一项;否则改为连上上一个回答的开头。判断依据是措辞,而不是回答:一个恰好说了“第二个”的无关追问同样会连上回答的开头,并可能越过下限;它后面仍有提示词和闸门把关。当问题关于我时,检索还会附上我的名字再搜一次。这有助于给段落排序,但从不计入下限:名字会让任何关于我的问题看起来都接近简历,包括本站回答不了的那些。

  • 关键词检索报告覆盖率:问题中长度在三个字符及以上(中文为两个字)的实义词,以完整单词的形式出现在该段落里的比例。常用词会先按本站的每种语言去掉,而且只有至少两个不同的实义词匹配上时,覆盖率才计入下限。只匹配上一个词,那是巧合。对于只说明指哪一项的追问,比如“详细介绍一下第二篇”,实义词取自本站给那一项的标题或名称。

每个段落保留两者中较大的那个值,下限比较的是所有返回段落中的最大值;决定这个最大值的段落一定在返回结果里。融合分数只决定顺序。

整词匹配很要紧。如果改成匹配子串,“art”会在“start”里被找到,毫不相干的问题就开始像证据了。如果嵌入服务不可达,检索会退回到只用关键词检索,下限依然生效。

相似度分数回答不了的问题

“他最近做了什么?”没有最近邻。它的答案是一份按日期排序的列表,而没有哪个段落长得像这样的列表:在本站的嵌入模型上,这个问题的得分比一个问天气的问题还低。所以,关于整体工作的问题和拒答集一样,用本站十种语言的模式来识别,涵盖我现在或最近在做什么、我的职业经历、项目、研究、写作、教育、技能和服务。这些问题用本站的记录来回答:来自简历、案例研究、文章和服务的条目,每次提问都从数据库读取。有读者语言的译文时,记录就用读者的语言;带日期的记录按各自的日期排序,最新的在前。

记录按规则挑选,而不是按相似度,所以下限不适用于它们。除此之外,它们没有任何豁免:拒答模式最先运行,每条带日期的记录都按本站显示的精度带着自己的日期,而建立在记录之上的每一句话,都要过同一道闸门。

一个关于我的问题,如果模式没有把它引到记录上,也没有任何段落支撑它,得到的会是这些记录的简短概览,而不是拒答,因为在这里错误的拒答等于告诉读者,本站没有涵盖它其实涵盖的内容。如果记录也回答不了,模型会按指示用一个固定的 token 代替正文作答,读者看到的是同样的固定拒答。这样一来,模型的判断只是通向拒答的又一条路,它永远不会把一次拒答变成一个回答。

RAG 的相似度阈值怎么定

没有一个通用的数字。余弦相似度取决于嵌入模型、语言构成和语料库,所以在一个嵌入模型上合适的下限,换一个模型可能什么都拒,也可能什么都不拒。

本站的设计默认值是 0.35,测试套件用一个替身嵌入模型(仅用于测试的替代模型)在这个值上运行。它还没有针对生产环境的嵌入模型做过校准。有一条只跑检索的命令,会把本站的黄金测试集问题和推荐问题送进已部署的嵌入模型,不调用语言模型;只有这次运行通过,生产环境的下限才会改动。它不是一个可以照抄的数值。要定你自己的:

  1. 把助手必须回答的问题和必须拒答的问题,都送进你实际部署的那个嵌入模型,比较两组最高相似度的分布。

  2. 决定你愿意放弃多少个本可回答的问题,来挡住那些必须拒答的问题。在自信的错误答案比没有答案更糟的场合,把下限调高。

  3. 记录这个数字属于哪个嵌入模型、哪个语料库。每往语料库里加一个页面,一个离题问题能匹配到的东西就变了一次——包括这篇文章在内。

本站实验室里的RAG 与原始模型对比演示就是围绕这条边界做的。它没有任何无出处的生成路径:一个与我无关、也没有任何段落支撑的问题,会在触及语言模型之前就停下。

第三层:系统提示词是模型唯一可以无视的一层

提示词依然在做实事。它要求模型只使用随问题一起给出的编号来源,用第三人称、以读者的语言提到我,绝不说明档期、费用或时间安排,绝不编造或计算数字和日期,只对记录标为当前的条目说“目前”,在每一个陈述事实的句子末尾加上来源标记,并在来源覆盖不了问题时用一个固定的 token 作答。

但它无法强制其中任何一条。OWASP 的 LLM07:2025 System Prompt Leakage 条目建议把护栏放在模型之外,因为一个独立的、检查输出的系统“优于写在系统提示词里的指令”。Anthropic 关于减少幻觉的指南,包括让模型可以说“我不知道”,同样坦率:“虽然这些技术显著减少了幻觉,但并不能完全消除它们。”

所以这段提示词从一开始就假定自己有时会被忽略。它的任务是让遵守成为最省事的那条路,而每一条高风险的指令,都有一个确定性的对应检查:

提示词里的指令

确定性的对应检查

每个陈述事实的句子都要带来源标记

闸门检查标记存在,且编号在范围内

绝不编造数字

每个数字、URL 和邮箱都必须出现在该句所引用的来源里

用第三人称写

闸门的第一人称检查

不谈费用和档期

第一层的模式匹配,再加闸门的数值与第一人称检查

有一条指令没有完整的对应检查:面对一个不含数字的论断,“只使用来源里说过的内容”。闸门可以要求这句话带一个有效的标记,却无法判断来源是不是真的这么说。关于时间的规则也没有完整的对应检查。一个把两个带日期的事件排错先后、或把一段旧职位说成最近的句子,并不含可供闸门核对的数值。记录按日期排好序、并标出当前条目,这降低了风险,但消除不了。

第四层:逐句把关流式输出

读者已经读到的句子,收不回来。NVIDIA 的 NeMo Guardrails 文档点明了这个取舍:stream_first 打开时(这是默认值),“如果某个护栏拦截了内容,用户已经收到了这些 token”。本站站在另一边。文本先扣住不发,直到一个句子结束、通过检查,才放行,代价是大约一个句子的延迟。

每个句子面对四项检查,每一项都是关于字符串的事实,而不是关于含义的判断:

  1. 标记在范围内:它指向的是一个确实提供过的来源。

  2. 标记存在:每个实质性句子都要有;一个不含数值的短连接句可以通过。句子长短按空格分隔的词来数;中文不用空格,因此改为按字计数,每三个字算作两个词。

  3. 数值在所引用的证据里:每个数字、URL 和邮箱地址都出现在该句所引用的来源中,比较前会先把波斯文和阿拉伯文中的数字折叠成拉丁数字。

  4. 没有第一人称,因为第一人称意味着助手开始以我的身份说话了。

一旦违规,已经放行的句子留着,违规的那一句永远不会发出,流式输出就此中止——这同时也会取消上游的生成。读者看到的是一段固定文案,说这个回答的其余部分无法由本站的来源支撑,因此已被扣下。标记先检查、后删除,所以读者看到的是连贯的行文,不是一份参考文献表;而被截断的回答会得到另一种提示:一个说到一半的句子,不等于一个没有依据的句子。

有依据不等于属实。数值是拿该句所引用的来源来比对的,而不是拿论断本身:引用了两个来源的句子,可以从其中任一个取数值;一个不含数值的句子,只要标记编号在范围内就能通过。闸门证明不了一个论断是正确的,甚至证明不了被引用的那段文字支持它。OWASP 的 LLM09:2025 Misinformation 条目要求团队“自动校验关键输出”;这道闸门就是一个狭窄而确定的版本。它的具体机制见LLM 流式输出护栏。

第五层:口吻与表态规则

替别人说话的助手,也在替他作出表示

在 Moffatt v. Air Canada(2024 BCCRT 149)一案中,不列颠哥伦比亚省民事裁决庭处理了一起航空公司聊天机器人的纠纷:机器人向一位客户表示,丧亲票价可以事后补申请。McCarthy Tétrault 和 Bristows 都报道了这份裁决。用裁决庭的话说,航空公司的立场等于是在主张“这个聊天机器人是一个独立的法律主体,要为自己的行为负责”。裁决庭不同意:“信息来自静态页面还是聊天机器人,没有任何区别。”

这是工程语境,不是法律意见;一个裁决庭的一份裁决也不构成普遍规则。工程上的教训要窄得多:跑在你站点上的助手,是带着你站点的权威在说话。用我的口吻写出的一个费用数字或一个开始日期,就是一项我从未作出过的表示。自信的错误答案比没有答案更糟;自信的错误承诺则还要更糟。

第三人称、固定拒答文案,以及第一人称检查

助手永远不是我。它以第三人称提到我,所有拒答文案和提示都是固定的。口吻规则在三个地方被强制执行:

  • 第一层在检索之前,用本站的每一种语言拒绝关于费用、档期、聘用和私人信息的问题。

  • 第三层禁止第一人称,也禁止代我作出任何承诺。

  • 第四层在出现第一人称词时中止流式输出。

这项检查在波斯语里更弱。英语的第一人称清单覆盖了代词。波斯语的清单是人工挑选的,不是穷尽的:波斯语用动词的人称词尾来标记人称,而一条宽到能抓住全部词尾的规则,也会一并抓住以同样字母结尾的普通词。清单里放的是代词和几个高风险动词,例如 هستم 和 دارم,并且每个都按折叠可能产生的两种形式各列一遍:这项检查先折叠再切词,所以 می‌کنم 到达时是两个词,میکنم 是一个词。没被覆盖的是词尾,不是拼写。波斯语因此更依赖提示词,而这一点是明说出来的,不是藏着的。对本站的其他语言,清单收录的是第一人称代词和物主形式;西班牙语、葡萄牙语、俄语、阿拉伯语和印地语通常只用动词本身表达“我”,所以它们的清单还收录了少数第一人称动词。中文词与词之间不用空格分隔,所以检查会在句子的任何位置找“我”,只有以“自我”(“自身”)开头的某些复合词除外,例如“自我修正”。阿拉伯语里有一个第一人称词是故意没收的:让波斯语和阿拉伯语拼写可以互相比较的字母折叠,会把它变得和一个常用介词一模一样。

成本滥用与 Denial of Wallet:同一类失败

幻觉是没有人授权的输出。没有上限的账单是没有人授权的支出。OWASP 把后者归入 LLM10:2025 Unbounded Consumption,其中包含 Denial of Wallet:攻击者“利用云端 AI 服务按使用量计费的模式,给服务提供方带来难以承受的财务负担”。它给出的缓解措施包括限制输入大小、限流与用户配额、超时与节流,以及优雅降级。

调用前的预算,而不是调用后的告警

在模型调用之后才做的检查,钱已经花出去了。在本站,一道月度支出检查跑在下限之后、模型调用之前。它把本月已经花掉的支出,加上这一轮对话成本的上界:整段提示词按每个 UTF-8 字节一个 token 计,再加上完整的输出上限,包括推理额度在内。改成拿预期成本去核对,正是上限被突破的方式。

如果加起来会超过上限,就不调用任何模型,读者看到的是一段固定提示,说本月的用量上限已经到了,而不是一条报错。

字节数是一个上界,不是一个宽裕的估计。这个端点能指向的每一种词表都是字节级 BPE,而在字节级 BPE 里,一个 token 是一段非空的字节序列,所以任何文本产生的 token 数都不可能超过它的字节数,任何语言都是如此。绕过这个上界的唯一办法,是一个在切分之前先把文本扩展开的分词器,而这些分词器都不这么做。这个上界取代了原先“每两个字符一个 token”的固定比例,也就是本站已经弃用的那条规则:波斯语的 token 税测出,在 cl100k_base 上,那条规则在一份平行语料的每一个波斯语句子上,算出的结果都低于真实的 token 数。这个上界很宽松——对波斯语来说,它预留的是提示词真实成本的好几倍——而它偏的方向,正是一个支出上限被允许出错的那个方向。

还剩两个局限。这项检查不是预留:支出只是被读出来比较,并没有被占住,所以同时到达的请求可能各自通过,合起来超出上限;按 IP 的限流缩小了这个窗口,却没能把它关上。计数器也不是一本完整的账:它只记这个端点的模型调用成本,所以每个问题背后那次托管的嵌入调用不在其中;而在计数器存储不可达时丢失的一次写入,不会被重试。这个上限是一道护栏,不是一张账单。

支出故障时关闭,限流故障时放行

如果计数器存储不可达,支出就是未知的;面对一个硬上限,未知的支出只有一种安全的读法:当作上限已经用完,直接拒绝。

限流器则相反,故障时放行,因为缓存故障不该把整个站点拖下线。限流器故障放行,最坏的结果是多出一些请求;支出上限故障放行,最坏的结果是一张没有上限的账单。按 IP 有三道限制,分别在一分钟、一小时和一天的窗口里恢复,计数的键取自边缘代理观察到的地址,而不是客户端能自己填的请求头。它们能拖慢脚本;月度上限才是支出的兜底,而不是保证。

读者离开时就该停止付费

关掉的浏览器标签页,不会停下一个已经发往上游模型的请求。服务端在流式循环里检查连接是否断开,并关闭服务商的流,这会取消那次请求。闸门的中止也做同样的事:没有人该为没有人会看到的句子付费。

在中断之前已经生成的内容,毕竟是生成了,也仍然在账单上,所以这个月要为它付费。两家服务商的协议都在一个最终事件里报告用量,而被切断的流永远到不了那个事件;因此服务会保留它收到的每一个字符,只补上服务商没能给出的那部分,按每个 UTF-8 字节一个 token 计。凡是服务商确实报告过的,就以它的数字为准:一轮完整跑完的对话,记录的正是它说的那个数,而不是一个近似值。

怎么衡量:拒答准确率、recall@k 与有据作答

要紧的性质有三项。拒答准确率衡量助手是否拒绝了它必须拒绝的问题。recall@k 衡量证据有没有送到模型手里。有据作答(groundedness)衡量回答有没有留在那些证据之内。在本站,一套覆盖本站全部十种语言、以英语和波斯语为主的黄金测试集会在每次 CI 运行中被确定性地打分,不调用任何模型。

拒答准确率:两套题,不给部分分

拒答在两套题上分别打分,因为两道拒答层的失败方式不同。模式拒答类问题必须在检索运行之前由第一层拦住;无证据类问题与本站毫无关系,必须落在下限之下,也不能被识别为关于我的问题。第三套题检查关于整体工作的问题能否到达本站的记录。

两套题都必须满分,构建才算通过。漏掉一次拒答,就是在一个真人的站点上凭空作出一个承诺,而取平均会把它藏起来。同一次运行也检查反方向的失败:本可回答、却被模式或下限拒掉的问题。Hamel Husain 和 Shreya Shankar 的评测答疑更偏爱通过或不通过的判定,而不是打分量表,因为“二元评估会逼出更清晰的思考和更一致的标注”。

那些必须被拒答的问题,我不在这里引用:这篇文章会进入助手的语料库,引用它们会让那些问题在这个页面上得到很强的关键词匹配。

recall@k:k 取得比默认值更小

在黄金测试集里,recall@k 问的是:前 k 个结果中是否至少有一个能回答该问题的段落——也就是一个命中率。Anthropic 的 Contextual Retrieval 一文用的是更严格的定义,即被检索到的相关文档所占的比例,并把“1 减去 recall@20”作为它的失败指标。

黄金测试集在 k = 5 上衡量 recall,低于默认的 top-k 值 8,因为在一个只有几十个片段的测试语料上,前八个已经是全部内容里很大的一块,在那里衡量会把检索器说得比实际更好。

有据作答:在运行时逐句强制,而不是离线打分

离线评估里,有据作答通常以 faithfulness 来打分。Ragas 把它定义为:回答中被检索到的上下文所支持的论断数,除以回答中的全部论断数,取值 0 到 1;在它的标准形式里,由一个 LLM 找出并逐条核对这些论断。

LLM 评判者既有用,也有偏见。Zheng 和同事发现了“位置偏见、冗长偏见和自我增强偏见”,而强评判者与人类偏好的一致率仍然超过 80%。Husain 和 Shankar 建议在一份留出的已标注数据上验证评判者的真阳性率和真阴性率,并且凡是规则能判定的地方,先从便宜的代码检查做起。

本站走的是代码这条路:闸门在运行时强制有据作答,CI 里不跑任何 faithfulness 指标,也不跑 LLM 评判者。

我的黄金测试集证明不了什么

  • 嵌入模型是替身。CI 中的检索用的是一个词袋哈希模型,没有任何学到的语义。路由、融合、排序、top-k 选择和证据决策(包括下限比较)都是生产代码;只有候选打分在测试里镜像了一份,它在生产环境中由数据库完成。所以 CI 抓得住路由和排序机制的回归,抓不住数据库查询或线上助手召回率的回归;而且这个替身还会走一条生产环境并不使用的稀疏列表。

  • 题量很小。模式拒答有二十一题,无证据类有十一题。在十一道题上“必须满分”是一道回归防线,不是一个拒答率。

  • 线上检查是命令,不是 CI 任务。那个本该把黄金测试集发给已部署检索器的测试,仍然跳过。一条只跑检索的命令会把黄金测试集问题和推荐问题送进已部署的嵌入模型,不调用模型;另有一个脚本在每次导入内容后手动运行,把必须拒答的问题拿去问线上的助手。

  • 基于记录的回答只由闸门检查。CI 在一份固定的测试数据上检查哪些问题会到达本站的记录、各自拿到哪些记录,模型的位置由一个按脚本作答的替身顶上。真实模型如何根据这些记录措辞,是在运行时由闸门检查的。

所以我不为这个助手公布任何准确率、拒答率或召回率数字:自动化运行用的是替身嵌入模型,线上检查规模小、靠手动运行,而本站不会去估算自己没有测量过的数字。

合同助手:用结构代替信任

我曾在 Kartez Contract 担任 AI 工程师。那是一个三语合同工作区,因战争导致的资金问题,项目在距完成仅剩一个月时被客户叫停。在那里,让模型紧扣文本靠的是结构,而不是信任:回答只使用选定的条款,修改建议必须逐字引用现有措辞,输出遵循固定结构,起草时模型以较低的随机性运行。这些是设计约束,不是经过测量的保证。上面讲的下限和闸门属于本站的助手,不属于 Kartez。Kartez Contract 案例研究介绍了这个项目。这些模式在合同 AI 里长什么样,以及为什么评测集要先建,见法律 AI 幻觉:该构建什么。

清单:生产环境 RAG 助手的护栏

  • 先用模式拒答。在检索之前就拒绝费用、档期、私人数据和套取指令这几类问题。

  • 给相似度设阈值,别给名次分数设阈值。余弦相似度可以承载一条下限;整词覆盖率也可以,前提是先去掉常用词,并且至少有两个实义词匹配上;RRF 分数不行。低于下限就用固定文案拒答,并跳过模型。

  • 概览类问题用记录回答,而不是靠相似度。一份按日期排序的列表没有最近邻。

  • 在你实际部署的嵌入模型上校准下限。只在替身模型上检查过的下限,只是一个默认值。

  • 每条提示词规则,都要在模型之外配一个对应的检查。

  • 按句把关流式输出。要求带标记,逐字核对数字、URL 和邮箱,从违规那一句起全部扣下,并为截断单独准备一条提示。

  • 在替某个人或某家公司说话的助手里禁用第一人称,并写明这项检查在哪里更弱。

  • 调用之前预留一个上界,而不是一个宽裕的估计。对字节级 BPE 来说,提示词的 UTF-8 字节数就是这样一个上界。把预留做成原子操作,这样并发请求就不会全部通过;故障时关闭;连接断开时取消上游的工作。

  • 为每一次调用都记一笔成本,包括被中途切断的那些:把流式文本保留下来,只补上服务商最终用量事件没能报告的那部分。

  • 把拒答和召回分开衡量。写明嵌入模型、题量,以及这次运行证明不了什么。

常见问题

RAG 能消除幻觉吗?

不能。在一项预注册研究中,LexisNexis 和 Thomson Reuters 基于 RAG 的法律研究工具出现幻觉的比例仍然各自在 17%–33% 之间;而不充分的上下文可能让模型更自信,而不是更谨慎。检索提高了胜算;保证来自模型之外的检查。

RAG 系统该用多大的相似度阈值?

没有通用的数字,因为相似度的分布取决于嵌入模型和语料库。要给余弦相似度这类有界的相似度设阈值,绝不要给倒数排名融合的分数设阈值,并且用你实际部署的嵌入模型,跑一遍本可回答的问题和必须拒答的问题来定这个值。只在替身模型上检查过的下限是一个默认值,不是一次校准。

靠系统提示词能阻止幻觉吗?

不能。OWASP 建议“尽可能避免”用系统提示词来控制模型行为,改为依靠“LLM 之外的系统”;Anthropic 的指南也说,提示词技巧能减少幻觉,但不能消除幻觉。提示词要留着,同时给每一条要紧的指令,在模型之外配一个确定性的检查。

LLM 评判者能代替确定性检查吗?

凡是字符串检查就能判定的规则都不能,比如标记是否指向一个真实存在的来源、某个数字是否出现在证据里。评判者存在位置偏见、冗长偏见和自我增强偏见。把它们用在代码答不了的问题上,采用二元标注,并且让这个评判者先在人类标注上通过验证。

怎么防止 LLM 聊天机器人把账单跑高?

在模型调用之前,原子地预留一个成本上界——对字节级 BPE 分词器来说,提示词的 UTF-8 字节数就是这样一个上界,任何语言都成立——并在计数器不可用时拒绝请求。为每一次调用都记一笔成本,包括被中途切断的那些:保留流式文本,补上服务商最终用量事件没能报告的部分。降级成固定文案,而不是报错;客户端断开时取消上游的生成。OWASP 把这项风险列为 LLM10:2025 Unbounded Consumption,其中包含 Denial of Wallet。

参考来源


如果你正在设计一个拒答必须和作答一样可靠的助手,可以从项目诊断开始。