2653 字
约 8 分钟
16
S20-A16 · 引用与可追溯:答案挂在哪段资料上

一个 RAG 问答答对了"差旅住宿标准 600 元/晚",并在末尾标注了来源文档。用户点开来源,发现那份文档里写的是"一线城市 550 元"——答案正确,来源错误。而这类"引用错配"比"没有引用"更危险,因为它看起来是可以核验的。

引用的价值不在"显得可信",而在让答案可以被核对。而可核对的前提是:引用的粒度足够细、来源与断言一一对应、且用户能验证。


1. 引用的三个粒度

粒度 可核对性
文档级(给出文档名) 低(要自己找)
段级(给出章节或片段) 中
句级(给出原文那句话) 高

第一项最常见也最没用:用户拿到一个 40 页的 PDF 名,等于没有引用。这个差别背后是一个工程现实:句级引用要求"检索片段"与"生成句子"能对齐,而多数系统在生成时已经把片段拼成一段长文本,对齐信息丢了。
而它在实现上最便宜,于是被大量系统采用。

第三项要求把"生成内容"与"原文片段"做对齐,实现成本高,但它是唯一能支撑"我核对过了"的粒度。

2. 引用错配的四种形态

形态 表现
张冠李戴 答案对、来源错
部分支持 来源只说了一半
语义漂移 原文"可能",答案"可以"
引用失效 链接过期或文档已改版

第一项在检索到多份相似文档时最常见:两段都讲住宿标准,模型引用了其中一段,而数字取自另一段。第二项(部分支持)最隐蔽:来源确实讲了这个主题,但只说了其中一条,而答案把两条都写上了。用户核对时会觉得"来源大概支持它",除非他逐字比对。

第三项是"强度漂移":原文有条件的结论被写成无条件的,而引用看起来完全合理。

xt
// 错配的检测:把答案里的断言与引用片段做逐条比对
断言:一线城市住宿标准为 600 元
引用片段:一线城市住宿标准为 550 元(2023 版)
// 结论:数字不符 → 引用错配,应降级为"未确认"或拒答

3. 让引用可核对的四种做法

做法 说明
生成时逐句挂来源 模型输出结构化引用
生成后逐条校验 断言与片段比对
展示原文片段 不只给文件名
提供跳转定位 页码/锚点

第二项是必须的:只让模型"标注来源"不可靠,因为它可能标一个"看起来对"的文档。校验要独立于生成——把答案里的数字、日期、专有名词逐个在引用片段里查找。

第一项的实现方式是让模型输出结构化结果:每句一个 citations 数组,而不是末尾一个总引用。

// 逐句挂引用:每个断言都有自己的来源
{
  "answer": [
    { "text": "一线城市住宿标准为 600 元/晚",
      "citations": [{ "doc": "travel-2024", "chunk": "3.2", "quote": "一线城市:600 元/晚" }] },
    { "text": "二线城市为 450 元/晚",
      "citations": [{ "doc": "travel-2024", "chunk": "3.3", "quote": "二线城市:450 元/晚" }] }
  ]
}
// 校验:每个 quote 必须能在对应 chunk 里精确找到

4. 校验的三道闸

闸 检查什么
存在性 引用的 chunk 真的存在
包含性 quote 真的出现在 chunk 里
一致性 答案数字与 quote 里的一致

第一项防"模型编造文档名":chunk id 应当由系统分配并在检索结果里回传,模型只能引用它见过的 id。

第二项防"模型改写 quote":要求 quote 是原文的精确子串,一条 in 判断就能验证。

第三项是核心:"600 元"与 quote 里的"550 元"不一致时,必须拦下。这一条能抓住绝大多数错配。第三项的实现要注意"数字的单位与格式":600 与 600.00 与 6e2 是同一个数,而字符串比较会误判。所以比对数字前应当先归一化。

def verify(answer_items, chunks) -> list[str]:
    errs = []
    for item in answer_items:
        for c in item["citations"]:
            chunk = chunks.get(c["chunk"])
            if chunk is None:
                errs.append(f"chunk 不存在: {c['chunk']}"); continue
            if c["quote"] not in chunk.text:
                errs.append(f"quote 不在原文: {c['quote'][:20]}")
            nums = extract_numbers(item["text"])
            if any(n not in c["quote"] and n not in chunk.text for n in nums):
                errs.append(f"数字无依据: {nums}")
    return errs

