RAG 相对裸大模型的核心卖点,是「答案基于给定材料」。但用户点开很多系统的引用体验时会发现:编号和句子对不上、点开引用看不到对应内容、甚至答案里的关键论断找不到出处。引用如果只是答案末尾挂一串「来源:文档 1、文档 3」的装饰,它提供不了任何可信度——用户无法验证,错了无法追责,开发团队自己排查坏例时也无法定位。这篇讲引用体系的完整链路:从入库时的标识设计,到生成时的引用协议,到生成后的校验,最后是呈现层的产品考量。引用做得好,它是用户信任的地基和团队 debug 的雷达;做得不好,它是信任的毒药。
第一层:chunk 的稳定标识
引用体系的物理基础是每个片段拥有稳定、可回溯的标识。要求有三:入库时生成、永不复用(chunk id 一旦指向过某段内容,就永远指向那段内容,文档重建时块全新编号——宁可新 id 多,不可旧 id 换内容);携带定位信息(文档名、章节路径、块序号,用户点开引用要能跳到「那一页那一段」);进入组装上下文时随行(《上下文组装》代码里 [chunk_id] 正文 的格式就是为此——模型看到的每段材料都带着身份证,才可能在答案里引用它)。
一个工程细节值得强调:父子检索与多轮会话会考验 id 设计。父子检索返回的是父块,引用应指向父块的标识与定位(用户读的是完整段落);多轮对话里「引用上一轮的第 2 条」(《多轮对话 RAG》的交互层),需要答案里的引用编号在会话内稳定可索引。id 体系在这些场景下的表现,决定了引用功能的上限。
第二层:生成协议——让模型「标着注」写答案
有了带 id 的材料,下一步是让模型在生成时同步标注引用。协议要点三条:句内标注优于文末罗列(「报销上限为年度工资的 8%[doc42#12]」优于结尾统一列来源——用户能逐句核对);协议写进 system prompt 且给足示例(标注格式、哪些论断必须标注——数字、日期、政策条款、任何可验证陈述必须带;过渡句、总结句可不带);允许模型输出「材料不足」(协议里明确:没有证据支撑的论断,标注 [无出处] 或直接不写——这与《拒答与校验》的 abstention 机制无缝衔接)。
用结构化输出能进一步收紧协议:让模型以 JSON 返回「答案 + 引用列表(论断 → chunk id 映射)」,前端自己把行内标注渲染出来。代价是输出解析的工程量与模型遵循度的波动;收益是引用数据结构化,可程序化校验。
第三层:后校验——引用也可以是编造的
生成协议解决「让模型标」,后校验解决「标得对不对」。模型的引用失灵有三种典型形态,各自要不同的检查:编造 id(引用了上下文里根本不存在的编号——纯幻觉,检查规则简单:引用列表 ∩ 上下文 chunk 集合 = 引用列表,差集即编造);错配(id 存在,但论断内容与该 chunk 无关——「A 文档说 X」实际 X 在 B 文档,需要语义检查:论断句与其引用 chunk 的相关度,嵌入相似度粗筛、NLI 模型或 LLM judge 精查);过度归纳(论断本身在 chunk 里有影子,但把「部分情况」写成了「普遍规则」——最难自动检测,靠评测集里的对抗样本兜)。
def verify_citations(answer, cited_map, ctx_chunks, embed, llm):
issues = []
for claim, cid in cited_map.items():
if cid not in ctx_chunks: # 一级:编造 id
issues.append(("fabricated", claim, cid)); continue
sim = embed(claim) @ embed(ctx_chunks[cid].text)
if sim < 0.5: # 二级粗筛:错配嫌疑
verdict = llm.json(f"论断是否被该材料支撑?\n论断:{claim}\n材料:{ctx_chunks[cid].text}")
if not verdict["supported"]:
issues.append(("mismatch", claim, cid))
return issues # 非空则触发重生成或降级为「低置信答案」
校验不通过怎么办:不要悄悄修——重新生成一次(把问题论断喂回去要求重写),二次仍失败就降级呈现:答案保留但标注「部分论断未找到出处」,或触发《拒答与校验》的流程。让带病答案「看起来很可信」地出街,比明确示弱更伤害信任。
流式输出下的引用处理
答案走流式(打字机)时,引用体系有两个额外的工程点。协议先行:流式场景下后校验来不及拦「编造引用」(答案已经逐字吐给用户了),防线要前移——生成协议在 system prompt 里收紧(每个数字必须带出处、不许引用材料外的编号),把编造率压低到「校验只是抽查兜底」的水平;对高敏场景,可以先整段生成、校验、再伪流式吐出(延迟换安全)。引用的延迟绑定:模型输出的是材料区编号 [3],前端要把编号实时替换成可点击的引用组件(文档名 + 定位跳转)——替换逻辑在流式渲染层做增量匹配(编号出现在句末时就地渲染),不要等全文结束再统一替换(用户已经滚过去了)。流式不是引用体系的豁免区,只是把「生成后校验」部分换成「生成时约束 + 渲染时绑定」。
引用密度与阅读体验的平衡
协议层面还有一个产品化的权衡:不是每句话都该挂引用。逐句密集标注的答案读起来像论文综述,普通用户的阅读体验明显劣化。按「论断类型」分级标注是更优的解:硬事实必须标(数字、日期、金额、条款编号——这些是可验证性所在,也是幻觉高发区);转述与概括可不标(「整体来看材料强调了三点」这类组织性语句);每段末尾汇总标(该段依据的材料编号列表)替代逐句标——阅读流畅性与可验证性的折中。分级规则写进生成协议的 few-shot 示例里,评测集里加一组「引用密度」用例(硬事实漏标即失败)兜住下限。目标是用户想验证时一步能到,不想验证时不被打扰。
引用体系的成本账
引用体系的运行成本主要在校验层,量级要心里有数:编造检查是集合运算(零成本);错配粗筛是每引用一次嵌入相似度(一批答案几十次点积,毫秒级);LLM 裁判是每答案一次调用(按主模型小一档计,约占单次问答总成本的 5%~10%)。这个成本换的是「答案可信度的可审计性」——它不只是拦错误,更是给整个系统装上定位雷达:每个坏例都能下钻到「哪个论断、引了哪条材料、为什么错配」,检索层的改进由此有了精确的靶子。把校验层当成本中心看会心疼,当基础设施看就明白:它是全系统唯一能回答「错在哪」的组件。
第四层:呈现——引用是产品功能,不是脚注
前端呈现决定引用体系的价值兑现。三个级别由低到高:可查(引用列出文档名与位置,用户能自己去翻);可跳(点引用直接展开对应 chunk 原文,高亮被支撑的句子——实现成本不高,体验提升立竿见影);可对(答案句 hover 即显示支撑片段对照——句子级 attribution 的完整形态)。另一个容易被产品忽略的场景:「查看全部来源」。答案只引用了直接支撑句的 chunk,但用户常想看「这个结论的完整上下文」——从 chunk 回溯文档原文的入口要保留,这正好复用《父子检索》的父块定位。
引用的评测
引用体系要进评测集三类用例:编造率(答案引用列表中不存在于上下文的 id 占比——应为零,纯规则可判);错配率(抽样人工判「论断-引用」对的支撑关系,或用裁判模型批量判——它是引用质量的主指标);漏标率(答案中的硬事实——数字、日期、金额——未挂引用的占比,正则抽取硬事实再检查邻近引用即可程序化判定)。三个指标进回归门禁:引用相关的改动(协议模板、校验规则、渲染逻辑)必须三项不劣化才可合入。引用的评测不用等生成层评测齐备——它依赖的只是「答案加上下文」对,检索层评测跑完顺带就能算,是性价比最高的一组生成侧指标。
表格与结构化材料的引用
引用体系的边界 case 里,表格最值得单独立规。表格 chunk 的引用粒度是「整表」,但答案常常只依赖表中一两个单元格——用户点开引用看到整张表要自己找数字,体验打折。改良方案是引用带定位锚:表格 chunk 的元数据里存「行头列头」的索引结构,答案生成协议允许「表引用 + 行列提示」的复合标注(「年费标准[doc7#table3, 行:专业版]」),前端按锚高亮目标单元格。图片类材料同理(「见架构图[doc2#fig1]」)。另一个边界是跨文档综合的引用:答案综合了三份文档的结论(「A 方案成本 X、B 方案成本 Y」),每个数据点各自挂引用,而不是综合段落挂一个笼统的「综合自三份材料」——综合型答案的引用规则写进生成协议的 few-shot,是引用协议里最容易漏的一条。
小结
引用体系四层递进:标识层(chunk 稳定 id + 定位信息,是物理基础)、协议层(句内标注、数字必标、无据不写,是生成约束)、校验层(编造/错配/过度归纳三级检查,是质量闸门)、呈现层(可查可跳可对,是价值兑现)。它同时服务三个对象:用户获得可验证性、团队获得 debug 雷达(每个坏例都能定位到具体 chunk)、下游机制获得接口(《拒答与校验》拿 grounded 信号、《多轮对话 RAG》拿「上一轮证据」的锚点)。在 RAG 的所有可信度建设里,引用体系的投入产出比最高——它不减少错误,但让每一个错误都无所遁形。
参考资料
- 本系列前篇:《上下文组装》《父子检索》《拒答与校验》《多轮对话 RAG》《评测集构建》
- LangChain / LlamaIndex 的 Citation 示例 —— 引用型查询引擎的输出协议参考
- 提示:句子级支撑度校验可复用 reranker 模型做句-块相关性打分,不必引入新模型
读者留言
COMMENTS 暂无还没有留言,来说第一句?