第 5 章 RAG、检索与长期记忆
手算 Recall@k,分析证据、引用、版本与长期记忆
5.1 答案为什么需要外部证据
本章目标:能拆出检索、证据选择、答案生成和引用核验四个环节;能测量漏检,并设计可更新的长期记忆。
假设研究助手被问“当前会员运费是多少”。模型可能记得旧规则,也可能编造一个合理数字。更可靠的路径是读取适用于当前用户和版本的政策文档,再依据具体段落回答。
原始 RAG 论文 [导读] 将参数化模型和外部非参数化知识结合。现代工程中的 RAG 常泛指检索后生成,不要求复现论文训练架构。它提供更新知识和追溯来源的机会,但检索错了、解释错了或引用错了,回答仍然不可靠。
5.2 从文档到索引:切块不是机械截字
设一份文档包含“普通用户满 100 免运费”“会员免运费”“负数订单不合法”。如果切块只保留“免运费”,丢掉适用条件,后续生成就可能把规则泛化到所有输入。
好的切块应尽量保留标题、条件和完整表格行。过小块丢条件,过大块降低定位精度并占用上下文;重叠可以缓解跨边界信息,但也增加索引和去重成本。对于代码,函数或模块边界通常比固定字数更有解释意义。
{
"chunk_id": "policy-v2-member-01",
"source_id": "shipping-policy",
"version": "v2",
"section": "会员规则",
"access_scope": "public",
"text": "合法订单的会员运费为 0。"
}
来源 ID 与版本必须区别开。相同文档更新后,旧块应删除、失效或进入明确的历史集合。否则向量相似的旧政策可能一直被召回,造成“有来源但过期”的答案。
5.3 召回、重排和证据选择怎样配合
关键词检索适合精确名称、错误码和符号;语义检索适合不同表述的同一意图。BM25 利用词项统计来排序,向量方法利用表示空间的相似性;两者都不会自动判定一段材料对具体结论是否充分。
混合召回把多个候选集合合并,再做去重和重排。不同检索器的原始分数通常不能直接相加,需要校准或使用基于排名的融合。重排器可以更细地比较问题与片段,但增加计算,并不能找回从未进入候选集的相关文档。
因此先检查召回有没有找齐证据,再检查排序有没有把关键证据放在预算内。权限与版本范围应在交给模型前执行;依赖模型读完私有文档后“不要说出来”,已经晚于数据边界。
5.4 离线实验:亲手计算 Recall@k
下载 retrieval_lab.py,或从仓库根目录运行:
python3 agent/examples/retrieval_lab.py
脚本有四份文档:D1 普通规则 v2;D2 会员规则 v2;D3 旧规则 v1;D4 内部例外。它先按公开范围与 v2 过滤,再用英文 token 重叠数排序,平分时按 ID 排序。这是便于手算的检索器,不是 BM25,也不是向量搜索。
| 查询 | 标准相关证据 | k=1 召回 | k=2 召回 |
|---|---|---|---|
standard shipping threshold |
D1 | D1 | D1、D2 |
member shipping fee |
D2 | D2 | D2、D1 |
standard member shipping |
D1、D2 | D1 | D1、D2 |
refund deadline |
无可用证据 | 空 | 空 |
Recall@k = 前 k 个结果中命中的相关证据数 / 标准相关证据总数。第三题 k=1 只命中两份中的一份,召回率为 0.5。前三个可回答问题的宏平均分别是 (1+1+0.5)/3=5/6 和 1。无答案问题的分母为零,脚本单独标记应拒答,不硬填一个 Recall 值。
注意 k=2 的前两题多出无关块,召回上升不等于精度提高。Precision@k 关注取出的结果中多少相关。实际检索还要考虑答案是否需要多个来源、段落粒度标注以及文档间条件关系。
5.5 从证据到回答:引用存在不等于支持结论
可以给模型输入带 chunk_id 的证据,再要求每个事实对应来源。回答“会员运费为 0 [D2]”可以获得支持;回答“所有用户任何金额都免费 [D2]”即使链接真实,也误扩展了会员条件。
引用核验至少问四件事:来源可访问吗?版本适用吗?片段表达了这条事实吗?结论遗漏了条件或不确定性吗?查询“退款期限”时没有材料,合理行为是说明当前证据不足并选择进一步检索或停止,而不是用运费文档凑出答案。
遇到冲突要保留条件。v1 阈值 200 与 v2 阈值 100 不应求平均得到 150;它们首先是版本适用性问题。不同地区或用户级别的规则也可能各自正确。先确定查询范围,再比较内容。
5.6 长期记忆:写入后还要能更新与删除
RAG 主要寻找外部事实供本次使用;长期记忆保存跨会话可能有用的信息;上下文保存当前工作所需内容。三者可以共享存储设施,但生命周期不同。
| 信息 | 更适合的保存位置 | 写入条件 |
|---|---|---|
| 用户偏好报告用中文 | 用户偏好记忆 | 用户明确表达且允许保存 |
| 当前测试失败日志 | 当前轨迹/产物 | 本次调试所需,可过期 |
| 已验证修复模式 | 程序性经验或 Skill | 说明适用条件并重新评测 |
| 当前公开政策 v2 | 文档索引 | 来源、版本与访问范围清晰 |
记忆条目应含内容、来源、确认时间、作用范围和更新规则。模型说“用户应该喜欢表格”只是推断,不应直接写成用户偏好。用户后来说“这次用段落”,可能只是本次选择;若说“以后都用段落”,才涉及更新长期偏好。写入策略需要理解这一区别。
错误经验也会传播。一次测试遗漏负数会员,不应总结成“会员判断永远放最前”。经验应携带反例与适用条件;发现冲突时修订或删除,而不是无限追加相互矛盾的条目。
5.7 练习与参考检查
在检索脚本中移除版本过滤,观察 D3 是否进入某些前 k 结果;把它排除不是为了提高漂亮分数,而是因为本次任务限定当前政策。再给查询增加同义词,观察 token 重叠检索为何漏检,解释语义检索可能帮助的地方。
为四个问题各写一份带证据的回答,标注“事实、适用条件、来源”。参考验收:无内部 D4 泄漏;当前问题不引用 D3;组合查询保留普通与会员两种规则;退款问题明确证据不足。还要保留未达到的指标,不能只展示三个好看的答案。
本章交付物:小型标注集、召回与引用核验表、一份记忆写入/更新规则。这样你能知道检索系统究竟在哪一层失败。