什么是RAG?为什么要做优化?

这张图画的是 RAG(检索增强生成) 的完整工作流程,说白了就是 "让 AI 先查资料再回答问题"。我用**"图书馆查资料写作业"**的比喻
一、建图书馆(上方流程,一次性准备工作)
文档 → Split 切分 → Chunks 数据块 → Embedding 向量化 → 向量数据库
| 步骤 | 例子 |
|---|---|
| 文档(PDF、Excel、TXT) | 你有一堆公司资料、产品手册、业务文档 |
| Split 切分 | 把一本厚书撕成一张张小卡片(每张小卡片叫一个 Chunk)。切法有讲究:按固定字数切、按句子切、按段落主题切、按语义相似度切等等(图上方那一堆就是各种切分策略) |
| Embedding 向量化 | 给每张卡片拍一张 "语义身份证照"------ 把文字变成一串数字(向量),意思相近的卡片,数字也相近 |
| 向量数据库 | 把所有带身份证的卡片按 "意思相近" 分门别类存进图书馆,方便以后快速找 |
二、查资料回答(下方流程,用户每次提问时走的路)
用户问题 → Embedding 向量化 → 去向量库检索(Retrieval) → 拿到相关片段(result)
↓
用户回答 ← LLM 大模型 ← 问题 + 检索到的资料
| 步骤 | 例子 |
|---|---|
| 用户问题 | 你问:"我们公司退货政策是什么?" |
| 问题 Embedding | 把你的问题也拍一张同样的语义身份证照 |
| Retrieval 检索 | 拿着问题的身份证,去图书馆里找意思最接近的几张卡片 |
| result 结果 | 找到了几张相关的资料片段 |
| LLM 大模型 | 把你的问题 + 找到的资料一起塞给 AI,让它 "参考这些资料,组织成通顺的话回答我" |
| Response 回答 | AI 给出基于公司真实资料的回答 |
三、右边写的是这个 "朴素版 RAG" 的毛病
图右边列了 Naive RAG(朴素 RAG) 的三大痛点:
- 检索效率低、结果不准 ------ 找卡片找得慢,还经常找错
- 用户问题太抽象 / 概念模糊 ------ 你问得太笼统,AI 不知道该找哪张卡片
- 生成质量差、不忠于原文事实 ------ AI 拿着资料还瞎编,或者答非所问
1.索引优化
1.1摘要索引
正常思维:
直接把庞大的数据文档,然后直接切分导入一个向量数据库。但是检索时问题百出
摘要索引思想:
把庞大的数据文档,然后直接切分成Chunk,但是通过大模型把每一个Chunk的摘要(Summrary)提取出来。类似于存储当前原始文档块的核心思想。在把摘要存储的向量数据库(Embedding Model)中。
对比之前普通 Naive‑RAG:普通是把原始 chunk 文本做向量入库 ;摘要索引是给每个块先写摘要,拿摘要去做向量检索。
建库阶段(预处理)
plaintext
原始Document文档
↓切分
Chunk‑1、Chunk‑2、Chunk‑3(原始原文片段)
↓每个chunk交给大模型生成简短摘要
Summary‑1、Summary‑2、Summary‑3(每一块的浓缩简短概括)
👉两件存储:
1. **摘要Summary**:送去Embedding模型,转为向量,存入向量数据库Vector Store(用来做检索匹配)
2. **原始Chunk原文**:存到 DocumentStore 文档仓库,保存完整原文,不做向量
通俗比喻:
一本厚书,把每一页(Chunk)写一张内容小纸条摘要 (Summary)。
- 把所有小纸条拿去做向量,放到向量库,用来搜索。
- 整本完整的书(原始 Chunk)另外放柜子 DocumentStore 保存。
用户提问查询阶段
- 用户问题 → 向量化,去向量库 Vector Store 检索,匹配的是【各个 chunk 的摘要向量】(不是原文!图中标注 1:检索文档段摘要)
- 向量库找到最匹配的摘要条目,拿到这个摘要对应的编号
- 根据编号,去
DocumentStore柜子里,把对应的完整原始 Chunk 原文取出来(图中标注 2:召回原文档段) - 把原始完整原文塞进 Prompt,交给 LLM 去生成回答。
为什么要这么干?优点
- 摘要文字很短,去除冗余废话,语义更聚焦。用户问题容易匹配上,解决普通 RAG:原文 chunk 噪音多、检索不准的问题。
比如一大段代码 + 说明文字,原文噪音大;LLM 提炼摘要,只保留核心含义,向量匹配更准。
缺点(坑)
- 预处理成本高:每个 chunk 都要调用大模型生成摘要,耗 token、耗时间。
- 如果摘要写歪、漏关键信息 → 检索直接就找不到对应原文,摘要错全流程错。
- 检索拿到的是摘要,但是传给大模型回答依然用原始完整 chunk,不是摘要,不能拿摘要去回答,摘要只是 "搜索钥匙"。
和普通 Naive‑RAG 对比
| 普通 RAG | 摘要索引 RAG |
|---|---|
| 原始 chunk 直接 embedding 入库 | chunk 先 LLM 生成摘要,摘要 embedding 入库 |
| 检索直接返回原始 chunk | 检索匹配摘要,再通过摘要索引找回原始 chunk |
| 适合短句、干净文本 | 适合长、噪音大、复杂文档 |
一句话总结: 摘要相当于 "目录小纸条",拿小纸条去搜索;搜到之后,再翻开真正的原文书页来回答用户。小纸条只用来搜,回答不用小纸条。
1.2假设性问题索引