引用还有一个"时间"维度需要固定:同一份文档会被更新,而历史答案引用的可能已是旧版。所以引用记录应当包含"文档版本或快照",否则半年后回看一条答案,无法判断"当时依据的原文是什么"。

// 引用记录里必须有的四个字段
citation:
  doc_id: travel-2024
  version: "2024-03-01"        # 生效版本或快照日期
  chunk_id: "3.2"
  quote: "一线城市:600 元/晚"  # 原文精确子串
  # 缺 version → 无法判断时效;缺 quote → 无法核对

5. 引用展示的设计

做法 效果
折叠式原文 不打断阅读
高亮命中片段 快速核对
标注版本与日期 判断时效
标注置信度 提示不确定

第三项常被忽略:同一制度有多个版本时,"哪一版"决定答案对不对。第二项(高亮命中片段)看似是 UI 细节,实际影响很大:没有高亮时,用户要在一个 300 字的片段里自己找那句话,多数人不会找,于是"可核对"变成了"看起来可核对"。
所以引用里要带版本或生效日期,否则用户无法判断。

第四项(置信度)在内部工具里可以粗一些,但对外部用户应当分档而不是给数字——用户会问"0.68 是不是不太可信",而系统没有能力解释这个数。
第四项要谨慎:给出置信度数字容易被过度解读("0.72 是什么意思"),更稳的做法是分档("依据充分/依据有限/无依据")。

还有一个"多跳"场景的难点:答案依据的是两段拼起来的信息(A 说标准,B 说适用城市),而单条引用只能指向其中一段。此时应当输出"多条引用 + 它们各自支持哪一部分",而不是勉强挂一条。

// 多跳引用的写法
答案:一线城市(按 B 的城市分级)住宿标准为 600 元/晚(按 A 的标准表)
引用 1:travel-2024 §3.2 "一线城市:600 元/晚"
引用 2:travel-2024 §2.1 "一线城市指北京、上海、广州、深圳"
// 只挂引用 1 → 用户不知道"一线"的定义从哪来

6. 排错:五种"引用不可信"

现象 成因 处理方向
答案对来源错 多份相似文档 逐句挂引用
来源只有文件名 粒度太粗 展示片段
引用文档已改版 无版本信息 存快照 + 标日期
点击链接失效 文档被移动 存 chunk 快照
用户核对后仍困惑 片段未高亮 高亮命中

第四项有一个彻底解法:在答案生成时把引用的 chunk 原文一起存进会话,于是即使原文档被改,用户看到的仍然是"当时依据的原文"。

评估引用质量也要有指标,否则"看起来有引用"就够过关了。三个可算的数就够用:

// 三个可算的引用质量指标(每周统计)
1 引用覆盖率 = 有引用的断言数 / 断言总数      (目标 > 90%)
2 引用准确率 = 校验通过的引用数 / 引用总数    (目标 > 98%)
3 引用可用率 = 用户能点击并定位的比例        (目标 > 95%)
// 只测第 1 项会得到一个"很好"的数字,而答案仍不可核对

7. 边界:什么情况不必做细粒度引用

场景 为什么 做法
闲聊与头脑风暴 无事实约束 不引用
单一权威来源(如内部规范) 几乎不会错 文档级引用够
已有人工复核流程 另有保障 粗引用即可
探索性问答(用户只是了解) 不用于决策 粗引用

判断口径一句话:用户会不会拿这个答案去"做决定"或"引用给别人"。最后一条与"成本"有关的边界:句级引用的成本主要在"切分粒度"。片段太大(如整章),quote 容易找到但定位不准;片段太小(如一句),定位准但上下文不足。常见折中是"200–500 字 + 句级偏移"。
会,就必须做到句级并可核对;只是了解情况,文档级引用足够。


8. 自测

  1. 引用的三种粒度各有什么可核对性?为什么"文档级引用"在实现上最便宜却最没用?
  2. 引用错配的四种形态里,哪一种最常见?为什么它比"没有引用"更危险?
  3. 为什么"让模型标注来源"不可靠?校验为什么必须独立于生成?
  4. 三道闸分别防什么?"一致性"这一闸能抓住哪类问题?
  5. 引用里为什么要带版本或生效日期?"存 chunk 快照"解决了什么问题?
  6. 判断"是否需要细粒度引用"的那个问题是什么?
S20-A16 · 引用与可追溯:答案挂在哪段资料上
https://www.clxhxhhr.top/posts/6505/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。