我之前写过一篇 RAG 入门的文章(《RAG 实战:构建一个私有知识库问答系统》),主要讲最简单的 "向量检索 + LLM" 模式。
用了半年之后发现,Naive RAG 在真实场景中只能解决大约 60% 的问题。剩下的 40% 需要更精细的设计——这也是 RAG 从"简单"走向"复杂"的原因。
这篇文章梳理 RAG 的进阶技术演进路线。
Naive RAG 的问题
先回顾一下最基本的 RAG 流程,以及它的问题:
用户提问 → Embedding → 向量检索 → Top-K 文档 → 拼 Prompt → LLM 回答
问题 1:检索不准确
"我买回来的产品有裂痕" → 向量匹配到"产品出厂质检流程"
(语义相似但不相关——都是讨论产品质量,但目的完全相反)
问题 2:信息不够
"帮我对比一下 Redis 和 Memcached"
→ 如果两个文档是独立的,分别检索都只有部分信息
问题 3:跨文档推理
"根据第一季度的销售数据和去年的库存策略,制定今年的采购计划"
→ 需要综合多个文档的信息才能回答,但每个文档单独检索得分都不高
问题 4:检索结果质量差
Top-3 里可能有两个是噪声、一个是答案
→ LLM 被噪声影响,输出质量下降
解决这些问题就是 RAG 进阶的方向。
进阶技术演进路线
RAG 的进化可以分为三代:
第一代:Naive RAG
文档 → 切割 → Embedding → 向量检索 → LLM 生成
缺点:检索精度有限,无法处理复杂查询
第二代:Advanced RAG
Naive + Query Rewriting + HyDE + Reranker + Hybrid Search
效果提升:让检索更精准
第三代:Agentic RAG
Advanced + Self-RAG + Graph RAG + 多步检索
效果提升:让推理更智能
第二代:Advanced RAG
Query Rewriting(查询改写)
用户的原始问题往往不是最好的检索查询。
用户原始问题:"这个怎么用?"
→ 缺少上下文,向量检索效果差
改写后:"详细描述 Redisson 分布式锁的使用方法"
→ 包含关键词,检索命中率大幅提升
Query Rewriting 可以用 LLM 来做,也可以用小模型快速改写。一个简单的实现:
def rewrite_query(user_query: str, history: list[str]) -> str:
"""用 LLM 把用户问题改写为更适合检索的形式"""
prompt = f"""基于对话历史,把用户问题改写成适合向量检索的形式。
要求:提取核心实体和问题意图,去除指代。
对话历史:{history}
用户问题:{user_query}
改写后的检索查询:"""
return llm.generate(prompt)
为什么不直接用 LLM 做检索? Rewriting 和最终回答用同一个 LLM 当然可以,但成本高。实践中用一个小模型(甚至一个规则 + 关键词提取)做改写,大模型只做最终回答,成本结构更合理。
HyDE(Hypothetical Document Embedding)
HyDE 的思路:先让 LLM 根据问题生成一个"假想的完美文档",然后用这个假文档的 embedding 去检索。
用户问题:"红黑树的插入过程是怎样的?"
HyDE 生成假想文档:
"红黑树的插入过程包括以下步骤:
1. 将新节点插入到树中,颜色设为红色
2. 检查并修复红黑树性质
3. 如果父节点是黑色,插入完成
4. 如果父节点是红色,需要进一步调整..."
用假想文档的 embedding 检索:
→ 找到更高质量的相关文档
为什么有效?因为问题和答案在语义空间中的位置不同。一个短问题的 embedding 可能偏离目标文档很远,而假想的答案文档更接近真实文档。
实际效果:在某些数据集上,HyDE 可以让 Recall@5 提升 10-15%。但有一个限制——对 LLM 生成假文档的质量有要求,如果假文档本身质量低,反而会降低检索效果。
Reranker(重排序)
向量检索的第一阶段(初筛)追求速度,返回 Top-100。Reranker 对 Top-100 做精细的交叉编码排序,找出真正的 Top-5。
第一阶段(向量检索):
100 个候选文档
速度:~50ms
精度:60-70%
第二阶段(Reranker):
对 100 个候选做交叉编码排序
速度:~500ms
精度:85-95%
Reranker 比向量检索慢一个数量级,但因为只对 Top-100 做排序,总的延迟增加可以接受。
权衡:加 Reranker 意味着系统多了一个组件(部署成本),每次检索多花几百毫秒。但带来的检索精度提升非常可观。是否值得取决于你的场景——如果用户对答案准确率要求高,值得上。
Hybrid Search(混合检索)
向量检索擅长语义匹配,但词法匹配(关键词精确命中)上的效果不如 BM25。
查询:"支付接口超时设置"
向量检索结果:
1. "payment gateway timeout configuration" (语义相似)
2. "超时重试机制的设计" (语义相关)
BM25 检索结果:
1. "支付接口超时如何处理" (包含完整关键词)
2. "HTTP 请求超时设置" (部分匹配)
混合检索 = 向量检索 + BM25,然后合并排序。
def hybrid_search(query: str, alpha: float = 0.5) -> list[Document]:
"""混合检索:合并向量相似度和 BM25 分数"""
vector_results = vector_search(query, top_k=100)
bm25_results = bm25_search(query, top_k=100)
# Reciprocal Rank Fusion (RRF)
scores = {}
for rank, doc in enumerate(vector_results):
scores[doc.id] = scores.get(doc.id, 0) + 1 / (60 + rank)
for rank, doc in enumerate(bm25_results):
scores[doc.id] = scores.get(doc.id, 0) + 1 / (60 + rank)
# 按总分排序
ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return [docs[id] for id, score in ranked[:top_k]]
RRF(Reciprocal Rank Fusion)是合并两种排序结果的常用方法,不需要调参,效果好于加权平均。
第三代:Agentic RAG
Advanced RAG 优化了"检索"环节,但检索策略本身仍然是固定的——每次查询,都做一次 embedding → 检索 → 生成。
Agentic RAG 在此基础上增加了检索决策的智能性。
Self-RAG:让 LLM 自己决定要不要检索
传统 RAG 是"每次都检索"。但有些问题不需要检索(比如闲聊),有些问题一次检索不够。
Self-RAG 引入两个机制:
反思 token(Reflection Tokens):
在每个预测阶段,模型输出额外的标记:
- [检索]:需要检索 → 触发检索过程
- [不检索]:不需要 → 直接生成
- [相关]:检索结果与问题相关
- [不相关]:检索结果不相关
- [支持]:生成内容被检索结果支持
- [不支持]:生成内容不在检索结果中
Self-RAG 的训练需要额外标注数据,不是随便一个模型就能用的。但现在的主流模型(GPT-4o、Claude、DeepSeek)已经可以通过在 prompt 中描述这个模式来模拟 Self-RAG 的行为。
Graph RAG:利用知识图谱的 RAG
传统的 RAG 把文档切块嵌入,丢失了文档之间的结构关系。Graph RAG 在此基础上构建知识图谱。
传统 RAG:
[文档块1] [文档块2] [文档块3] [文档块4]
(互相独立,没有关联)
Graph RAG:
┌─ 实体 A ─→ 关系1 ─→ 实体 B
│
关系2 关系3
│ │
▼ ▼
实体 C ─→ 关系4 ─→ 实体 D
Graph RAG 的处理流程:
1. 实体提取:从文档中提取命名实体(人、组织、概念、产品)
2. 关系构建:识别实体之间的关系
3. 社区检测:把紧密关联的实体和文档聚合成社区
4. 社区摘要:对每个社区生成摘要
5. 查询时:
第一步:在实体级别检索(找相关实体和关系)
第二步:在社区摘要级别检索(找相关社区总结)
第三步:合并结果给 LLM
Graph RAG 最大的优势是全局性查询——比如"公司的整体战略是什么",传统的 RAG 只能抓到几个碎片化的段落,Graph RAG 可以通过实体关联找到完整的上下文。
代价是什么?构建知识图谱的成本非常高。微软发布的 Graph RAG 论文中提到,索引一份 100 万字的文档需要调用数千次 LLM API、耗时几十分钟。不是所有场景都值得。
自适应检索的多步推理
最复杂的 Agentic RAG 模式——Agent 可以在多个步骤之间决定如何检索:
步骤 1:分析问题
"2024 年 Q1 销量最好的产品是什么?"
→ 决定先检索产品列表
步骤 2:检索 + 分析
找到产品列表 → 发现需要按销量排序
→ 决定检索销售数据
步骤 3:再次检索 + 推理
得到销售数据 → 计算排名 → 得出答案
→ 决定是否需要额外信息
→ 不需要 → 输出最终答案
这与 ReAct 模式本质相同——Agent 可以通过多轮工具调用(检索工具)逐步逼近答案。区别在于:每一步的"检索"本身也是经过优化的(带 Query Rewriting、Reranker 等)。
如何选择 RAG 方案
你面临的 RAG 问题 推荐方案
────────────────────────────────────────────────────────
知识库文档少(< 100 份),问题简单 Naive RAG
知识库大但问得浅(FAQ 类) Naive + Hybrid Search
用户提问表达不准确 需要 Query Rewriting
需要精确匹配关键词 需要 Hybrid Search (BM25 + 向量)
用户反复问相似问题但用词不同 需要 HyDE
检索结果不稳定,时好时坏 需要 Reranker
需要综合多份文档回答 需要 Graph RAG
单次检索永远不够 需要 Self-RAG / 多步推理
公司内部文档,需要精确溯源 需要 Reranker + Hybrid Search
一个生产级 RAG 系统的完整架构
用户 Query
│
▼
┌──────────────────────┐
│ 步骤 1:路由 │
│ 是检索问题吗? │──→ 否 → 直接 LLM 回答
│ 需要什么类型的检索? │
└──────────┬───────────┘
▼ 是
┌──────────────────────┐
│ 步骤 2:Query 处理 │
│ Query Rewriting │ ← LLM 改写
│ Query Expansion │ ← 生成多个相关查询
│ HyDE 生成 │ ← 可选
└──────────┬───────────┘
▼
┌──────────────────────┐
│ 步骤 3:多路检索 │
│ ├─ 向量检索 │ ← Embedding
│ ├─ BM25 关键词检索 │ ← 精确匹配
│ └─ SQL/元数据过滤 │ ← 结构化约束
└──────────┬───────────┘
▼
┌──────────────────────┐
│ 步骤 4:融合与重排 │
│ RRF 融合 │ ← 多路结果合并
│ Reranker 精排 │ ← 交叉编码器
│ Context Management │ ← 去重 + 截断
└──────────┬───────────┘
▼
┌──────────────────────┐
│ 步骤 5:LLM 生成 │
│ 严格按照检索结果生成 │
│ 引用来源 │
│ 不知道的说不知道 │
└──────────────────────┘
这个架构看着复杂,但 90% 的场景不需要全部组件。建议的切入路径:
第一步:Naive RAG 跑通(1-2 天)
能回答基础问题,知道系统能不能跑
第二步:加 Hybrid Search(+1 天)
解决关键词匹配问题,提升 10-15% 准确率
第三步:加 Reranker(+2 天)
精排 Top-100 结果,提升 15-20% 准确率
第四步:按需加 Query Rewriting / HyDE / 多步推理
根据 bad case 针对性优化
不要一开始就追求"完整的 RAG 架构"——大多数场景不需要。从最简单的开始,根据真实 bad case 逐步迭代。
RAG 发展到今天,技术已经相对成熟。真正决定系统上限的往往不是用了多少花哨的技术,而是文档质量和用户问题的覆盖度。这和搜索引擎的道理一样——垃圾进,垃圾出。
评论