通俗讲:拿文档块,让大模型猜 "用户会问什么问题",用猜出来的问题建向量库,不是拿原文建库
一、建库预处理流程(离线)
plaintext
原始Document文档
↓切分
Chunk‑1、Chunk‑2、Chunk‑3 原始文本片段
↓送入LLM
让大模型阅读这一段文字,自动生成N个用户可能会问的假设问题 Question‑1、Question‑2...Question‑n
👉 两件存储:
1. 这些【生成出来的假设问题】拿去做Embedding,向量存入Vector Store向量库(检索用)
2. 原始的Chunk原文保存到 Documents Store,不做向量,只存原文,保留映射关系:假设问题 ↔ 对应原始chunk
Chunk 原文:
DeepSeek公司成立于2023年7月17日LLM 读完,自动生成一堆假设问题:
- DeepSeek 公司什么时候成立?
- DeepSeek 成立日期是?
- 深度求索公司创立时间?
把上面这一堆问题做向量入库,而不是把 "DeepSeek 公司成立于 2023 年 7 月 17 日" 这句话入库。 原始的那段文字另外存起来。
二、用户线上查询流程
- 用户输入真实问题,做 Embedding 向量
- 去向量库,和一堆预先生成的假设问题向量做相似度匹配
- 找到相似度最高的那条假设问题,通过映射关系,取出它绑定的原始 Chunk 原文
- 将原始 chunk + 用户问题一起丢给 LLM,生成最终回答
为什么要这么做?解决什么痛点
普通 RAG 痛点:用户提问是问句,文档 chunk 是陈述句,句式不一样,向量相似度容易匹配不上
用户问:
DeepSeek什么时候成立?原文 chunk 是陈述句:DeepSeek公司成立于2023年7月17日陈述句 vs 问句,文本句式差异大,向量容易算出来相似度低,检索漏召回。
这个方案的妙招: 向量库里存的全部都是问句!用户输入也是问句,句式对齐,向量更容易匹配上,召回率提升。
缺点(现实坑)
- 成本高:每一个 chunk 都调用 LLM 批量造问题,消耗大量 token,文档多的时候预处理很慢。
- LLM 会瞎编问题:生成出来的假设问题,有可能和原文无关,造成错误映射。
- 向量库体积膨胀:1 个 chunk 生成 5 个问题,向量库数量直接翻 5 倍,占用存储、检索速度变慢。
1.3元数据索引

