RAG 工程与检索系统设计

第17章 引用、溯源与答案可信度

作者 杨艺韬 · 11,229 字 · 发布于 · 更新于

"Trust is lost faster than it is earned. In RAG, every unsourced sentence is a tax on trust." — 企业 AI 产品经理的共识

本章要点

  • 引用不是"美化答案"——是可信度的基础设施:用户、监管、事故追查都依赖引用能真实溯源
  • 四个层次:引用锚点生成 / grounding 验证 / 可信度评分 / 溯源 UI——每层都有对应的工程
  • 引用生成的两条路线:post-hoc 匹配(答案里找 chunk 片段)和 in-line 要求(prompt 要求 LLM 输出引用)
  • Grounding 验证的黄金指标:每条陈述能否在被引用的 chunk 里找到对应文字——可用 LLM-as-judge 或 NLI 模型自动化
  • 可信度评分不是单一分数——是多信号聚合(召回分数、grounding 强度、证据一致性)

17.1 为什么引用是基础设施

RAG 里的引用不只是"让答案好看"。四个硬需求:

  • 用户信任:用户看到答案引用的具体来源、能跳转验证——才敢基于答案做决策
  • 合规审计:金融、医疗、法律场景要求"每个回答可溯源"——没有引用等于无法合规
  • 事故追查:badcase 发生时必须知道"答案用的是哪一条 chunk"——才能定位问题
  • 反馈闭环:引用关联让用户标错(某引用不相关)→ 标注关联 chunk → 进入训练集——精细反馈

缺少引用的 RAG 答案像无源头的流言——"听说...",权威性归零。

17.2 引用的四层工程

flowchart TB
    classDef l1 fill:#fee,stroke:#c00
    classDef l2 fill:#fed,stroke:#c60
    classDef l3 fill:#def,stroke:#06c
    classDef l4 fill:#efe,stroke:#080
    L1[引用锚点生成]:::l1
    L2[Grounding 验证]:::l2
    L3[可信度评分]:::l3
    L4[溯源 UI]:::l4
    L1 --> L2 --> L3 --> L4
    L1 -.-> D1[每个 chunk 有稳定 ID<br/>prompt 要求 LLM 标注]
    L2 -.-> D2[答案每条声明<br/>能否在 chunk 里找到]
    L3 -.-> D3[多信号聚合<br/>输出 confidence 分]
    L4 -.-> D4[高亮原文<br/>跳转链接]

17.3 第 1 层:引用锚点生成

引用锚点是答案里的"[doc-3]"这类标记、指向 context 里的某个 chunk。两条生成路线:

路线 A:In-line(LLM 自己输出引用)

在 prompt 里要求 LLM 一边回答一边引用:

回答要求:
- 每个陈述后立即加引用 [doc-N]
- doc-N 必须对应上文参考资料里的标识
- 没有依据的陈述不要输出

示例:
企业版支持 SSO [doc-1]。定价为每年 20000 元 [doc-3]。

优点:输出干净、引用和陈述精确配对;缺点:依赖 LLM 自觉,可能漏引或乱引。

路线 B:Post-hoc(答案生成后机械匹配)

LLM 先自由生成答案、系统再分析答案里每句话和哪个 chunk 重合最大、加上引用。

优点:不干扰生成、引用客观;缺点:匹配算法复杂——字面重叠不一定等于语义引用、同义改写的句子可能找不到原 chunk。

路线 C:混合

生产常见——要求 LLM in-line 引用、后处理器验证引用合法(doc-N 确实在 context 里)、对缺引用的句子用 post-hoc 补。

锚点格式的选择

  • [doc-1]:最常见、简洁
  • [1]:极简、但多源时可能冲突
  • {source: doc_id, chunk: N}:结构化、适合 JSON 输出
  • [来源: 产品手册第3章]:可读性好、但不稳定(LLM 可能改写"第3章"成"第三章"、对不上)

推荐用稳定 ID + 人类可读 label 两级:输出 [doc-1]、UI 渲染时把 doc-1 映射成"《产品手册》第 3 章"。

17.4 第 2 层:Grounding 验证

有引用不等于引用准。LLM 经常 标了 [doc-1] 但 doc-1 内容里根本没提那件事。grounding 验证就是机械检查每条陈述是否真能在被引用 chunk 里找到对应。

验证方法

方法 1:字符串重叠

把答案句子和 chunk 做 n-gram 重叠检测。答案里连续 5+ gram 在 chunk 里出现——高概率 grounding 好。简单但对同义改写无能为力。

方法 2:NLI 模型

自然语言推理(Natural Language Inference)模型判断 "chunk 是否 entail 答案里的这句话"。DeBERTa-NLI、bge-nli 等模型专门训练。输出三类:entailment(支持)、neutral(无关)、contradiction(反驳)。

对答案每句话 × 被引 chunk 跑 NLI,要求 entailment 概率 > 0.7 才算 grounded。

方法 3:LLM-as-judge

用另一个 LLM 做裁判:

下面是一段答案和一个 chunk。请判断 chunk 是否支持答案。
答案: {sentence}
Chunk: {chunk_text}
输出: "grounded" / "partial" / "ungrounded"

灵活、可解释(输出理由),但慢且贵。适合离线评估、不适合在线。

验证的生产用法

  • 离线监控:每天对抽样的 trace 跑 grounding 验证、计算 citation_grounding_rate、监控漂移
  • 在线告警:低 grounding 的答案不阻塞返回、但记 alert、触发人工 review
  • 极严格场景:低 grounding 的句子自动移除、只返回 grounded 部分

Grounding 验证的流水线

flowchart LR
    A[LLM 答案] --> SPLIT[拆成原子 claims]
    CTX[被引 context] --> PAIR[pair 每 claim 和被引 chunk]
    SPLIT --> PAIR
    PAIR --> VERIFY{NLI / LLM-judge}
    VERIFY -->|entail| OK[grounded]
    VERIFY -->|neutral| AMBIG[无法判定]
    VERIFY -->|contradict| BAD[矛盾]
    OK & AMBIG & BAD --> SCORE[faithfulness 分数]

