RAG 引用校验:答案写得顺,不代表证据站得住
一、RAG 的可信度来自引用
RAG 系统常见问题不是答案错,而是答案看起来很有道理但其实没有证据支持。模型可能从多个 chunk 拼出一段流畅的结论,也可能把相似但过期的旧信息当作当前事实。更隐蔽的情况是:引用列了三篇文档,但仔细看会发现,有一篇是错的、有一篇用户没权限看、第三篇引用的段落已经被更新了。如果引用只是装饰性的"脚注",用户无法判断答案是否真的可信。
我曾测试过一个 RAG 系统,问"退款需要几天",系统回答"需要 30 天,引用:退款政策 v2"。答案很顺、引用也对上了,但退款政策 v2 是三个月前的版本,现在的 v4 明确规定是 7 天。系统不是没有更新索引,而是旧 chunk 还在、缓存还在、模型也忠实地用了旧资料。这是典型的"引用了但没校验版本"的问题。
还见过更糟糕的情况:引用列表里有一篇文档实际上已经被删除了,但向量索引没同步,检索结果仍然返回了旧 chunk。用户点击引用链接跳转到 404,信任感瞬间归零。引用校验不能只检查"是否存在",还要检查"是否有效、是否可见、是否为当前版本"。
RAG 引用校验要检查答案里的每一个关键事实是否真的被当前有效的证据所支持。不是为了增加技术复杂度,而是为了从一个最基础的问题做起:用户凭什么相信这个答案。
二、先拆生成链路
引用校验应该在答案返回用户之前执行。不是让模型自己说"我有依据",那相当于让裁判同时当运动员。而是让独立的系统模块检查答案中的每一个事实主张(claim)是否都能对应到某个可见的、当前有效的文档片段。
flowchart TD A[用户问题] --> B[检索相关文档] B --> C[重排筛选 Top-N] C --> D[模型生成答案] D --> E[系统拆解答案为 Claim 列表] E --> F{每条 Claim 是否有有效证据} F -->|全部有| G[返回答案 + 引用] F -->|部分缺失| H[删除或标注无证据部分] F -->|全部缺失| I[拒答 — 无足够依据] H --> G引用校验的核心逻辑:
citation_check: require_citation_per_claim: true # 每个事实主张必须对应证据 verify_doc_current_version: true # 引用的必须是当前有效版本 verify_user_visible: true # 用户有权限看这篇文档 reject_missing_evidence: true # 没证据就不输出 mark_partial_support: true # 部分支持也要标注清楚这里有一个实现上的取舍:校验是在模型生成之后的模块完成,还是在 prompt 里就引导模型标注 citation。两种方式各有优势——prompt 引导的引用更自然但可能遗漏,系统校验更严格但可能误杀。实际项目中建议两者结合:prompt 要求模型结构化输出引用,系统再做二次校验。
三、把答案拆成结构化的 claim
与其让模型输出一段自然语言再去事后解析,不如在 prompt 设计时就要求模型输出结构化的 claim 列表。每条 claim 独立对应一个或多个 citation,方便系统做逐条校验。
type VerifiedClaim struct { Text string `json:"text"` CitationIDs []string `json:"citation_ids"` Status string `json:"status"` // verified, missing_evidence, unverified }如果某条 claim 没有证据支持,应该在返回前删除它,或者标注为"以下信息来自模型推断,非知识库内容"。不要把无证据的内容混进最终答案当作可信事实。
分拆 claim 的做法在实际中会遇到一个挑战:模型输出的 claim 粒度不一致。有的返回"退款需要 7 天",有的返回"退款需要 7-14 个工作日,具体取决于支付方式"。后者可能需要两个 citation 分别覆盖。建议在 prompt 中给出 claim 拆分的示例,引导模型把复合事实拆成原子命题。
四、引用要检查权限、版本和多源支持
引用正确还远不够。引用的文档用户能看吗?版本是当前的吗?如果一份文档支持观点 A、另一份支持观点 B,模型可以只挑了 A 来回答吗?引用校验需要覆盖:
- 权限检查:被引用文档的 ACL 是否包含当前用户
- 版本检查:引用的必须是当前有效版本,不是旧版本
- 删除检查:已删除的文档不应出现
- 多源完整性:如果多份文档结论不一致,答案要呈现分歧而非只挑有利的
citation_guard: check_acl: true check_kb_version: true check_doc_not_deleted: true show_quote_snippet: true allow_multiple_sources_per_claim: true require_all_sources_visible: true多源完整性值得单独展开说。如果一个知识库里同时存在"最低消费 100 元(2024 版政策)"和"最低消费 50 元(2025 版政策)",模型可能只引用 2024 版因为它在检索结果中排名更高。引用校验应该检测到"存在多个版本且结论冲突",要求模型呈现差异而非省略。这对于内部政策、合同条款、产品规格等场景尤其重要。
还要记录每次引用失败的原因。是检索阶段就没找到相关文档?是找到了但用户没权限看?是引用的文档版本已过期?还是模型纯粹编造了一个不存在的引用?不同的失败原因意味着完全不同的优化方向。只有分类记录,后续改进才有依据。
引用校验的最终目标是:用户可以点开每一条引用,看到原文片段,确认答案来自于真实的知识库而非模型的想象力。做不到这一步,RAG 就只是包装得好看的生成模型,和直接调 API 本质没有区别。
五、总结
RAG 引用校验不是装饰性的脚注功能。它要求系统把答案拆成可验证的事实主张,逐条检查证据是否存在、是否可见、是否有效、是否完整。答案写得顺不代表证据站得住。让用户相信 RAG 的答案,靠的不是模型自信的语气,而是每一条事实背后可追溯的引用。