前面几种:靠文本语义向量 找内容; 元数据索引:一部分条件走标签 / 属性过滤(类似数据库 where 条件),一部分走语义向量搜索,两者结合检索。
什么是元数据 metadata
每一段 Chunk 除了正文文本,额外带上一堆标签信息: {"author":"bar", "category":"报告", "time":"2026‑08", "source":"文档A"} 这些不属于正文内容,是描述文档的属性,就叫元数据。
流程图完整流程(Self‑querying 自查询)
用户原始问题:What did bar say about foo(作者 bar 对于 foo 说了哪些内容)
- Query constructor(查询构造器,交给 LLM 处理) LLM 分析人类自然语言问题,自动拆成两部分:
- 语义搜索关键词:
foo→ 用来做向量语义检索 - 元数据过滤条件:
eq("author","bar")等价于 SQLwhere author = 'bar'
LLM 把人话,翻译成【向量搜索词 + 元数据过滤条件】
-
组装查询指令
search:"foo", where:{"author":"bar"} -
送入 Vector Store 向量库执行 先过滤元数据:只保留 author=bar 的文档块,在过滤后的子集里面,再做向量语义相似度检索。
不是全库比对向量,先筛标签,再算向量,缩小搜索范围,减少噪音。
现实例子
文档库里面几百份文档,每条 chunk 附带元数据:{作者,部门,年份,文件类型}
用户提问:"2025 年研发部写的关于机器人机械臂的方案是什么?"
- LLM 拆解: ①语义搜索词:
机器人机械臂方案(向量匹配正文) ②元数据过滤条件:year=2025,department=研发部 - 向量库先过滤:只留下 2025、研发部的 chunk,再在这批里面做语义检索。
✅优点
- 过滤掉大量无关文档,提升检索准确度,减少无关内容混入 Prompt。
- 区分「属性条件」和「文本语义」,适合多来源、多分类的知识库。
- 元数据过滤速度很快,不需要计算向量相似度,类似数据库查询。
❌缺点
- 入库阶段必须给每一条 chunk 补全元数据标签,没有标签就无法过滤。
- 依赖 LLM 解析用户问题生成 filter 条件,LLM 容易生成错误的过滤字段,导致查不到数据。
- 如果用户问题里面没有属性条件,就退化成普通向量检索。
2.查询优化
2.1查询优化-Enrich完善问题

多轮追问补全意图(RAG 前置对话补全流程)
普通 RAG 拿到用户一句话直接检索;这个流程是:大模型先判断用户提问信息够不够,信息缺就主动反问用户,循环收集信息,参数齐全之后,才去做 RAG 检索、输出答案。
完整流程图拆解
- 用户输入原始问题,交给 LLM 思考
LLM 思考三件事: ①这个问题信息完整吗? ②能不能直接拿去检索? ③我还缺哪些信息?
- 分支判断
-
✅分支 A:信息完整充足 LLM 总结、重写用户的完整问题 → 进入 RAG 检索 → 生成答案返回给用户。
-
❌分支 B:信息不完整,缺少关键参数 把缺失点整理成自然语言,向用户发起询问:
"你说的 XX,请问是指哪一个?还需要你补充 XX 信息" 用户看到提问,回复补充信息;回到最开头,形成循环。 循环 N 轮,直到参数全部补齐。
例子(机械臂场景)
知识库里面存机械臂操作文档。
用户输入:帮我抓取物体。
LLM 思考:信息不全!缺少:抓取哪个物体?目标坐标?夹持力度? → 反问用户:"请问抓取哪个物体?目标位置是多少?需要多大夹持力?"
用户回复补充:"抓取方块,坐标 x=0.3,y=0.2,力度 30N" 现在信息完整,LLM 整理完整问题,走 RAG 检索文档,输出操作方案。
现实价值(解决什么痛点)
普通 RAG 坑:用户说半句话,信息残缺,直接检索,召回一堆无关文档,答案错误。 这套机制就是:不强行猜用户意图,缺参数就主动问,多轮对话补齐条件再检索。
缺点
- 增加交互轮次,用户不能一次拿到结果;
- 需要维护对话上下文历史,LLM 要记住前面几轮的对话;
- 如果用户一直不给关键信息,会无限循环追问,需要设置最大循环次数上限。
2.2查询优化-Multi-Query多路召回