Ragas 的 Faithfulness 指标

ragas 的 faithfulness 是标准化的 grounding 验证:

  1. 把答案拆成 atomic claims(每条独立陈述)
  2. 对每 claim 用 LLM-as-judge 判断是否由 context 支持
  3. faithfulness = supported_claims / total_claims

典型数值:好系统 faithfulness > 0.9、一般系统 0.7-0.85、差系统 < 0.7。第 20 章会展开。

17.5 第 3 层:可信度评分

除了二元的 grounded / ungrounded,生产 RAG 常输出一个置信度分数让用户/下游知道"这个答案多可信"。分数聚合多个信号:

信号 1:召回分数

被引用 chunk 的 rerank 分数。分数高 = 相关性强 = 答案基础扎实。

信号 2:Grounding 强度

答案里每个陈述的 NLI entailment 概率均值。全是强 entailment 置信高、有 neutral/contradiction 置信低。

信号 3:证据一致性

多个 chunk 对答案的说法是否一致——都支持 vs 有矛盾。矛盾时置信度应低。可以用 LLM 比较多 chunk 的 consensus。

信号 4:答案确定性

LLM 输出的 logprob——整段答案的 log probability 作为 self-certainty 的代理。低 logprob 通常伴随犹豫表达("可能"、"据我了解"),应降低置信。

信号 5:引用覆盖率

答案里有引用的句子占比。全句有引用 = 高、半句有引用 = 中、几乎无引用 = 低。

组合

加权或简单平均:

confidence = (
    0.3 * rerank_top5_avg +
    0.3 * grounding_strength +
    0.2 * consistency_score +
    0.1 * logprob_proxy +
    0.1 * citation_coverage
)

典型阈值:high > 0.8、medium 0.5-0.8、low < 0.5。UI 侧可以按 confidence 分层显示(高置信直接答、中置信带提醒、低置信建议用户自查)。

置信度的校准

分数值和"实际正确率"要对齐——confidence=0.9 的答案实际正确率应约 90%。不对齐的话用 Platt scaling / isotonic regression 校准一下。校准数据来自人工标注的 gold set。

17.6 第 4 层:溯源 UI

后端给出答案 + 引用 + 置信度后,UI 层要把这些信息展现给用户。好的溯源 UI 三个要素:

要素 1:引用标记可点击

[doc-1] 在答案里高亮为可点击链接、点开跳到原文档的具体位置。跳转的精度影响用户信任:

  • 跳到文档首页:弱——用户还要在 30 页 PDF 里翻
  • 跳到章节:中——好于首页、但章节可能几千字
  • 跳到段落 + 高亮:强——Perplexity、Bing Chat 都这么做
  • 跳到句子 + 高亮:最强——要求精细的 chunk metadata + 解析位置信息

要素 2:证据并排显示

答案旁边一侧显示 context 的 chunk 列表、用户可以同时看"答案"和"证据"。Perplexity 的 UI 就是这种——左边答案带 citation、右边 sources 列表。

要素 3:不确定性提示

置信度低时显式告诉用户:"这个答案基于有限资料、仅供参考、请自行验证。"比偷偷给个错答案好。

17.7 答案和证据不一致怎么办

LLM 有时会生成"和证据相反"的答案——幻觉或推理错误。Grounding 验证检测到矛盾后有三种处理:

策略 1:拒答

grounding 严重不足时直接返回"现有资料无法可靠回答、建议你..."。金融、医疗场景推荐。

策略 2:重生成

用不同 prompt 或不同模型重跑一次。仍然矛盾则 fallback 到策略 1。

策略 3:保守改写

LLM 后处理把肯定语气改成不确定语气("企业版支持 SSO" → "根据资料,企业版可能支持 SSO,请与销售确认")。折中方案。

策略 4:直接返回证据

不生成自然语言答案、直接把相关 chunk 的摘要给用户:"这是 3 段相关资料,请自行判断。" 严格场景的 fallback。

生产选择:默认走策略 2(重生成)、严格场景走策略 1(拒答)、极严格场景永远策略 4。策略要明确、不要在不同答案间混用。

17.8 引用和响应的格式

生产 RAG 返回给前端的 response 建议格式:

{
  "answer": "企业版支持 SSO [doc-1]。定价为每年 20000 元 [doc-3]。",
  "citations": [
    {
      "marker": "[doc-1]",
      "chunk_id": "prod-spec-v3#7#a1b2",
      "doc_id": "prod-spec-v3",
      "doc_title": "企业版产品规格 V3",
      "section_path": ["产品规格", "SSO"],
      "url": "/docs/prod-spec-v3#sso",
      "highlight_text": "企业版支持完整的 SSO 功能"
    },
    { "marker": "[doc-3]", ... }
  ],
  "confidence": 0.89,
  "confidence_level": "high",
  "insufficient_evidence": false,
  "trace_id": "req-abc123"
}

每项元素都有用途:

  • answer + citations:UI 渲染基础
  • chunk_id + doc_id:下游日志、回归
  • url + highlight_text:溯源跳转
  • confidence:展示 + 下游决策(下游业务可以按 confidence 决定是否相信)
  • trace_id:事故追查唯一标识

17.9 引用的反模式

反模式 1:虚假引用——LLM 编造 "[doc-5]" 但 context 里只有 doc-1 到 doc-4。必须后处理验证引用标记都在 context 范围内、不在的要标记为 "ungrounded" 甚至删除。

反模式 2:引用漂移——答案末尾堆一串 "[doc-1][doc-2][doc-3]" 但不指向具体陈述。强制"每个引用紧跟在具体陈述后"、不是答案尾部。

反模式 3:原文复制——LLM 直接复制 chunk 大段原文当答案、引用变装饰。这违反"生成式回答"的意图。prompt 里显式要求"用自己的话组织、不要大段复制"。

反模式 4:过度引用——每半句话都 [doc-N][doc-N][doc-N]——可读性崩溃。合并相邻同源引用、一个陈述一个引用。

