RAG 怎样从文档得到可引用证据
RAG 把外部文档作为可更新、可追踪的非参数知识。它不是“向量数据库 + LLM”两个黑盒,而是两条相接的数据路径。
索引侧
文档 → 解析 → 结构/权限/版本 → 切块 → Embedding
→ 向量/关键词索引 → chunk_id 与来源元数据查询侧
问题 → query 处理 → 版本/权限限定的候选集合 → 召回 → 重排
→ 上下文组装 → 模型回答 → 引用映射固定政策文档的每个 chunk 必须保留 document_id、section、version、valid_from/to、ACL 和原文位置。否则回答中的 [1] 只是编号,不能回到证据。
切块权衡
过小会丢上下文,过大则降低检索精度并浪费 context。优先利用标题、段落、表格等结构;重叠用于保留边界信息,但会增加重复候选。切块规则必须版本化。
混合召回与重排
向量检索擅长语义近似,关键词检索擅长精确名称、编号和日期。融合后再用重排器对 query—chunk 相关性排序。召回扩大候选,重排改善前几名;二者评测不能合并成一个“RAG 分数”。
上下文组装
按 token 预算选择证据,保留来源分隔和优先级。冲突文档要依据版本/生效时间处理,而不是让模型自行猜。检索内容是不可信数据,不能覆盖系统指令或获得工具权限。
引用成立的条件
回答中的具体主张能定位到引用 chunk,chunk 原文直接支持主张,版本/权限有效。引用相关但不支持数字,仍是 grounding 失败。
常见错误
- PDF 解析丢表格列;
- chunk 没有标题和版本;
- 先检索再做权限过滤导致侧信道;
- top-k 都是同一重复文档;
- 引用编号与最终上下文重排错位;
- 新索引上线未保留旧版本,无法重放。
从一份报销制度追到回答
教学问题:2026 年员工住宿报销上限是多少? 固定材料有 2025 版制度、2026 版制度和仅主管可读的制度;按条款切块,保留来源与章节。它们是人为编写的练习文本,不是任何公司的真实政策。
| chunk_id | 原文 | 生效年份 | 权限 |
|---|---|---|---|
| travel-2025:s1 | 住宿:每晚报销上限 300 元。 | 2025 | staff |
| travel-2026:s1 | 住宿:每晚报销上限 400 元。 | 2026 | staff |
| travel-2026:s2 | 餐饮:每日报销上限 100 元。 | 2026 | staff |
| executive-2026:s1 | 住宿:每晚报销上限 800 元。 | 2026 | executive |
逐步追踪:
- 查询日期为
2026-09-06、角色为staff,先排除旧版本和主管专用文档; - 提取查询中出现的
住宿、报销、上限三个词; - 本例按匹配词数量排序:住宿条款 3 分,餐饮条款 2 分;
- 选择匹配费用类型的住宿条款,将原文与
travel-2026:s1一起放入上下文; - 若接生成模型,Prompt 应明确“只依据以下证据回答,无证据则说明无法确定”,并把
[1]与该 chunk 绑定; - 合格回答“每晚 400 元 [1]”需逐项核对:数字 400、单位每晚、当前版本、身份权限和引用位置。
3 分 是本实验的简单关键词计数,不是向量相似度或真实重排模型评分。先把完整链条跑通,再替换检索器,并比较替换前后的召回证据。
代码实际运行的是检索与原文摘录,没有伪造 LLM 调用。它输出候选 ID/得分、摘录和引用映射,预期当前员工问题返回 400 与 travel-2026:s1。生成模型接入后,应沿用这个引用映射,并独立检查生成的每个主张;摘录正确不代表自由生成一定正确。
换问题、改条件,再验收
- 将查询日期改成
2025-06-01:应返回 300 和旧版引用; - 将身份改成
guest:候选应为空,无引用; - 问交通费:应说明无对应证据,不能借用住宿额度;
- 将问题改为“餐饮报销上限是多少”:先不看代码预测引用和数值,再运行。答案为
travel-2026:s2和 100 元/日; - 故意删除权限判断:如果员工候选中出现
executive-2026,即使最终回答碰巧为 400,权限测试也必须失败。
本地运行:python3 examples/python/rag_trace.py。从这里进入真实 RAG 时,保留原文、候选、最终上下文、模型输入输出和引用映射五份中间结果,才能判断错误发生在哪一步。
自测
先作答,再查看解释。记录只保存在当前浏览器。
打印版答案:1. 错;2. 版本和生效时间;3. 身份、位置、版本、权限和处理版本。
来源:Retrieval-Augmented Generation。
下一步
进入怎样分别评测检索与回答。