流程通俗拆解
plaintext
原始用户问题
↓【问题改写】LLM生成多个不同表达方式的子问题
→ 用户问题1、用户问题2、用户问题3(同一个含义,换不同说法)
↓每一个子问题分别做【信息抽取】,各自独立去向量库检索
→ 拿到多组检索结果,做结果合并去重
↓把合并后的全部文档块送入LLM
生成最终答案
举例子
用户原始问题:机械臂怎么降低抖动?
LLM 做问题改写,一次性输出 3 个变体:
- 机械臂运行抖动如何处理
- 机械臂关节振动抑制方法
- 怎么减少机械臂运动过程震荡
三个问题分别向量化,分别检索向量库。
- 问题 1 可能命中 A 文档
- 问题 2 可能命中 B 文档
- 问题 3 可能命中 C 文档
把 A/B/C 全部收集、去重,合并成一份上下文,再交给大模型回答。
解决什么痛点
普通 RAG:只用用户原始一句话去检索。 用户提问表达方式,和文档里面的措辞不一样,向量相似度低,相关文档漏召回。
用户说:"机械臂抖动怎么解决" 文档写:"关节振动抑制方案" 字面不一样,语义一样,直接检索很容易匹配不到。
✅多路改写:生成多种问法,多方向检索,把不同措辞的相关文档全部捞回来,提升召回率。
缺点
- 多次向量检索,耗时翻倍,生成 N 个问题就要执行 N 次检索;
- 检索结果会变多,很多冗余文档,必须做去重、过滤,不然上下文 token 暴涨;
- LLM 改写出来的子问题有可能跑偏,引入无关检索结果。
2.3查询优化-Decomposiyion问题分解

和上一节**多路问题改写 (Multi‑Query)**容易混淆:
- Multi‑Query:同一个意思,换多种说法,语义等价,只是措辞改写。
- Decomposition 问题分解:把一个复杂大问题,拆成多个逻辑独立的子问题,子问题之间有依赖关系。
有两种执行模式:串行执行、并行执行
1、并行执行(下面那张图)
流程:
- 用户复杂大问题 → LLM 做问题分解,拆成 3 个独立子问题 1/2/3
- 三个子问题互不依赖,可以同时跑:每个子问题分别做检索、分别生成子答案
- 收集全部子答案,交给 LLM 做汇总融合,输出最终完整答案
示例 用户问题:
机械臂型号A,最大负载多少?重复定位精度多少?供电电压是多少?拆出 3 个子问题,三者互不依赖,并行分别检索,拿到三份结果,最后合并输出。
2、串行执行(上面那张图)
子问题有先后依赖,后一个子问题需要前一个子问题的结果作为输入,不能一起跑。
- 原始大问题拆解为子问题 1、子问题 2、子问题 3
- 先跑子问题 1:检索 + 生成子答案 1 3. 把子答案 1,塞到子问题 2 的 prompt 里面,再跑子问题 2,得到子答案 2 4. 把子答案 2 传入子问题 3,执行子问题 3
- 全部跑完,汇总得到最终答案
机械臂例子 用户提问:
使用A机械臂,抓取方块,计算从起点到目标点的安全运动轨迹,并且给出避障参数。
- 子问题 1:查询 A 机械臂运动学参数(得到关节限位、最大速度)
- 子问题 2:依赖子问题 1 输出的参数,检索轨迹规划方案
- 子问题 3:依赖子问题 2 轨迹结果,查询对应的避障配置参数 必须一步一步串行,后面任务要拿前面的输出。
✅优点
- 对付复杂复合型问题,大问题直接检索很难召回全部信息;拆成小问题,每个子问题针对性检索,效果更好。
- 串行适合有逻辑依赖的链式推理;并行适合互不相关的多查询,可以并发提速。
❌缺点
- 拆解出来子问题多,多次调用 RAG,token、耗时上升;
- 串行模式总耗时会累加;
- 如果前面子问题检索出错,错误会向后传递扩散(串行风险尤其大);
- LLM 拆解子问题可能拆错,漏关键点。
3.1Rerank重排序-RPF