反模式 5:不诚实的引用——LLM 在"不确定"的陈述也加引用——看起来有凭据实际没有。grounding 验证挡这一类。

17.10 Grounding 在流式生成下的工程

生产 RAG 多数走流式(streaming)生成——TTFT 优先、答案一字一字出。但 grounding 验证天然是批式——要完整答案 + 被引 chunk 才能验证。两者冲突的解法:

方案 A:流式展示 + 后置验证

用户先看到流式文本、系统在后台并行跑 grounding。验证完成后:

  • Grounding OK:在答案底部加 ✓ 置信度标识
  • Grounding 失败:答案上方显示提示"以下内容可能含偏差、请自行核实"
  • Grounding 严重失败:覆盖答案为 "资料不足、无法可靠回答"

用户体验上损失不大——验证一般 100-300ms 内完成、流式展示过程中就验证好了。

方案 B:关键句子先验证

不验证整段答案、只验证"含引用标记的句子"。LLM 每输出一个 [doc-N] 就触发一次小验证(NLI 模型快、10-30ms)。验证失败的 chunk 直接在界面上标红。

方案 B 的体验最好(实时反馈)但实现复杂。方案 A 是生产主流。

验证失败的 UX 策略

grounding 失败不能粗暴"擦除答案"——用户已经看到了、二次修改体验更差。四种策略按强度递增:

  • 被动提示:加黄色 banner "部分内容可能需要核实"
  • 局部高亮:标红失败的句子、其他保留
  • 降级回答:把失败的句子替换为 "相关资料不足以回答这部分"
  • 拒答重生成:整体判定失败、触发重新生成

严格场景(金融 / 医疗)用方案 3-4、一般场景方案 1-2。

17.11 引用质量的监控

生产 RAG 的引用质量指标:

  • citation_rate:答案里有引用的句子占比。健康值 > 80%
  • citation_grounding_rate:有引用 + 被引 chunk 确实支持的比例。健康值 > 90%
  • ungrounded_claims:没引用或 grounding 失败的陈述比例。健康值 < 5%
  • citation_latency:grounding 验证的延迟。不应 > 100ms per answer
  • citation_precision:引用的 chunk 是否是最优支持(不是随便一段同主题)。人工抽样

每日 dashboard 看。ungrounded_claims 突涨一般意味着 rerank 或 prompt 出了问题——要排查。

17.12 引用在不同用户群的呈现

同样一个有引用的答案、对不同类型用户 UI 呈现应不同:

普通用户(消费级)

  • 默认折叠引用、点"查看依据"才展开
  • 引用标记用小数字角标、不用 [doc-3] 这类技术感
  • 点开后显示段落级摘要、不是整份文档

消费级用户对引用存在性敏感、但对内容深度不敏感——有 = 可信。

专业用户(企业、科研)

  • 默认展开引用、和答案并排
  • 引用显示完整 metadata(文档标题、章节、日期、作者)
  • 可点开原文高亮、可导出 citation

专业用户需要可审计——每个陈述都能追到原始证据、导出写进报告或论文。

审计 / 合规用户

  • 每个 API 请求都保留完整 trace
  • 引用链接直接到 chunk_id + content_hash(严格不变)
  • 可查询历史某时刻、某用户、某 query 的完整响应

审计 UI 不暴露给普通用户——但后端 API 要支持。

跨设备一致

引用在 Web / Mobile / API 调用下表现要一致:

  • Web:点击高亮跳原文
  • Mobile:点击弹出 modal 显示 snippet
  • API:返回结构化 citations 让接入方自己渲染

底层数据一致、呈现按端适配。

17.13 反直觉:有引用反而降低信任

研究表明(Cao et al. 2024 等):加引用不总是提升用户信任。具体:

  • 引用不相关时:用户看出引用是"装饰"、对答案信任度比无引用更低
  • 引用精准且相关时:信任度显著高于无引用
  • 引用数量过多(每句 3+ 引用):用户觉得"噪杂"、反而减少依赖

生产 RAG 的工程含义:

  • 引用数量控制(1 个陈述 1 个引用即可)
  • Grounding 验证兜底(避免不相关引用)
  • UI 设计引导用户看关键引用(而非压倒性信息量)

引用是工具、不是装饰。工具用错反而扣分。

17.14 法务级溯源:不可变引用与可取证 RAG

前面 13 节把引用作为"用户信任工具"。在金融、医疗、法律等强合规场景,引用是另一种东西——法律证据。客户根据 RAG 给的"你的保单不覆盖 X"做了决策、半年后出了纠纷、需要在法庭出示当时 AI 的回答和引用——这时候引用必须能证明"当时确实有这条 chunk 说了 X"。这对引用的工程化提出三个额外要求。

法务级引用的三个属性

flowchart TB
    classDef req fill:#fed,stroke:#c60
    classDef tech fill:#def,stroke:#06c
    P1[不可变<br/>Immutable]:::req
    P2[可重放<br/>Reproducible]:::req
    P3[可审计<br/>Auditable]:::req
    P1 --> T1[chunk 版本号 + content hash]:::tech
    P2 --> T2[完整请求快照 + 冻结索引版本]:::tech
    P3 --> T3[签名 + 时间戳 + 访问日志]:::tech
  • 不可变:引用的 chunk 必须是当时那一版的精确内容。文档三个月后更新了、但当时 AI 看到的是旧版——法庭要看旧版、不是现在的版本
  • 可重放:给定 request_id + 时间戳,系统能精确重现当时的答案和引用。需要冻结的索引版本 + 确定性生成(temperature=0)
  • 可审计:引用内容、时间、查询人、返回给谁,都要可审计、第三方(审计师、监管)能独立核验

普通 RAG 的引用 ID 是 chunk_id——文档改了就对不上。法务级必须引用 (chunk_id, content_hash, version, timestamp) 四元组。

Content hash 与快照

每条 chunk 入库时算一次 content_hash = sha256(text + metadata)、作为 chunk 的版本指纹。引用格式从 [doc-3] 升级为:

{
  "chunk_id": "prod-spec-v3#7#a1b2",
  "content_hash": "sha256:9f86d081...",
  "version": 47,
  "indexed_at": "2026-04-22T09:00:00Z"
}

用户点击引用时系统通过 hash + version 取出当时那一版的 chunk 原文、而非现在最新版。这要求后端保留历史版本——不能做"原地更新"。

实现上用 append-only 存储:每次 chunk 变更产生新版本行、老版本保留。存储成本增加(典型 2-5×、随更新频率),换来可追溯性。

查询重放与索引版本固定

"半年后重放同一 query 还能得到同一答案" 是法务级的硬要求。支持重放需要:

  • 请求完整快照:query、user_ctx、时间戳、使用的模型版本、temperature、prompt 模板版本
  • 索引版本固定:每次请求记录 index_version、老版本索引保留一段时间(法规要求多长)
  • LLM 确定性:temperature=0 + seed 固定(部分 LLM API 支持 seed 参数)

重放时系统加载对应 index_version 的快照、用相同参数跑一遍——理论上得到相同答案和相同引用。这叫 time-travel query

完全精确重放对商业 LLM 有限制——模型本身可能静默升级、即便 seed 固定也不保 100% 一致。妥协:每次请求把LLM 产出的答案 + citations 整块存档、重放时直接返回档案(不是重算)。存档占空间、但这是合规刚需。

监管调查或诉讼期间,涉及用户的所有数据要冻结——TTL 清理、GDPR 删除、定期重建都不能动。实现:

  • 每个 user / tenant 可打 legal_hold: true 标签
  • 数据清理 / 删除操作在写入 legal hold 的 user 时要先检查、拒绝执行
  • Legal hold 解除后才允许清理
  • 所有 hold / unhold 操作本身记审计日志

这和 GDPR 的"删除权"有冲突——用户要求删除但数据在 legal hold 中——必须按司法优先。法务会给出明确说明。

审计签名与第三方核验

最严格的场景(如上市公司合规、药监 AI 辅助诊断)要求每条响应带数字签名

  • 答案 + citations + 时间戳 用企业私钥签名
  • 第三方用企业公钥验签——确认 "这确实是企业 A 在时间 T 给出的响应"
  • 签名私钥本身进 HSM(硬件安全模块)、不可导出

这层几乎没人一开始就做——等合规要求上来再补。但设计阶段预留签名 hook 成本极低、事后补成本高。

引用证据链的完整性

法务级要求能拿出完整的证据链:

证据 保留位置 保留周期
原始文档 + 版本 对象存储 + 版本号 监管周期(金融 5-7 年、医疗 6+ 年)
Chunk + content_hash 版本化 chunk 表 同上
向量索引 snapshot 对象存储(定期快照) 同上
请求快照(query/ctx/params) 审计日志 同上
响应快照(answer/citations) 审计日志 同上
LLM 版本 + 模型签名(如有) 元数据表 同上

这六项齐备才叫"可取证 RAG"。缺一项就有缺口——诉讼时可能被对方律师攻击。

法务级的反模式

  • Chunk 原地更新:旧 chunk 被覆盖、历史答案里的引用打不开——证据链断裂
  • 索引不做版本快照:半年前的某次回答无法重现
  • 审计日志里存指针不存内容:指针指向的数据被删除 / 改动、审计记录失效
  • 用非确定性模型(高 temperature):相同 query 每次答案不同、无法重现
  • 日志保留周期不够:业务周期 5 年但日志只存 90 天

什么场景需要法务级溯源

不是所有 RAG 都需要这么重。分层适用:

  • 消费级聊天助手:普通引用(§17.3-17.6)就够
  • 企业知识库:普通引用 + 审计日志(§22.14)
  • 金融 / 医疗 / 法律 / 公共服务 AI:法务级溯源——本节
  • 上市公司对外披露相关:法务级 + 数字签名

识别场景的关键信号:用户是否可能基于你的回答做出有金钱 / 健康 / 法律后果的决策。是——法务级;否——普通引用。

17.15 引用粒度:chunk / 句子 / 短语

前面章节讨论的"引用"默认是 chunk 级——答案句后加 [doc-3] 指向某个 chunk。这是最常见做法、但不是唯一做法。粒度的选择影响用户体验、验证成本、可信度展示——多数项目默认 chunk 级、没意识到还有更精细的选择。

三档粒度

flowchart LR
    classDef coarse fill:#fed,stroke:#c60
    classDef mid fill:#def,stroke:#06c
    classDef fine fill:#efe,stroke:#080
    C[Chunk 级<br/>整个 chunk 作证据]:::coarse
    S[句子级<br/>chunk 内具体句子]:::mid
    P[短语/span 级<br/>精确到几个字符]:::fine
    C --> C1[实现简单 / 定位粗]
    S --> S1[NLI 验证自然 / 实现中]
    P --> P1[定位最准 / 实现复杂]
  • Chunk 级:答案后的 [doc-3] 指向整个 chunk(通常 300-500 token)。用户点击跳到 chunk、自己找哪句是证据
  • 句子级[doc-3 S2] 指向该 chunk 的第 2 句。UI 高亮那一句
  • 短语 / span 级[doc-3 42-78] 指向 chunk 里第 42-78 字符。UI 精确高亮几个词

三档实现复杂度和精度递增。

Chunk 级的局限

Chunk 级引用的两个问题:

  • 用户找证据负担重:chunk 几百字、答案一句话、用户要自己在 chunk 里找依据句。对非专业用户门槛高
  • grounding 验证粒度粗:NLI 判断 "chunk 整体是否支持句子" 可能得到 "部分支持"——但 chunk 里真正支持的只是一句、其他是干扰。faithfulness 分数被拉低

所以消费级产品常选 chunk 级就够、企业级或合规级要细化。

句子级引用的实现

句子级比 chunk 级多两步:

  1. Chunk 内句子分割:入库时就把 chunk 切成句子、每句有 chunk_id#sentence_idx 稳定 ID
  2. 生成 + 验证:prompt 里让 LLM 标 [doc-3 S2]、后处理验证 S2 句确实包含答案支持的事实

