主题
第5章 检索增强进阶
5.1 问题场景
基础检索增强系统能跑通后,常见问题并不会自然消失,反而会以更复杂的形式出现:
- 命中率波动大。
- 引用不稳定。
- 复杂问句下回答质量下降。
- 同一个问题换一种问法就召回失败。
- 调了一堆参数,但效果提升不可复现。
这也是为什么很多团队在完成第 4 章的“最小 RAG 闭环”后,会进入一个很尴尬的阶段: 系统看起来能用,但一上线或一扩展样本规模,问题就不断暴露。 真正的挑战不再是“有没有 RAG”,而是“RAG 是否稳定、可解释、可复现”。
前端迁移提示
第 5 章和前端工程也很像,因为它本质上是在做系统优化、实验对照与证据呈现:
- 前端性能优化思维 -> 检索链路参数与上下文优化。
- A/B 实验与埋点思维 -> 不同检索策略的对照实验。
- 列表排序和筛选思维 -> 重排、裁剪和上下文压缩。
- 结果可视化能力 -> 评测结果、失败样本和引用稳定性展示。
最小项目目标
本章建议先完成一个“RAG 优化实验最小闭环”项目,例如:
- 固定一批低分样本。
- 设计一组小型实验矩阵。
- 记录查询改写、召回数量、重排策略变化前后的结果。
参考入口:
- 示例2 RAG接口与评测
docs/examples/assets/samples/chapter-05-rag-optimization-log.json- 章节代码片段索引
5.2 核心原理
RAG 进阶优化的关键,不是“继续调参数”这么简单,而是把“问题归因 -> 实验设计 -> 指标对比 -> 配置固化”做成一套标准过程。 如果没有这个过程,优化很容易沦为随机试错。
一、查询优化:让系统更容易理解用户真实意图
同一个业务问题,用户可能会有很多问法。 如果不做查询优化,常见问题包括:
- 表达不同但本质相同的问题,召回结果不一致。
- 复杂问句里关键信息被淹没。
- 长尾问句命中率明显偏低。
因此查询优化常常是第一层收益来源。
二、检索融合:不要把希望押在单一路径上
只用向量检索或只用关键词检索,都可能产生偏差。 在很多业务场景里,融合检索会更稳,因为它同时兼顾:
- 语义相近。
- 关键词命中。
- 文档结构信息。
三、结果精排:真正进入模型上下文的证据必须更干净
很多 RAG 系统的问题,不是没召回正确文档,而是正确文档排得不够前,或者被无关上下文稀释。 重排和裁剪的目标是:
- 把最关键证据放在最前。
- 删掉噪音片段。
- 减少上下文浪费。
四、上下文压缩:不是证据越多越好
RAG 优化的一个高频误区是: “既然模型错了,那就给更多证据。” 实际情况往往相反:
- 噪音增多。
- 成本上升。
- 延迟变长。
- 模型注意力被稀释。
因此“更少但更相关”的上下文,通常比“更多但更乱”的上下文更有效。
五、评测驱动:进阶优化必须和样本绑定
如果没有固定评测集和实验记录,你很难回答:
- 这次优化到底有收益吗。
- 收益来自哪一项改动。
- 这种收益是否可复现。
5.3 实操步骤
推荐做法不是同时改很多变量,而是先做“固定评测集 + 小步实验矩阵 + 失败归因”三件事。 先把优化方法跑起来,再逐步扩展复杂策略。
步骤 1:建立稳定基线
先在固定评测集上记录当前指标:
- 命中率。
- 引用准确率。
- 拒答率。
- 平均延迟。
步骤 2:挑低分样本做误差归因
建议先把表现最差的一批样本挑出来,判断它们主要属于:
- 查询表达问题。
- 召回问题。
- 重排问题。
- 生成解释问题。
步骤 3:设计小规模实验矩阵
建议一次只变动 1 到 2 个参数或策略因素,例如:
- 是否做查询改写。
top_k从 3 调到 5。- 是否启用重排。
- 片段长度调整。
步骤 4:记录实验结果
建议至少记录:
- 实验 ID。
- 改动点。
- 样本集名称。
- 指标变化。
- 结论。
参考入口:
docs/examples/assets/samples/chapter-05-rag-optimization-log.json
步骤 5:固化收益明显的配置
不是所有优化都值得保留。 建议优先固化:
- 收益明显。
- 成本增长可接受。
- 结果可复现。
- 对高频场景有效。
步骤 6:同步准备前端和证据材料
建议至少保留:
- 优化日志样本:
docs/examples/assets/samples/chapter-05-rag-optimization-log.json - 评测结果样本:
docs/examples/assets/samples/example-02-eval-result.json - 截图清单:
docs/examples/assets/screenshots/README.md - 章节片段索引
步骤 7:把优化能力映射回项目线
如果你已经做通最小优化闭环,可以继续映射:
- 项目线 A:知识问答效果优化。
- 第 8 章:优化结果的回归评测与门禁。
- 第 9 章:优化配置的上线与回退策略。
5.4 常见坑
坑 1:只追求单一指标最大化
如果只盯一个指标,整体体验反而可能变差。
坑 2:同时改动多个变量
最后根本不知道哪一项真正起作用。
坑 3:没有失败样本复盘
这样同类问题会在后续版本里反复出现。
坑 4:召回正确却不做重排和裁剪
正确文档被噪音淹没,最终效果仍然不稳。
坑 5:把“更多上下文”当成万能解法
上下文越多不一定越好,很多时候反而会伤害效果和成本。
坑 6:实验记录不完整
没有实验日志,后面很难把优化过程讲清楚,也不利于作品集展示。
5.5 验证方式
RAG 进阶优化做完后,不应只看“这次感觉更好了”,而要看它是否真的形成了可复现的优化方法。
一、指标波动是否可控
- 固定评测集多次运行结果是否稳定。
- 命中率波动是否控制在合理范围。
二、复杂问句是否明显改善
- 长尾问句是否比基线更稳。
- 引用是否更集中、更准确。
三、实验记录是否完整
- 每次优化是否有实验 ID、改动点和结果记录。
- 是否能回溯“为什么保留这个配置”。
四、前端证据是否更可信
从前端视角检查:
- 引用来源是否更稳定。
- 引用片段是否更短、更清晰。
- 风险提示是否更准确。
五、证据材料
- 优化日志样本:
docs/examples/assets/samples/chapter-05-rag-optimization-log.json - 评测结果样本:
docs/examples/assets/samples/example-02-eval-result.json - 截图清单:
docs/examples/assets/screenshots/README.md - 图解配图:
docs/examples/assets/diagrams/chapter-04-rag-flow.svg - 章节片段索引
5.6 面试表达
我在基础检索增强可用后,重点做了查询优化、检索融合、结果精排和上下文压缩四层优化。 通过固定评测集、实验矩阵和失败样本复盘,把效果提升从偶发结果变成可复现的方法论。
实战案例:制度问答命中率优化
- 背景:企业制度问答在复杂问句和长尾问题上命中率偏低,引用不稳定。
- 动作:引入查询改写、混合检索和重排,并以实验矩阵记录不同策略的收益与成本变化。
- 结果:复杂问题命中率和引用一致性显著改善,优化过程也可被后续版本复用。
推荐答题框架
如果面试官问“RAG 进阶和入门的差别是什么”,可以按下面顺序回答:
- 先讲基础闭环和稳定可用是两回事。
- 再讲查询优化、检索融合、重排和压缩。
- 再讲如何用实验矩阵和失败样本做归因。
- 最后讲如何固化配置,并接入后续评测与上线流程。
5.7 练习任务
- 选 20 条低分或高风险样本,做一次失败原因标注。
- 设计 2 组实验矩阵,分别测试查询改写和重排策略。
- 记录每次实验的改动点、指标变化和结论。
- 固化一套当前最优配置,并说明为什么保留它。
- 基于
docs/examples/assets/samples/chapter-05-rag-optimization-log.json,整理出你的第一版 RAG 优化日志。
5.8 验收清单
- 任务完成率 >= 85%
- 关键指标达标率 >= 90%
- 异常场景通过率 >= 90%
- 至少完成 1 组实验矩阵和 1 份优化日志。
- 至少保留 1 份优化样本、1 组截图占位清单和 1 份章节代码片段索引。
- 至少能用 2 分钟讲清楚“为什么 RAG 进阶不是继续调参数,而是建立一套可复现的优化方法”。
5.9 💡 自托管 RAG 进阶 — 本地环境下的检索优化
🏠 自托管替代方案
第 4 章介绍了基础的本地 RAG 搭建(BGE-M3 + ChromaDB),本节聚焦在自托管环境下如何应用第 5 章的进阶优化技术。
自托管环境下的进阶优化
本章介绍的 RAG 优化技术(查询改写、重排序、混合检索等)在自托管环境下同样适用,但有一些差异需要注意:
| 优化技术 | 云端实现 | 自托管实现 | 差异说明 |
|---|---|---|---|
| 查询改写 | GPT-4o 改写 | Qwen3-8B 改写 | 本地小模型改写质量稍低,建议多生成几个变体 |
| 重排序 | Cohere Rerank API | BGE-Reranker-v2 本地 | 开源重排模型精度接近商业 API |
| 混合检索 | 云端向量+关键词 | Qdrant 混合检索 | Qdrant 原生支持稀疏+稠密混合 |
| Chunk 策略 | 大上下文窗口容错 | 需更精细分块 | 本地模型上下文窗口通常更小 |
本地重排序方案
typescript
// 使用 Ollama 运行本地模型进行重排序打分
async function localRerank(query: string, documents: string[]): Promise<string[]> {
const scored = await Promise.all(
documents.map(async (doc) => {
const res = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'qwen3:8b',
prompt: `判断以下文档与查询的相关性,只回答 1-10 的分数。\n查询:${query}\n文档:${doc}\n分数:`,
stream: false,
}),
});
const data = await res.json();
return { doc, score: parseInt(data.response) || 0 };
})
);
return scored.sort((a, b) => b.score - a.score).map((s) => s.doc);
}自托管 RAG 的特殊调优建议
- Chunk 更小:本地模型上下文窗口通常 4K-8K,建议 chunk 控制在 300-500 token
- Top-K 更少:检索结果控制在 3-5 条,避免超出上下文窗口
- Embedding 维度对齐:确保检索和存储使用同一模型的同一维度
- 缓存策略:本地推理较慢,对高频查询做结果缓存
🖥️ 前端迁移提示:自托管 RAG 调优就像前端性能优化——资源有限时,需要更精细的 budget 分配(chunk size = bundle size,top-K = 网络请求数)。