RAG — 先分清是三个零件里的哪一个坏了
一句话总结
RAG 是检索器、重排序器和生成器三个部件的组合。收到“回答质量差”的反馈时,首先要做的是判断是哪一个部件出了故障。为此,每一次回答都必须记录检索到了什么、送进模型的又是什么。
为什么需要它
内部规章聊天机器人回答“退款申请期限为 14 天”,而规章上个月已改为 7 天。负责人首先在提示词中加入“优先采用最新规章”这句话,回答却依旧如此。后来才发现,新规章文档根本没有进入索引,模型只是忠实地按照拿到的文档作答。需要修改的地方从一开始就是索引。RAG 优化中最常见的时间浪费就是这种样子。
尽管如此,使用 RAG 的理由依然清楚。模型只掌握训练时的知识,并不了解内部文档或最新信息。也可以通过微调注入知识,但成本高、速度慢、难以更新。RAG 在问题到来时先找出相关文档放进提示词,让模型以这些文档为依据作答。知识变了,只需替换文档。不过,正如上面的例子,“只需替换文档”是否真正做到,需要另外确认。
它如何工作
流水线分为三个部件。
检索器(Retriever)。 广泛取回与查询相似的文本块。有稠密检索(嵌入)和稀疏检索(BM25 之类的关键词方法),两者结合的混合检索是实践中的标准做法,因为它们的失败方式不同。嵌入擅长处理改写,却不擅长产品代码之类的精确匹配;关键词则相反。合并两份结果时,由于分数尺度不同不能直接相加,因此普遍采用基于排名的方法。倒数排名融合(RRF)按文档在各个列表中的排名计分并求和。
RRF(d) = sum over lists of 1 / (k + rank(d)) k is commonly 60
k 是一个常数,用来压制前几名独占结果。Elasticsearch 的默认值也是 60。
重排序器(Reranker)。 把检索器广泛取回的候选重新精细排序。它把查询和文档一起送进模型打分,所以准确但慢,只用于前几十条结果。
生成器(Generator)。 以取回的上下文为依据生成回答。
诊断质量问题时,必须把检索和生成分开来看。如果正确文档根本没被检索到,再怎么调整生成器也无济于事,要看检索指标。如果正确文档就在上下文中,回答仍然错误,那才是生成器的问题,要看忠实度。
| 指标 | 衡量什么 |
|---|---|
| Recall@K | 前 K 个结果中是否包含正确文档 |
| MRR | 第一个正确结果排在第几位(排名倒数的平均值) |
| Precision@K | 前 K 个结果中相关文档所占比例 |
| Faithfulness | 回答是否以上下文为依据 |
| Answer Relevancy | 回答是否切合问题 |
用一个小例子计算一下,就能看出两个指标的区别。假设三个查询的正确文档分别出现在第 1 位、第 3 位和前 3 名之外。Recall@3 为三个中有两个命中,即 0.667。MRR 是排名倒数 1、1/3、0 的平均值,即 0.444。Recall 只看“有没有进来”,不区分第 1 位和第 3 位;MRR 则会对这种差别扣分。如果系统只把前几个结果放进上下文,两个指标都要看。
实际项目中会出什么问题
把症状与出故障的部件配对,调查会快得多。
过时的索引。 文档已经改了,索引还是旧的。症状是“说已经修改的内容没有生效”。文档删除了、文本块却留在索引里继续被检索到,也属于同一类问题。索引更新必须与原文变更绑定为同一套流程,并且每个文本块都要附上原文的更新时间,才能核查。
权限泄露。 用户无权查看的文档被检索出来混进了回答。在生成之后再遮挡为时已晚,因为模型已经读过并总结了这些内容。权限过滤必须在检索阶段进行。
版本冲突。 旧规章和新规章同时被检索出来时,模型会二选一或者混在一起。症状是同一个问题的回答时而这样时而那样。要在检索前过滤掉已废止的文档,或者根据元数据中的生效日期只保留最新版本。
在边界处被截断的答案。 正确的句子被切在两个文本块之间,哪一块都进不了前几名。这是分块策略的问题,下一篇理论课会讲。
上下文里有、回答却错了。 这时才轮到生成器的问题。常见情况是无关的文本块太多,或者正确的文本块埋在长上下文的中间。用重排序减少数量,并把最相关的文本块放在前面。
似是而非的编造。 即使是语料之外的问题,模型也会以最相似的文档为依据编出答案。检索分数低于阈值时不作答并如实说明,要好得多。阈值用评测集确定:太高会拒绝本可回答的问题,太低则幻觉增多。另外,给回答附上出处不是一项功能,而是安全装置。用户能够核对原文,错误回答的危害就会减小,运维中也能观察到哪些文本块被错误检索。
如何确认
对每一次回答,至少用一行记录以下内容:查询、检索到的文本块 id 与分数、实际送进模型的文本块 id、回答、回答引用的出处。没有这份记录,收到反馈时就无法判断是哪个部件的问题。
收集到失败案例后,分成三类。
gold chunk not in top-K -> retriever (index, chunking, query)
in top-K but not in context -> reranker or context budget
in context but answer wrong -> generator (prompt, ordering, noise)
评测集不必很宏大。只要给几十个真实用户问题标上正确文档,就能计算 Recall@K 和 MRR;每次调整分块或权重时重新测量同样的数字,就能判断是变好还是变差。没有这些数字就改提示词或配置,等于凭感觉行事。
接下来阅读
紧接着的理论课讲确定检索单位的分块。之后的实验中,把文档切成文本块并用 TF-IDF 建立索引,检索 8 个查询。与正确文档列表对比,计算 Recall@3 和 MRR,组装上下文,用词汇重叠判断回答是否以上下文为依据,并实现一条对语料之外的问题不予作答的阈值规则。