NLI 验证在句子级最自然——"这一句是否 entail 那一句" 是 NLI 的基本任务、精度远高于 chunk 级 NLI。

典型提升:faithfulness 分数从 chunk 级的 0.78 提到句子级的 0.89(同一系统、不同粒度)。

短语 / span 级:SOTA

最精细的做法:引用指向 chunk 里连续的字符范围。例子:

  • 答案: "企业版价格是 20000 元 [doc-3: 42-58]"
  • doc-3 chunk 里字符 42-58 是 "每年 20000 元"
  • UI 跳转到 doc-3 原文、高亮那几个字

实现方式:

  • LLM 输出 span:prompt 要求 LLM 输出引用时带 (start_char, end_char)。精度依赖 LLM、经常有偏差
  • Post-hoc 匹配:答案生成后、从 chunk 里找和答案最相似的连续 span。用字符串匹配或语义匹配实现
  • Attention-based(自托管 LLM):从 LLM 的 attention 层提取答案每个 token 最关注的 context token 位置、映射到 span

Perplexity、Claude Projects 的新版 UI 已经在用 span-level citation——精准定位证据句、用户一眼看到。

粒度和 UI 的协同

粒度提升、UI 也要跟上:

  • Chunk 级:跳到 chunk 首行、可以在侧边栏显示整段
  • 句子级:跳到句子、高亮整句、附近上下文浅色显示
  • 短语级:跳到精确字符位置、高亮几个字、上下文正常显示

没有相应 UI 支持的精细粒度是浪费——后端算得准、前端展示不出、用户仍然要自己翻。

评估不同粒度

粒度不同、评估指标也要调:

指标 Chunk 级 句子级 短语级
Faithfulness NLI on (chunk, answer) NLI on (sentence, answer) string-match rate
Grounding latency 快(每 chunk 一次) 中(每句一次) 慢(字符级匹配)
Precision
Recall 高(chunk 大、包含证据概率高) 可能漏(span 太小)

生产选择时precision 和 recall 要平衡——太细粒度可能漏掉"隐含证据"、太粗粒度用户找得累。

混合粒度:按场景切换

最实用的做法不是二选一、而是按场景混合

  • 答案里的具体数字 / 日期 / 名称 → span 级(精确定位)
  • 答案里的概念性断言 → 句子级
  • 答案里的一般背景信息 → chunk 级
"企业版价格是 20000 元 [doc-3 span:42-58]、支持 SAML/OIDC [doc-3 S5]。详细部署见 [doc-5]。"

这样既有精确引用、也有背景引用、用户体验和验证精度都好。实现需要 LLM 按不同类型断言标不同粒度、复杂但可行。

粒度升级的投入和收益

从 chunk 级升到句子级、典型投入 1-2 周工程(入库切句、prompt 改造、UI 适配)、收益:

  • faithfulness +10 点左右
  • 用户信任度显著提升(用户研究数据)
  • 审计 / 合规场景的"可追溯性"直接达标

从句子级升到 span 级、投入翻倍、收益边际递减——除非是极严格场景(法律、医疗)、消费级和企业级到句子级就够。

什么时候不要细粒度

粒度越细越好是误解——以下场景 chunk 级足够:

  • 聊天类产品(用户看答案不验证)
  • 内部 demo(迭代快、细节不重要)
  • 总结类任务(引用一整段比引用一句自然)

判断:用户是否会点击引用、是否需要"几秒内定位到证据"——是,上精细粒度;否,chunk 级够。

17.16 多证据融合的引用:综合性答案的溯源挑战

§17.3-17.5 的引用机制默认是 "一个陈述 → 一个引用" 的简单模型。但真实 RAG 里很多答案是综合多个 chunk 得出的——比如问"企业版的完整功能列表"、需要综合功能矩阵 + 升级说明 + 限制条款三个 chunk 才能完整回答。综合性陈述的引用比简单陈述复杂得多——一个句子可能引用多个源、其中可能还有部分冲突。

综合答案的三种引用关系

flowchart TB
    classDef conv fill:#fed,stroke:#c60
    classDef merge fill:#def,stroke:#06c
    classDef synth fill:#efe,stroke:#080
    A[陈述和证据的关系]
    A --> A1[单源引用<br/>一句话一个 chunk]:::conv
    A --> A2[汇总引用<br/>一句话综合多个 chunk]:::merge
    A --> A3[推理引用<br/>结论超越任何单 chunk]:::synth
  • 单源引用"企业版价格 20000 元 [doc-3]"——chunk 3 直接说了
  • 汇总引用"企业版包含 SSO、LDAP、审计三大功能 [doc-1][doc-2][doc-3]"——三个 chunk 各说一个功能、合起来才完整
  • 推理引用"企业版比专业版贵 5 倍 [doc-3][doc-5]"——doc-3 说企业版 20000、doc-5 说专业版 4000、具体倍数是推理结果、不在任何 chunk

后两种是 RAG 的"综合价值"——也是引用的难点。

汇总引用的标注方式

多个 chunk 综合成一个陈述时、三种标注选择:

方式 A:句末多引用

企业版包含 SSO、LDAP、审计三大功能 [doc-1][doc-2][doc-3]。

优点:简洁 缺点:不知道哪个 chunk 支持哪个功能、grounding 验证粗

方式 B:子句分散引用

企业版包含 SSO [doc-1]、LDAP [doc-2]、审计 [doc-3] 三大功能。

优点:精确到每个子事实、grounding 验证准 缺点:读起来有点卡顿

方式 C:脚注风格

企业版包含 SSO、LDAP、审计三大功能¹。

---
¹ SSO: [doc-1]、LDAP: [doc-2]、审计: [doc-3]

优点:主文流畅、细节可查 缺点:实现复杂、UI 要支持脚注交互

生产推荐:方式 B——精度和可读性平衡。

推理引用的特殊处理

推理引用最棘手——结论不在任何 chunk 里、但依据两个 chunk 的事实推导。要明确标注:

企业版比专业版贵 5 倍 [推导自 doc-3、doc-5]。

或用专门的引用标记:

企业版比专业版贵 5 倍 [doc-3 × doc-5]。  // × 符号表示综合推理

让用户知道:"这个结论是 AI 算出来的、不是文档里直接写的"——给用户判断的空间。

冲突证据的引用

多个 chunk 在同一事实上冲突时——如何引用?

  • chunk A(2024 年版):"企业版价格 20000 元"
  • chunk B(2026 年版):"企业版价格 25000 元"
  • 用户问"企业版多少钱"

三种处理:

  • 选最新"企业版价格 25000 元 [doc-B]"——基于时间 metadata 判断
  • 显式冲突"2024 年版显示 20000 元 [doc-A]、2026 年版显示 25000 元 [doc-B]"——把冲突暴露给用户
  • 拒答"资料存在冲突、无法给出确定答案"——最保守

推荐:显式冲突是最诚实的——不假装系统"知道"哪个对、让用户判断。选最新适合明确的"版本迭代"场景、不适合"历史 vs 当前" 均有效的场景(如法律条款)。

多引用的可读性

5 条证据综合成一句话、5 个引用标记会让答案变成:

这个流程包含 [doc-1] 登录 [doc-2] 验证 [doc-3] 授权 [doc-4] 记录 [doc-5] 回调。

极难读。优化:

  • 合并相邻同源引用:同一 chunk 连续出现只标一次
  • 末尾汇总"... 这个流程包含五步(详见 [doc-1-5])"
  • 分段引用:把长综合陈述拆成几句、每句独立引用

可读性和精度要平衡——过度引用等于没引用(用户直接忽略)。

综合答案的 grounding 验证

普通 grounding 验证(§17.4)是"这句话能在这个 chunk 里找到"——综合陈述可能单个 chunk 都找不到、但整体被多个 chunk 共同支持。验证要升级:

  • 分解验证:把综合陈述拆成原子事实、每个原子和对应 chunk 分别 NLI
  • 整体验证:把综合陈述和所有被引 chunk 作为一整体 NLI、判断是否被支持

分解验证更严格、整体验证更宽松。生产可以两者混合——关键断言分解、一般描述整体。

综合引用的评估指标

除普通 faithfulness、综合场景还要看:

  • Synthesis quality:综合结论是否超越简单罗列、有真正的合成价值
  • Citation completeness:每个原子事实是否都有对应引用(不能漏)
  • Conflict handling:冲突证据的处理方式是否合理
  • Over-citation rate:同一 chunk 被重复引用的比例(应低)

人工抽检 + LLM-as-judge 结合——这些维度现有框架不直接支持、多数要自建。

常见坑

  • 全部引用塞句末:用户看不出哪个引用对应哪个事实、精度缺失
  • 推理引用没标明:用户以为 AI 是从文档里直接读的、后来发现是推出来的、信任崩塌
  • 冲突不暴露:AI 挑一个说、其他的 chunk 的存在被隐藏、有法务风险
  • 过度分散引用:每个词后都 [doc-X]、可读性崩
  • 综合质量没评估:只看单句 faithfulness、看不出综合能力

综合引用的工程价值

好的综合引用让 RAG 从"文档查询"升级为"信息合成"——这是 RAG 相对于纯搜索的核心价值。如果 RAG 只做单句引用、用户自己翻原文也能得到一样的答案——那 RAG 的价值就只是 embedding 相似度检索、不是真正的"增强生成"。

生产项目应该专门投入工程优化综合引用——这是差异化的能力建设。第一步是让 prompt 里显式鼓励 LLM 综合:

要求:
- 如果答案综合了多个 chunk、在每个子事实后标对应引用
- 如果得出的结论是推理的(不在任何 chunk 里)、用 [推导自 doc-X、doc-Y] 标明
- 如果证据冲突、在答案中暴露冲突

不主动 prompt LLM、它默认会偷懒只引用一两个——失去综合价值。

17.17 引用作为反馈信号:用户点击的 RAG 价值

前面 16 节讲的引用都是输出方向——系统给用户。但引用在 UI 上是可交互的,用户点击、略过、质疑,这些行为本身就是反馈信号,很多团队没利用起来。

flowchart TB
    classDef implicit fill:#def,stroke:#06c
    classDef explicit fill:#fed,stroke:#c60
    U[用户对引用的行为]
    U --> I[隐式反馈]:::implicit
    U --> E[显式反馈]:::explicit
    I --> I1[点击引用 看原文]
    I --> I2[悬停查看预览]
    I --> I3[忽略某些引用]
    I --> I4[停留时间]
    E --> E1[点 helpful / not helpful]
    E --> E2[标记引用错误]
    E --> E3[写反馈评论]

两类信号是互补的:隐式反馈量大(每次请求都有)但信号弱,显式反馈量小但信号强。主力用隐式,关键场景用显式补。

采集:埋点与聚合

前端埋两个事件就够起步:

// 前端
function onCitationClick(citationId, chunkId, answerId) {
  analytics.track("citation_clicked", {
    citation_id: citationId,
    chunk_id: chunkId,
    answer_id: answerId,
    timestamp: Date.now(),
    dwell_time_on_answer: getAnswerDwellTime(),
  });
}

function onCitationHover(citationId, chunkId, durationMs) {
  if (durationMs > 500) {  // 只记真正看了的
    analytics.track("citation_hovered", {
      citation_id: citationId,
      chunk_id: chunkId,
      duration_ms: durationMs,
    });
  }
}

后端把它们聚合成 chunk 级指标——被引用时的点击率、hover 率、以及被引用却看都不看的比例:

chunk_id  | 被引次数 | 点击次数 | 点击率 | 显式标 helpful | 显式标错
doc-A#3   |   1200   |   350    | 29.2%  |      42        |   8
doc-B#7   |   800    |   50     |  6.3%  |      5         |   1
doc-C#12  |   100    |   45     | 45.0%  |      15        |   0