把两套 / 多套不同检索器返回的文档列表做融合重排序 。 典型组合:关键词检索(BM25) + 向量相似度检索。
BM25 擅长字面关键词匹配;向量检索擅长语义匹配;两者各有优缺点,RRF 把两份结果合并,取长补短。
公式
- \(rank_i(d)\):文档 d,在第 i 个检索器里面的排名,从 1 开始,第 1 名 rank=1,第 2 名 rank=2
- k:平滑常数,工业界一般取
k=60,避免排名靠前的文档权重爆炸 - 对同一个文档,在每个检索列表分别算分,全部累加得到总 RRF 分数;总分数越高,文档越优先。
示例
两套检索结果
- 关键词检索:
[doc1, doc2, doc3] - 向量相似度检索:
[doc1, doc3, doc4]
1)关键词检索计算(k=60)
- doc1 rank=1 → \(1/(60+1)=1/61≈0.0164\)
- doc2 rank=2 → \(1/(60+2)=1/62≈0.0161\)
- doc3 rank=3 → \(1/(60+3)=1/63≈0.0159\)
2)向量检索计算
- doc1 rank=1 → \(1/(60+1)=1/61≈0.0164\)
- doc3 rank=2 → \(1/(60+2)=1/62≈0.0161\)
- doc4 rank=3 → \(1/(60+3)=1/63≈0.0159\)
3)累加总分数
- doc1:\(0.0164+0.0164=\boldsymbol{0.0328}\)(两套都排第一,总分最高,排最前面)
- doc3:\(0.0159+0.0161=\boldsymbol{0.0320}\)
- doc2:\(0.0161+0=\boldsymbol{0.0161}\)(仅关键词检索出现)
- doc4:\(0+0.0159=\boldsymbol{0.0159}\)(仅向量检索出现)
✅融合之后最终排序:doc1 > doc3 > doc2 > doc4
核心思想:只要文档在任意检索系统排名靠前,就会拿到较高分数;多个检索器同时命中同一篇文档,分数叠加,优先级最高。
优点
- 不需要关心两种检索器原始分数值域,只看排名序号。BM25 分数、向量 cos 相似度数值范围完全不一样,没法直接相加;RRF 只拿排名,天然解决不同检索分数不能直接相加的痛点。
- 同时吸收关键词检索、语义向量检索两者优势:既抓字面关键词,又抓语义。
- 实现简单,没有复杂调参,k 一般固定 60 即可。
缺点
- 需要同时跑多路检索,性能开销变大;
- 完全抛弃原始相似度分数,只看位次;如果某一个检索器整体质量很差,依然会干扰融合结果。
工程对应场景(RAG 经典架构)
Hybrid 检索(混合检索)= BM25 关键词检索 + 向量检索 + RRF 倒数排名融合重排。 这是工业 RAG 最常用的标配组合。
💡对比其他重排: RRF:基于排名序号做融合; Cross‑Encoder 交叉编码器:输入文档 + query,模型输出相关性分数,做二次重排序。
一句话记忆:
RRF 倒数排名融合:只看文档在各个检索列表的名次,套公式累加分数,不直接使用原始相似度数值,实现多路检索结果合并。
