模型最危险的时候不是答错,而是答错时语气和答对时一模一样。
一、一句「应该可以」让用户错过了申报期
沈瑜给一个产业园区做政策问答助手,知识库是园区整理的补贴文件:一次性创业补贴、场地租金减免、社保补贴,按年度和主体类型分册。上线第一个月,回答看起来都很像回事。
以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。
六月,一位个体户问:「我这种个体工商户能不能领一次性创业补贴?」助手答:「应该可以,建议尽快准备材料申请。」还顺手给了一个截止日期。用户照着准备了半个月,到窗口才被告知:当年的细则已经把个体工商户排除在外,那个日期来自两年前的一份通知。
复盘时发现三件事。知识库里同时存在 2024 年与 2026 年两份文件,检索把旧的那份排在了前面;提示词只写了「尽量给出处」,模型于是自由发挥;系统里根本没有「没有依据就不许下结论」这条硬规则,也没有任何转人工的出口。
这不是一次孤立的幻觉,而是一套边界设计的缺失:没有白名单,过期的文档照样能被召回;没有逐句引用,用户无法核验;没有阈值,低置信度的答案照样被当成结论输出。
事实边界要解决的不是「让模型更聪明」,而是「让它在没有依据时闭嘴,并且在闭嘴时把人送到能解决问题的地方」。前者靠模型,后者只能靠你自己的路由与流程。
二、四种回答模式:按风险与置信度路由
所有回答先落到四种模式之一,再决定要不要生成。路由同时考虑错误后果、问题范围、来源适用条件和经校准的检索分数。相似度或重排分数只是路由信号,不是答案正确的概率;最高分过线也不能代替地区、年度及主体条件检查。
| 模式 | 触发条件 | 输出要求 | 失败后果 |
|---|---|---|---|
| 有据可答 | 命中白名单文档,最高分过阈值,问题在可答范围 | 每条事实句带可点击来源,无来源的句子删掉 | 引用错位,用户按错误条款办事 |
| 无据但低风险 | 检索为空或低于阈值,但答错只造成困惑 | 首句声明「文档中未找到依据」,禁止出现数字、日期、金额 | 用户误以为是官方口径 |
| 澄清或拒答 | 问题缺关键要素,或要求预测审批结果、绕过条件 | 固定话术 + 需要补充的信息清单 + 可自查的入口 | 用户反复追问,或转向别处被骗 |
| 转人工 | 高风险且低置信度,或同一问题连续两轮未解决 | 带原问题、命中文档 ID、已生成草稿 | 用户流失,且没人知道发生过 |
有据可答的第一条规则是「句子级引用」。 不是文末挂三个链接,而是每一句包含事实的陈述后面跟一个来源编号,点开能定位到文档的具体段落。做不到句子级,至少要做到段落级;做不到段落级,就别声称这是「有据」。
无据但低风险是一个窄口子,不能当成默认出口。 它只适用于「解释一个通用概念」这类答错也不造成实际损失的问题,并且必须在首句声明没有找到依据。一旦问题涉及金额、期限、资格、责任,无论检索结果如何,都不许走这条路径。
澄清优先于拒答。 「我能领吗」这类问题缺三个要素:注册地、主体类型、成立时间。直接拒答会让用户觉得系统没用,先问清要素往往就能变成有据可答。澄清话术要写成清单,不要每次都让模型重新组织语言。
拒答必须是产品行为,不是模型即兴发挥。 固定话术里写清三件事:为什么不能答(超出可答范围 / 文档未覆盖)、用户可以去哪里查、能不能转人工。让模型自由生成拒答理由,拒答本身就会变成新的幻觉来源。
转人工要带三样东西:原问题、命中的文档 ID、已生成的草稿。 少了草稿,人工要从零开始;少了文档 ID,人工不知道机器看过什么;少了原问题,用户得重述一遍。这三样东西决定兜底是「接手」还是「重来」。
路由要写成代码里的一个函数,不要写在提示词里。 提示词里的规则会被模型的措辞偏好稀释,而函数里的阈值不会。风险等级来自功能配置,置信度来自检索分数,两者都不该由模型自己判断。
三、检索白名单、稳定 chunk ID 与逐句引用
检索质量决定事实边界能不能成立。检索召回了一份过期文件,后面所有的引用、校验、拒答都是在给错误做包装。
| 规则 | 具体做法 | 可验收的信号 |
|---|---|---|
| 白名单 | 只检索审核过的文档集合,按地区、年度、主体类型打标签 | 每次检索都能打印命中的文档 ID 与标签 |
| 过期处理 | 旧文件不删除,标 deprecated 并在检索时过滤 | 抽查 20 条查询,无过期文档进入上下文 |
| 片段身份与版本 | chunk_id 为持久分配的逻辑 ID;另存完整 version_sha256 与源文档快照 | 改标题不改逻辑 ID;改正文或来源元数据生成新版本,旧版仍可查 |
| 切分方式 | 按标题层级切,单块不超过 800 字,保留父标题 | 每个块能独立读通,且能回溯到章节 |
| 引用校验 | source_id 必须在白名单内,quote 必须是块内容的子串 | 校验失败时整条回答重写或拒答 |
身份、位置与版本分开保存。 chunk_id 存在持久映射表里,标题路径只是可变的定位信息;version_sha256 对规范化正文及来源元数据计算完整摘要,并绑定不可变快照。审计记录引用 (chunk_id, version_sha256),重建索引复用映射,标题修改不会改变逻辑 ID。拆分或合并片段时分配新 ID、记录前后映射,保留被历史答案引用的版本;不能只留八位摘要或覆盖旧正文。
白名单不是一次配置,而是一份要维护的清单。 新文件进来要有人签字,旧文件过期要有人标 deprecated。一个人做的话,把它做成每周一次十分钟的例行检查:看新增文件、看失效日期、看有没有被误标的年度。
没有超过阈值的来源,就不生成事实断言。 阈值用重排分数或相似度都可以,具体数值必须在自己的语料上校准:拿 30 条已知能答的问题看分数分布,再拿 30 条已知答不了的问题看分布,阈值取在两者之间的低谷。不要照抄别人的 0.8。
机器校验只能先保证引用存在。 输出结构使用 facts: [{text, chunk_id, version_sha256, quote}],服务端检查该版本确实在本次授权检索的上下文中,且 quote 是对应快照的子串。子串匹配并不证明引文支持结论,还要审查否定词、例外和适用条件;高风险结论交给领域人员复核。失败时重写或拒答,不能删掉限定句后留下更强的错误结论。
引用覆盖率只统计事实句,不统计寒暄。 分子是带有通过存在性校验引用的事实句,分母是全部事实句;把寒暄只加进分母会压低比率,不能与其他口径混用。另行抽检“引文是否支持结论”,不要把引用覆盖率称作事实准确率。
上下文拼装要带生效日期,并按时序排。 每个块除了正文还要带 effective_from 与 effective_to,拼装时把过期块排除,剩下的按年度倒序放。这样即使白名单里同时存在新旧两份文件,模型看到的也是当前有效的那份,而不是先看到谁就信谁。
四、拒答、转人工与每周审计
拒答做得好不好,取决于你敢不敢承认「应拒答」这个集合是人工标出来的,而不是模型算出来的。
| 审计字段 | 记录什么 | 用来回答什么问题 |
|---|---|---|
| 问题原文 | 脱敏后的用户输入 | 这类问题该不该由系统接 |
| 来源 ID | 命中的文档与 chunk 列表 | 答错时它看了什么 |
| 答案与引用 | 最终输出、逐句来源 | 引用是否真的支撑结论 |
| 拒答原因 | 越界、无依据、要素缺失、低置信度 | 拒答是在拦风险还是在偷懒 |
| 人工结论 | 人工是否认同、正确口径是什么 | 该拒不拒与误拒各占多少 |
| 成本与延迟 | token 数、耗时 | 兜底是否被滥用 |
越界检测分三层,按成本从低到高排。 第一层是规则:正则命中「保证」「一定能批」「包过」这类词,直接拦截。第二层是分类器:判断意图是否落在可答范围内。第三层是模型自检:生成前先回答「现有证据是否足以支持结论」。规则快、分类器稳、自检便宜,三层都过才放行。
置信度阈值要按风险分档,而不是全局一个数。 涉及钱和期限的问题,阈值调高,宁可拒答;通用概念解释,阈值可以低。分档的依据写进配置,和功能开关放在一起,改的时候走同一条变更流程。
拒答话术要固定,并且给出去处。 一句「抱歉,我无法回答」等于把用户推走。合格的话术包含:说明限制、给出可自查的官方入口、提供转人工按钮。这三样每次都由模板渲染,不让模型改写。
转人工队列要有上限和优先级。 一个人一天能处理的人工请求是有限的,队列满了就要降低转人工的触发面,只保留高风险场景。否则兜底会变成第二份全职工作,而且没有人会为它排期。
每周抽 20 条复核,重点看两类错误。 「该拒不拒」会造成实际损失,「不该拒却拒了」只会损失体验,两者成本不对称,所以抽样要刻意多抽高风险场景。复核结论写回审计表,成为下周的对照。
误拒也要有申诉入口。 用户点「这个回答有帮助」或「我被拒绝得不合理」,这条记录就是最好的误杀样本。一个月能收到十条左右,比你自己构造的对抗集更真实。
人工结论要回流成知识,否则兜底永远在兜同一件事。 每周把人工回复整理成一条 FAQ 或一份补充文档,走白名单审核后入库,下一周观察同类问题的转人工量有没有下降。这个数字不降,说明回流做成了摆设:要么文档没进白名单,要么检索阈值把新文档挡在外面。
五、行动清单与复算验收
先把四种模式的路由写成函数,再补检索白名单,最后才调提示词。顺序反了,你会用提示词去弥补结构问题,越补越脆。
下面 8 条按一周节奏排,做完就能跑出第一版审计指标。
- 列出功能的可答范围与三类越界情形,写成路由函数的输入
- 打标签:给知识库文档补地区、年度、主体类型,并标出已过期文件
- 建立持久 chunk_id 映射、完整版本摘要与不可变快照;重建索引后仍能打开旧答案引用
- 校准检索阈值:用 60 条已知可答与不可答的问题记录分数分布
- 改输出结构为逐句
facts数组,服务端校验来源与引文子串 - 写四种模式的固定话术,禁止模型自由生成拒答理由
- 接通人工队列,转人工时自动带上原问题、文档 ID 与草稿
- 建审计表并抽最近 20 条会话复核,标注该拒不拒与误拒
验收标准:随便抽一条回答,都能在不问工程师的情况下指出每个事实句的来源,或指出它为什么被拒答。
教学假设:一周 1200 次会话,4200 个事实句,转人工 96 次
人工标注应拒答处置 150 次:正确拦下 138,漏拒 12
其余 1050 次中误拒 18,正常放行 1032;实际拒答处置 156 次
这里“拒答处置”包含停止自动回答并转人工,96 次属于该处置集合
引用覆盖率 = 3860 ÷ 4200 = 91.9%
拒答召回率 = 138 ÷ (138 + 12) = 92.0%
拒答精确率 = 138 ÷ (138 + 18) = 88.5%
正常问题误拒率 = 18 ÷ (18 + 1032) = 1.7%
人工兜底率 = 96 ÷ 1200 = 8.0%
结论:召回率过 90% 内部线,但精确率未过;引用覆盖率不是事实准确率
优先复核 12 条跨年度漏拒与 18 条误拒,再决定文档、规则或路由怎么改
使用边界与实操验收
这套路由适用于「答案有明确出处、错误可被追溯」的场景:政策、产品文档、内部制度、合同条款。它不适用于需要价值判断的问题,也不适用于出处本身就有争议的领域。
- RAG 架构实战:从零搭建 Embedding、检索与生成的知识问答系统 :它讲检索与生成的整体架构怎么搭;本文只处理搭好之后,什么情况下不许生成、生成时必须带什么。
- RAG 评估体系:召回、忠实度与自动化指标 :它讲召回率、忠实度等指标怎么定义与自动计算;本文的引用覆盖率是它的一个工程化子集,口径更窄但必须能人工复核。
- 人机协作模式(Human-in-the-Loop):让 Agent 在关键时刻停下来,等待人类确认 :它讲人机协作的通用模式与确认点设计;本文讲的是其中最简单的那个兜底出口怎么接。
实操验收可以这样跑:从最近 100 条会话里随机抽 10 条,让一个不了解系统的人只看回答,判断每句话有没有来源、该不该被拒答。他判断不出来的地方,就是你的边界没写清楚的地方。
不适用情形要提前说清。知识库本身错误或过期时,引用做得再好也只是把错误包装得更可信,这类问题要先解决文档治理。另一个不适用之处是多跳推理:答案需要合并三份文件才能得出时,逐句引用会退化成「每句都有出处,合起来是错的」,需要额外的推理链校验。
关键参考与核验范围
核对日期:2026-10-25。下列来源仅支持明确标注的机制或原则。
- W3C:PROV Data Model : 用于核对可追溯记录中实体、活动及来源关系的基本概念。
- NIST AI Risk Management Framework : 用于核对输出风险需要测量、评估和持续治理的原则。
使用边界。 chunk_id、完整版本摘要和不可变快照是本文的工程约定,不声称为 W3C 指定的 ID 格式。会话量、引用数量、拒答与转人工数字均为教学假设,未核验任何地区政策资格。
如何自行复核。 用旧答案中的 ID 与版本取回当时原文;分别标注应拒、实拒和正确拒答,重算召回率与精确率,再人工核对引文是否支持结论。
系列导航
- 上一篇:小样本评测:50 条用例、评分表与上线门槛
- 下一篇:滥用防线:输入过滤、输出约束、速率与审计
- 回到目录:一个人给产品加 AI:判据、成本、护栏与验收
- 相关阅读:模型评估与 LLMOps:从离线评测到生产监控的闭环体系 、模型评估与基准:从分类指标到 LLM-as-Judge
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。