这张表已经能直接用:doc-B#7 点击率极低,很可能不相关;doc-C#12 点击率高,多半正是用户要的证据。据此可以降低低质量 chunk 的检索权重、优先推用户爱看的、并把数据源里的垃圾 chunk 挑出来下线或重做。

显式反馈则靠 UI 上三个按钮收集,每 100–1000 条答案能收到 1 条:

答案: 企业版 SSO 价格 20000 元 [doc-3]

[👍 有用]  [👎 没用]  [✏️ 引用错]

其中「引用错」最有价值——它直接指出哪条引用和陈述不匹配,是可以立刻修的 grounding 问题;另外两类「答非所问」和「漏掉信息」分别否定整个检索链路和 context 密度。

点击之外还有更细的信号:点开立刻关闭说明这个 chunk 里没有答案、切分切错了;滚动到某一段停留说明那一段才是有价值的部分;从头读完说明 chunk 尺寸合适。这些能回流到 chunking 策略——搜索引擎的「点击后行为」分析在 RAG 里同样成立。

用起来:训练 rerank,但要先去偏

隐式反馈最直接的用途是当 rerank 的训练数据,被点的作正例、没点的作负例:

# 从用户行为构造训练对
for request in query_log.last_week():
    citations = request.citations
    clicks = request.citation_clicks

    # 被点击的 chunk 作为 positive
    positives = [c.chunk_id for c in citations if c.id in clicks]
    # 没被点击的作为 negative
    negatives = [c.chunk_id for c in citations if c.id not in clicks]

    for pos in positives:
        for neg in negatives:
            training_data.append({
                "query": request.query,
                "positive_chunk": pos,
                "negative_chunk": neg,
            })

# 用这些对微调 rerank
rerank_model.finetune(training_data)

这是每天几万到上百万对、持续累积的免费训练数据。

但点击不等于相关,直接拿来训会把偏差一起学进去——四类偏差都很常见:位置偏差(靠前的引用更容易被点,和相关度无关)、外观偏差(有标题的比没标题的易点)、好奇偏差(用户对陌生术语好奇点开,其实无关)、以及最反直觉的采纳偏差——答案足够好时用户反而不点引用

这也是为什么单看点击率无法区分两种「没点」:可能是 chunk 确实相关、用户直接信了答案(好信号),也可能是 chunk 不相关、用户觉得没必要看(坏信号)。三条缓解手段:用 counterfactual eval 控制位置因素、把点击与 hover 与停留时长与后续追问多信号加权、以及定期用显式反馈校准隐式信号。

放到评估体系里看,两者正好互补:gold set 是人工构造的,质量高但量小;引用反馈是用户自然产生的,量大但有偏差。成熟做法是用引用反馈扩展 gold set,用 gold set 校准反馈偏差(§20)。

落地节奏、隐私与常见错误

节奏上越早越好,因为反馈数据有时间复利——晚做半年就少半年的积累。MVP 上线后立刻加基础的点击埋点(前端约一天工作量),稳定期补 hover 和停留时长,DAU 到千级把反馈回流进 rerank,到万级再做完整闭环和自动模型更新。

一个真实的量级参考:某客服 RAG 上线一年后累计 1.2 亿条 query log,其中有引用点击的 3600 万条(30%),显式反馈 85 万条(0.7%),「引用错」标记 5400 条。靠这些数据把 rerank 的 recall@5 从 0.82 提到 0.91,团队没有额外投入人工标注。

收集这些数据要注意隐私:谁点了哪条引用能和用户画像关联,企业场景里某个员工的点击模式甚至会暴露他在做什么。合规做法是只做聚合级收集(不存 per-user 点击)、单用户反馈脱敏后保留、并允许用户 opt-out。

最后是五个常见错误,每一个都对应上面某一步没做到位:完全不 track 引用(信号白白浪费)、track 了但从不喂回模型(数据躺在表里)、直接用点击训练而不校准偏差(反而变差)、隐式与显式割裂只用一种、以及不监控反馈数据本身的质量(坏了都不知道)。

做全了之后,RAG 就不再是一条静态 pipeline,而是每次用户交互都让它变好一点的自学习系统:

flowchart LR
    classDef loop fill:#def,stroke:#06c
    U[用户查询]:::loop --> R[检索]:::loop
    R --> A[答案 + 引用]:::loop
    A --> F[用户反馈]:::loop
    F --> T[训练数据]:::loop
    T --> M[更新模型]:::loop
    M --> R

17.18 引用策略的产品定制化:不同形态的引用差异

前面的引用讨论默认是 chat 风格的 RAG,但企业 RAG 有各种形态:聊天助手、搜索框、文档生成、API 接入、语音、移动端。每种形态的用户心智不同,引用的呈现和交互要求也完全不同——一套引用策略通吃是典型错误。

flowchart TB
    classDef form fill:#fed,stroke:#c60
    F[产品形态]
    F --> F1[Chat UI<br/>对话式]:::form
    F --> F2[搜索框<br/>Google 风]:::form
    F --> F3[文档生成<br/>报告 / 总结]:::form
    F --> F4[API 消费<br/>下游开发]:::form
    F --> F5[Voice 语音]:::form
    F --> F6[Mobile 移动]:::form
    F1 --> I1[inline citation]
    F2 --> I2[左右分栏]
    F3 --> I3[脚注 / 参考文献]
    F4 --> I4[结构化 JSON]
    F5 --> I5[语音提示 + 事后可查]
    F6 --> I6[简化 inline + tap]

六种形态各自的引用形式

Chat 用 inline citation,是目前的主流:

企业版价格是 20000 元/年 [1],包含 SSO 和 LDAP [2]。

Sources:
[1] 2026 Product Pricing.pdf
[2] Enterprise Features.md

标记落在句末、底部给 sources 列表、点击跳原文,并且要和流式输出兼容。交互细节上,桌面 hover 显示 snippet,移动端 tap 展开 modal,还要支持长按复制链接。

搜索框形态(Google 的 AI Overview、Perplexity)走左右分栏,答案区不嵌引用标记,来源作为独立面板供扫视,信息密度高、适合桌面:

