RAG 常用优化方法:从切块到评测,一层层调
答错了,先看是没找到,还是找到了没用好。
RAG 答错,原因大致分两种:对的内容没被检索出来,或者检索到了,模型没用好。这两种的改法完全不同,所以优化要按链路一层层看。
先有一份评测集
准备几十条真实问题,每条标上标准答案和它应该命中的文档片段。之后每改一处,都跑一遍对比。
检索看召回率和排序,比如 Recall@K、MRR。生成看回答是否忠于检索内容、有没有答到点上。Ragas 的 context recall、context precision、faithfulness、answer relevancy 就是干这个的。
解析和切块
文档解析错了,后面怎么调都救不回来。表格、多栏排版和扫描件最容易出问题,值得先抽查解析结果。
切块常用这几种做法:
- 按结构切: 沿标题、段落切,比按固定字数切更不容易把一句话切断。
- 加重叠: 相邻两块留一小段重复,保住跨块的上下文。
- 父子块: 用小块做检索,定位更准;命中后把它所在的大块交给模型,上下文更完整。
- 补上下文: 给每块加上所属文档的标题和章节,或者让模型写一句这块在讲什么,再一起做索引。
最后一种就是 Anthropic 说的 Contextual Retrieval。在他们的测试里,只加上下文向量,前 20 个片段的检索失败率降了 35%;再加 BM25,降 49%;再加重排,降 67%。
处理用户的问题
用户的问题往往很短,还可能依赖上文。直接拿去检索,效果通常不好。
| 方法 | 做法 | 适合 |
|---|---|---|
| 问题改写 | 结合对话历史,把「那它呢」补成完整问题 | 多轮对话 |
| 多路查询 | 生成几种不同问法,分别检索再合并 | 用词和文档差别大 |
| 问题拆解 | 把复合问题拆成几个子问题,分别检索 | 对比类、多步推理类问题 |
| HyDE | 先让模型写一段假想答案,用它去做向量检索 | 问题很短,和文档不像 |
这些方法都要多调用模型,响应会变慢,成本也会增加。
检索:向量加关键词
向量检索擅长找意思相近的内容,但对型号、人名、错误码这类精确词不太可靠。BM25 正好相反。两路一起查,再合并结果,就是混合检索。
合并常用 RRF,只看排名不看分数:每个文档得分是各路 1 / (k + 排名) 的和,k 常取 60。两路的分数尺度不同,也不用另外做归一化。
能确定范围的就用元数据过滤,比如部门、产品线、文档日期。范围先缩小,准确率和速度都会好,权限隔离也是在这一步做。
重排
向量检索为了快,只做粗略匹配。常见做法是先召回几十条,再用 Cross-Encoder 这类重排模型,把问题和每个片段放在一起打分,只留下最相关的几条交给大模型。
bge-reranker 就是常用的重排模型。这一步投入小、见效快。
生成
- 控制数量: 塞给模型的片段不是越多越好,无关内容会干扰回答。
- 注意顺序: 把最相关的片段放在开头或结尾,别埋在中间。
- 要求引用: 让回答标明出处,方便核对,也更容易发现模型在编。
- 允许说不知道: 检索内容里没有答案时,明确让模型直接说明,别让它硬答。
先做哪个
我会按这个顺序来:
- 建评测集。
- 抽查解析和切块。
- 上混合检索和重排。
- 针对答错的问题类型,加问题改写或问题拆解。
- 还不够,再试 Contextual Retrieval 这类需要重建索引的方法。
前几步成本低、见效快,也能先把大部分「没找到」的问题解决掉。