┌─────────────────────────┬──────────────────────┐
│ 答案区                  │ 相关来源             │
│ 企业版价格是 20000 元... │ 1. Pricing doc      │
│                         │    2 min read        │
│                         │ 2. Features doc      │
│                         │    5 min read        │
└─────────────────────────┴──────────────────────┘

文档生成(写报告、写总结)用学术风的脚注加参考文献,便于导出 PDF / Word,权威感也强:

# 产品定价分析

企业版价格为每年 20000 元¹,包含完整的单点登录功能²。
对比专业版,企业版多支持 LDAP 集成³...

## 参考文献
1. 2026 Product Pricing Document, p.15
2. Enterprise Features Specification, Section 3.2
3. Authentication Comparison Matrix, Row 5

API 消费者要的是机器可解析的结构化数据,带完整元数据,UI 由下游自己渲染:

{
  "answer": "企业版价格是 20000 元/年",
  "citations": [
    {
      "id": "cite-1",
      "text_span": "20000 元/年",
      "source": {
        "doc_id": "pricing-2026",
        "title": "2026 Pricing Document",
        "chunk_id": "c-47",
        "page": 15,
        "url": "https://..."
      },
      "confidence": 0.95
    }
  ]
}

语音是所有形态里最难加引用的。不能念「根据来源 1、2、3」,那会毁掉语音体验。可行的做法是答案简洁口语化、引用只在后台记录,用户问「来源是什么」时再读,有屏幕的设备(Echo Show 一类)同步显示 sources。这里的妥协点是能验证,而不是即时看见。

移动端不是桌面的缩小版:inline 标记要短([1] 好过 [Source: long title]),引用详情用 bottomsheet 而不是 hover,多个引用可以水平滑动展示,链接失效时显示 snippet 而不是死链。

答案里含图、表、代码时,各元素要用各自的引用方式——图在下方标 source 和 page,表格用表脚注,代码放在语言注释或顶部 comment 里,不要混成一团。

用户群、可访问性与国际化

同一个产品往往面向多类用户,期望差别很大:消费级用户要简洁不打扰、tap 能展开就行;专业用户要完整元数据、能导出;开发者要 JSON 和精确 span;合规官要每个断言都有引用、可审计;非技术背景的用户则需要自然语言的引用名,而不是 [doc-47-chunk-3] 这种内部标识。可行的做法是按 user tier 切换展示形态。

可访问性不是小众需求:引用标记要带语义 ARIA、支持快捷键跳转、提供高对比度与大字号选项、高亮配色对色盲友好。

多语言产品还要处理四件事:引用 label(「来源」/「Sources」/「参考文献」)本地化、时间与数字格式本地化、支持阿拉伯语等 RTL 语言的引用布局、以及跨语言引用(中文提问、英文来源)时显式标注语种。

还有一条容易忽略的原则:引用风格要和答案风格协调。答案正式就配完整的参考文献,答案口语化就配「[来源 1]」,答案结构化则引用也结构化。风格不协调时用户会明显感觉到「这是机器拼的」。

选择、验证与演进

给一个新 RAG 产品定引用策略,按五步走:识别主要用户群和设备形态,调研用户习惯(竞品用什么、用户期望什么),做原型测试让用户在几种风格里选,从一种开始渐进 rollout、数据好再扩,最后按平台和用户群分化。照抄 Perplexity 不一定对——要找适合自己产品的形态。

后端的定位要清楚:后端提供数据,不决定展示形态。Web、Mobile、API 可以各有各的引用策略,后端支持多种、前端按平台选。

引用展示的改动同样要 A/B,看三个信号:引用命中率(用户点击比例)、信任度变化(满意度调研)、以及对理解的帮助(用户追问「告诉我更多」的比例)。

这个环节的反模式很集中:一套引用走天下(把 chat 的形式套给 API 消费者)、忽略移动端、不管 a11y、API 不返回结构化数据让下游自己解析、语音场景照搬文字引用。

值得强调的是,引用不是「一个 feature」,它就是 RAG 产品的核心用户体验——Perplexity 的引用设计是它的杀手锏,Claude Projects 的引用与 UI 深度集成,Notion AI 的引用能直接插进文档。再往前一步是按用户习惯个性化:新手给详细引用并解释为什么这里要引用,老用户给简化版不打扰,审计员永远完整,开发者默认 JSON。技术上不难,产品上有价值。

17.19 跨专栏关联:引用机制和学术论文的 citation

RAG 的引用机制和学术论文的 citation 系统精神同构——每个断言背后要有 reference、读者能追溯、同行可以验证。不同点:

  • 学术 citation 是作者自觉 + 审稿验证、RAG 是 LLM 输出 + 机械验证
  • 学术论文每个 citation 对应一篇完整文献、RAG 对应一个 chunk

RAG 的 grounding 验证某种意义上是把学术同行评议工程化——每句话都被"机械审稿人"检查。

这和《MCP 协议》第 20 章讨论的 Resource 溯源、《LangGraph 设计与实现》讨论的 Tool 调用溯源属于同一个大问题:AI 系统的每一步输出都应当可追溯——不是锦上添花,是生产部署的基础设施。

17.20 本章小结

  1. 引用是 RAG 的基础设施——用户信任、合规、事故追查、反馈闭环都依赖它
  2. 四层工程:锚点生成 / grounding 验证 / 可信度评分 / 溯源 UI
  3. 生成两路线:in-line(LLM 自己引)vs post-hoc(事后匹配)——生产常混合
  4. Grounding 验证用 NLI 模型 或 LLM-as-judge——faithfulness 是核心指标
  5. 可信度评分是多信号聚合——不是单一数字,是组合
  6. 答案和证据不一致时拒答 > 重生成 > 保守改写——严格场景首选拒答
  7. 引用反模式五类(虚假 / 漂移 / 复制 / 过度 / 不诚实)——生产 pipeline 必须捕获

下一章起进入第五部分——Agent Memory 与 GraphRAG——把 RAG 从单轮问答扩展到长期记忆和图检索。