大家好,我是程序员鱼皮。
最近这两年,只要你接触过 AI 编程,大概率听过一个词,RAG(Retrieval-Augmented Generation)。
但很多小伙伴对 RAG 都只是一知半解,导致面试的时候只能说出 "检索增强生成" 这六个字,面试官再多问一点,就只能 "阿巴阿巴"。
而这篇文章,是对 RAG 技术的一个全景科普,从最初的 Naive RAG、到现在最主流的 Agentic RAG,总共 16 种主流 RAG 方案,我会一次性给大家讲清楚。以后开发 AI 应用的过程中,无论遇到什么场景,都能选择最合适的 RAG 方案。
干货比较多,建议先点赞收藏,再慢慢往下看~
什么是 RAG?
AI 大模型有一些硬伤,比如:
- 知识有截止日期
- 会一本正经地胡说八道,也就是我们常说的幻觉
- 缺乏私有知识,了解不到内部的文档写了什么
比如问 DeepSeek:程序员鱼皮的最新项目是什么?
结果它给我扯了个两年半以前的项目出来,技术栈也完全不对!
解决这个问题就可以用 RAG。RAG 的核心思想是 先搜再答,让大模型在回答之前先去搜一遍相关资料,再基于搜到的知识来组织答案。
就跟考试的时候偷偷翻书一样,遇到不会的先翻一翻书,再根据书里的知识答题。
还是问 AI 同样的问题,我们主动给 AI 一些参考资料,他的回答就会准确一些:
这个思路听起来简单,但在实际工程上 RAG 已经演化出了很多种不同的实现方法,从最初的「切块 → 搜索 → 生成」,到让 AI Agent 自主决策检索策略的 Agentic RAG,复杂度和能力天差地别。
有朋友可能会问:现在的大模型不是已经支持百万 token 的上下文窗口了,还需要 RAG 吗?
答案是:需要,而且用得比以前更多了!
因为把所有文档塞进上下文窗口,既贵又不靠谱。上下文越长 token 费用越高,而且大模型普遍存在 "Lost in the Middle" 问题,顾名思义,就是对超长上下文中间部分的注意力会明显下降。
这个也不难理解,就像听别人说话一样,我们对开头和结尾的印象会相对深刻一些,中间的总是容易忘记。
不过呢,RAG 和长上下文也不是互斥的关系,现在一般的最佳实践是先用 RAG 给 AI 提供相对精确的资料,再利用长上下文窗口进行有针对性的分析推理,两者互补。
好,背景交代完了。下面我们正式进入主题,挨个讲讲每种 RAG 方案。
我们先从最基础的 RAG 讲起,如果你之前完全没接触过 RAG,看完这一节就能理解它的核心原理。
主流 RAG 方案
标准 RAG 及变体
Naive RAG
Naive RAG 这个词听起来就很牛对不对?
但其实,Naive 是朴素的意思,Naive RAG 是最基本的 RAG 实现方案。
假设你有一份 200 页的公司员工手册,怎么让 AI 基于里面的内容回答员工的提问呢?
最简单粗暴的做法就是每次提问都把整本手册塞给 AI 大模型。
但是一本手册可能几十万字,全塞进去又贵又慢,而且上下文一长,大模型还容易犯前面提到的 "Lost in the Middle" 的毛病,导致回答质量下降。
所以更合理的思路是:员工问什么,我们就只把相关的几段内容塞给 AI。
那问题又来了,怎么定位到相关的那几段呢?
如果用关键词匹配,很容易出现问题和文档里的关键词不一致的问题,比如员工问 "老板不批假怎么办",文档里写的是 "请假审批流程",关键词对不上,就搜不到。
这就需要用到 向量 了。
所谓向量,就是把一段文字用一串数字表示出来,让计算机可以比较语义上的相似度。
举几个例子感受一下:
| 文本 | 向量(简化示意) |
|---|---|
| 我喜欢吃鱼 | [0.21, 0.85, 0.13, ...] |
| 我爱吃海鲜 | [0.23, 0.82, 0.15, ...] |
| 今天天气真好 | [0.88, 0.12, 0.41, ...] |
语义越接近的句子,它们的向量在数学空间里离得越近。
负责把文字转成向量的模型,叫 Embedding 模型;存储这些向量并支持快速相似度搜索的数据库,叫向量数据库,比如 Milvus、Chroma、Qdrant 等。
理解了向量,Naive RAG 的做法就很好理解了,主要分为两步。
第一步是离线索引:
- 把文档切成小块(chunk),每块几百字
- 用 Embedding 模型把每个小块转成向量
- 把向量和对应原文都存进向量数据库
第二步是在线查询问答:
- 把用户问题也用 Embedding 模型转成向量
- 去向量库里搜最相似的几个文档块(比如 Top 5)
- 把这几个块和用户问题拼成 Prompt,交给大模型生成回答
回到开头那份 200 页的员工手册。我们先把它切成几百个小块并向量化入库,当员工问 "年假有多少天?" 的时候,RAG 的执行流程是这样的:
- 系统把问题转成向量
- 在向量库里找到最相似的 5 个文档块(比如某块写着 "入职满一年享有 10 天年假,满三年 15 天...")
- 把这 5 个块连同问题一起交给大模型
- 大模型回答:"入职满一年享有 10 天年假"
不过 Naive RAG 也有一些比较明显的问题:
- 切块方式粗暴,可能把一段完整的语义从中间截断
- 检索质量完全依赖 embedding 模型,搜不到就没辙
- 搜到了垃圾文档也不管,导致输出错误答案
这些局限,就是后面所有进阶方法要解决的问题。
下面我用伪代码来帮助大家理解,不熟悉编程的同学可以跳过,不影响后续的学习~
1)离线索引阶段:
python
# 1. 把文档切成小块,chunk_size=500 表示每块 500 字,
# chunk_overlap=50 表示相邻块之间重叠 50 字,避免关键信息被切断
chunks = split_into_chunks(documents, chunk_size=500, chunk_overlap=50)
# 2. 把每个小块转成向量,连同原文一起存入向量数据库
for each chunk in chunks:
vector = embedding_model.encode(chunk) # 调用 Embedding 模型编码
vector_store.insert(vector, chunk) # 向量和原文一起入库
2)在线查询阶段:
python
# 1. 用同一个 Embedding 模型把用户问题也转成向量
query_vector = embedding_model.encode(user_query)
# 2. 在向量库里搜最相似的 Top 5 文档块
top_k_chunks = vector_store.search(query_vector, k=5)
# 3. 把检索到的文档块拼进 Prompt,作为参考资料
prompt = "基于以下参考资料回答问题:\n" + join(top_k_chunks) + "\n问题:" + user_query
# 4. 交给大模型生成最终回答
answer = LLM.generate(prompt)
Multi-Query RAG
用户提问的方式千奇百怪。比如同样是想知道公司报销流程,有人会问怎么报销,有人会问费用审批流程是什么,还有人会问花了钱怎么找公司要回来。
但文档里可能只写了 "报销申请流程" 这个表述,如果用户的措辞和文档差距太大,向量检索就可能搜不到正确的内容。
Multi-Query 的思路就是:既然一种问法搜不全,那就让大模型把原始问题改成多种不同的表述,分别去搜,最后把结果合并去重。
这种方法的代价就是每次提问要多调用一次 LLM 做改写,再多跑 N 次向量检索,延迟和成本都会增加。
而且如果 LLM 改写出的问题方向跑偏,会把无关文档也带进来,影响答案质量。
所以它比较适合 面向普通用户的客服、电商等场景,用户表述口语化、和文档术语差距大,多花这点成本还是有必要的。但是在术语规范的专业领域,Multi-Query 的收益就很有限了。
Multi-Query RAG 的代码实现如下:
python
# 1. 让 LLM 把原问题改写成多个表述
queries = LLM.generate("请将以下问题改写成 3 个不同的表述:" + user_query)
// 例如 AI 返回: ["报销流程是什么", "费用审批怎么操作", "如何提交报销申请"]
# 2. 每个表述分别走一次向量检索
all_results = []
for each query in queries:
results = vector_store.search(embed(query), k=5)
all_results.append(results)
# 3. 合并去重,得到覆盖面更广的候选文档
merged_chunks = deduplicate(all_results)
# 4. 把合并后的文档 + 原始问题交给大模型生成回答
answer = LLM.generate(merged_chunks + user_query)
HyDE
AI 回复的效果不好,可能不是因为用户的问法不好,而是用户的问题和文档的语义空间不一致。
用户的提问往往很短,比如 "KV Cache 是什么?",就这么几个字。
但文档里关于 KV Cache 的描述可能是一大段技术解释。一短一长,在 embedding 空间中可能离得很远,就检索不到了。
HyDE 的做法就是让大模型凭空写一个答案(不必完全准确),然后用这段假答案的向量去检索。因为假答案和真文档的文体更接近,两者在向量空间中离得也更近。
还是上面的例子,用户问:"KV Cache 是什么?"
LLM 先编一段假答案:"KV Cache 是一种在 Transformer 推理过程中缓存 Key 和 Value 矩阵的优化技术,可以避免重复计算..."
这段假答案虽然不一定完全准确,但它的向量和真实文档里关于 KV Cache 的那段描述非常接近,所以能精准命中正确的文档。
不过 HyDE 也有一个风险,如果 LLM 编的假答案方向完全跑偏了(比如把 KV Cache 理解成了 Redis 缓存),那检索结果就会更差。所以它比较适合 LLM 对问题领域有基本认知的场景,冷门领域或企业私有术语慎用。
HyDE 的代码示例如下:
python
# 1. 让 LLM 先凭空生成一段"假答案"(不必完全准确)
hypothetical_answer = LLM.generate("请回答:" + user_query)
# 2. 用假答案的向量去检索,因为它的文体更接近真实文档
hyp_vector = embedding_model.encode(hypothetical_answer)
top_k_chunks = vector_store.search(hyp_vector, k=5)
# 3. 把检索到的真实文档 + 原始问题交给大模型生成最终回答
answer = LLM.generate(top_k_chunks + user_query)
上面这三种方法,解决的是「能不能搜到」的问题。但搜到之后,资料的质量好不好呢?这就是下面要处理的了。
提升检索质量
语义分块(Semantic Chunking)
Naive RAG 里最粗暴的一步就是切块,每 500 个字切一刀,管你是不是正好切在一句话中间。
比如某块文本是 "员工请假需要提前 3 天申请,超过 5 天需要"
刚好在这里被截断了,下一个块从 "部门经理审批" 开始。
这两个块单独看都不完整,检索和 AI 的理解都会打折扣。
虽然可以通过 chunk_overlap 设置相邻块之间重复内容的长度,但是效果相对有限。
语义分块的做法是先把文档按句子拆开,计算相邻句子的 embedding 相似度,当相似度突然下降时,说明话题变了,就在这里切一刀,尽量保证每个句子的语义都是完整的:
其余的步骤就跟 Naive RAG 一致了。
不过这种方式代价也不小,首先要为每一句话都算一次 embedding,成本和耗时比按字数切分要高。
而且相似度阈值非常难调,所以它比较适合结构松散、话题变化比较快的文档,像会议纪要、访谈记录这些。
对于本身就有清晰章节结构的技术手册、产品说明文档,直接按标题切效果也不差,还更便宜。
语义分块的伪代码示例如下:
python
# 1. 把文档拆成单句
sentences = split_into_sentences(document)
chunks = []
current_chunk = [sentences[0]]
# 2. 遍历相邻句子,计算 embedding 相似度
for i from 1 to len(sentences) - 1:
similarity = cosine_similarity(embed(sentences[i-1]), embed(sentences[i]))
# 相似度骤降,话题切换
if similarity < threshold:
chunks.append(join(current_chunk))
current_chunk = []
current_chunk.append(sentences[i])
层级索引(Parent-Child Retrieval)
切块这件事有一个天然矛盾。切得小吧,检索精度高但上下文不足;切得大吧,上下文丰富但噪声也大。
Parent-Child Retrieval 的策略则是 两层都要。
先把文档切成大块,再把每个大块细分成小块。检索时用小块匹配,命中后返回它所属的大块。
相当于在书里搜到了某一句话时,读的时候把它所在的整个章节都拿过来看:
学过数据库的朋友们应该对这种思路不陌生吧?
这种方案就很适合长文档场景,比如技术手册、法律合同、产品文档这些内容,往往需要连带上下文一起看才能理解。
层级索引的伪代码实现如下:
python
# 1. 离线阶段:文档先切大块,再在大块内切小块
for each document:
parent_chunks = split_into_sections(document)
for each parent in parent_chunks:
child_chunks = split_into_paragraphs(parent)
# 只对小块建向量索引,但保留它属于哪个大块
for each child in child_chunks:
index.insert(embed(child), child, parent_id=parent.id)
# 2. 在线阶段:用小块做精确匹配
matched_children = index.search(embed(query), k=5)
# 3. 但返回的是小块所属的大块,保证上下文完整
parent_ids = unique([child.parent_id for child in matched_children])
context = [get_parent_chunk(pid) for pid in parent_ids]
# 4. 把大块作为上下文交给大模型
answer = LLM.generate(context + query)
Hybrid Search 混合检索
纯向量检索有一个缺点,就是无法精确术语匹配。
比如用户问:"ERROR_CODE_4012 是什么意思?"
向量检索会去找语义相似的内容。
但这种编码本身没什么语义,向量搜索可能找到一堆讲错误处理的段落,就是找不到那个精确提到 4012 的段落。
而传统的关键词搜索擅长精确匹配,但不理解语义。
比如搜 "如何退款",就搜不到写着 "退货及返还货款流程" 的文档,类似数据库里的 like 操作。
Hybrid Search 就是同时使用两种搜索,然后合并排序。向量搜索负责语义理解,BM25 负责精确匹配,通过 RRF(Reciprocal Rank Fusion 倒数排序融合)算法,把两边的结果合并成一个排序。
几乎所有生产环境都建议用 Hybrid Search 替代纯向量搜索,尤其是像技术文档、医疗、法律等术语密集的领域。
Hybrid Search 混合检索的实现代码如下:
python
# 1. 同时跑两路检索,各自召回 Top 20
semantic_results = vector_store.search(embed(query), k=20)
keyword_results = bm25_index.search(query, k=20)
# 2. 用 RRF 算法融合两路结果,公式:1 / (60 + 排名),分数越高越相关
for each doc in (semantic_results ∪ keyword_results):
score = 0
if doc in semantic_results: score += 1 / (60 + rank_in_semantic)
if doc in keyword_results: score += 1 / (60 + rank_in_keyword)
# 3. 按融合后的分数排序,取 Top 5 交给大模型
final_results = sort_by_score(all_docs, top_k=5)
answer = LLM.generate(final_results + query)
Reranking 精排
不管是向量搜索还是 Hybrid Search,检索回来的候选文档里总会混着一些看起来相关但实际没用的噪声。
Reranking 的做法是在检索和生成之间加一个精排步骤:用 Reranker 模型给每对 (query, doc) 重新打分。
前面提到的 embedding 模型是分别给 query 和 doc 算向量再比较距离,快但粗糙,Reranker 是把它们拼在一起送进模型打分,慢但精准。
在实际生产中,如果语料库有十万级以上的 chunk,一般会采用级联检索方案,分层筛选:
为什么分这么多层呢?
如果使用粗检索,只捞 20 个内容,在大型语料库里很容易漏掉关键文档;但如果一次性捞 150 个,全送进 Cross-Encoder 交叉编码器来精确计算两个文本片段的相关性分数,算力又扛不住。
分层筛选则是在召回率和计算成本之间找平衡。当语料库里的 chunk 比较多时,Reranking 的效果提升会非常显著。
可以这么理解,粗检索负责不遗漏,精排负责不掺假,跟推荐系统里的粗排和精排很像。
分层筛选的实现代码如下:
python
# 第一层:粗检索,保证召回率
candidates = hybrid_search(query, k=150)
# 第二层:轻量 Reranker 初筛
semi_final = lightweight_reranker.rank(query, candidates, top_k=20)
# 第三层:Cross-Encoder 精排,只处理 20 个
scored = []
for each doc in semi_final:
score = cross_encoder.score(query, doc)
scored.append((doc, score))
top_docs = top_k(scored, k=5)
answer = LLM.generate(top_docs + query)
到这里,我们已经能搭出一个相当不错的 RAG 系统了,语义分块 + Hybrid Search + Reranking,这三板斧组合起来,就是大多数生产级 RAG 系统的基础配置。
前面的方法都在优化怎么搜得更准,但有一个更根本的问题没解决:如果搜到的全是垃圾,大模型还是会一本正经地基于这些垃圾内容生成答案。
这就像开卷考试带错了书,还照着抄了上去......
所以接下来我们要聊一聊,怎么让 RAG 学会自我纠错。
RAG 反思机制
Corrective RAG(CRAG)
不管我们怎么优化检索,总会有搜不到或者搜歪了的情况,这时候大模型如果还硬着头皮拿这些内容去回答,那就避免不了一本正经地胡说八道。
而 CRAG 就是 把搜到的资料过滤一遍 来解决这个问题的。
具体做法就是在检索和生成之间插一个质检员,逐个审查检索到的文档是否和问题相关,然后根据审查结果走不同的分支。
- 打分高的,说明资料靠谱,直接喂给大模型生成答案
- 打分低的,说明内部知识库里压根没找着相关内容,干脆回退到 Web 搜索兜底
- 打分模糊的,就两边的结果合一起送进去
Corrective RAG 的示例实现代码如下:
python
# 1. 先跑一遍常规检索
docs = retriever.search(query, k=5)
# 2. 逐个让 LLM 判断文档是否和问题相关
relevant_docs = []
for each doc in docs:
score = LLM.judge("这段内容和问题相关吗?", query, doc)
if score > threshold:
relevant_docs.append(doc)
# 3. 根据审查结果走不同分支
if len(relevant_docs) > 0:
answer = LLM.generate(relevant_docs + query)
else:
new_query = LLM.rewrite(query)
web_results = web_search(new_query)
answer = LLM.generate(web_results + query)
最关键的是那个 else 分支,如果内部知识库完全搜不到有用信息,CRAG 会自动回退到 Web 搜索(当然也可以是其他策略)。
Self-RAG
CRAG 在检索阶段做了质检,但是生成阶段呢?
大模型完全有可能拿到了正确的参考资料,但回答的时候夹带私货,在答案里掺入了参考资料中根本没提到的内容。这就是生成阶段的幻觉。
Self-RAG 的思路是在整个流程中设置四个检查点,每一步都让模型自我审视:
- 这个问题需要检索吗(Retrieve)?
- 检索到的文档相关吗(IsRel)?
- 我的回答有文档支撑吗(IsSup)?
- 这个答案对用户有用吗(IsUse)?
Self-RAG 的示例实现代码如下:
python
# 1. 判断这个问题需不需要检索
need_retrieval = LLM.judge("这个问题需要外部知识吗?", query)
if not need_retrieval:
return LLM.generate(query)
# 2. 检索 + 逐个过滤相关文档
docs = retriever.search(query, k=5)
relevant_docs = filter(docs, where LLM.judge("和问题相关吗?") == true)
# 3. 生成初步答案
answer = LLM.generate(relevant_docs + query)
# 4. 检查答案的每个论断是否都有文档依据
is_supported = LLM.judge("答案中的每个论断都能在文档中找到依据吗?", answer, relevant_docs)
if not is_supported:
answer = LLM.regenerate(relevant_docs + query + "请严格基于参考资料回答")
一般来说第三个检查点的价值相对较高,能在一定程度上避免 AI 的幻觉。
Adaptive RAG
CRAG 和 Self-RAG 都在给 RAG 加流程来解决问题,但这样多了好几次 LLM 调用,增加了成本。
如果用户问的是 "你好" 或者 "今天星期几",还要跑一遍完整的检索 + 质检 + 生成流程,有点开着坦克去买菜的意思,纯属浪费。
Adaptive RAG 在最前面加了一个路由器(分类器),先判断问题的复杂度,然后决定走哪条路线:
Adaptive RAG 的实现代码很简单:
python
complexity = classifier.predict(query)
# 简单问题直接答
if complexity == "simple":
answer = LLM.generate(query)
# 一般问题检索一次
else if complexity == "moderate":
docs = retriever.search(query)
answer = LLM.generate(docs + query)
# 复杂问题采用 crag
else:
answer = run_full_crag_pipeline(query)
这个分类器可以是一个微调的小模型,也可以用 LLM 的 few-shot 少样本提示来实现,关键是让简单问题和复杂问题分别处理。
这种方案适合流量混杂的场景,比如既有 "公司地址在哪" 这种一句话能答的问题,又有 "对比 A 和 B 两个方案的优缺点" 这种需要多文档综合分析的复杂问题。
上面这些方法处理的都是非结构化文本,但如果我们的数据是关系网络或者表格呢?那就需要下面的方法了。
结构化知识增强
GraphRAG
前面这些方法都有一个共同的问题,就是答案必须落在某一个文档块里。
但现实里经常有一类问题:答案散落在多个文档中,需要串起来推理。
举个例子,假设文档库里有两段话。
- 文档 A 写着 "张三是 AI 部门的负责人"
- 文档 B 写着 "AI 部门属于技术中心"
用户问:"张三属于哪个中心?"
传统向量检索大概率只能搜到文档 A,但要回答这个问题,必须把 A 和 B 连起来推理:张三 → AI 部门 → 技术中心。
这种跨文档的多跳推理,纯向量搜索就很难搞定了。
GraphRAG 就是用来解决这种问题的,这是 Microsoft Research 在 2024 年提出的方法。
它的思路是先把文档变成知识图谱,再基于图谱来检索和推理。
具体做法分为 3 步:
- 用 LLM 逐篇读文档,抽取里面的实体(人、部门、产品等)和关系,构建成一张图谱
- 用 Leiden 算法对图谱做社区划分,把关联紧密的实体聚成一团,然后让 LLM 为每个社区生成一段摘要
- 提问时先定位到相关实体,沿着关系拿到子图
根据微软的评测,在全局语义理解类问题上,GraphRAG 答案的全面性和多样性显著优于传统向量 RAG。
但对于简单的事实查询,两者效果差不多,就没必要用了。
需要注意的是,GraphRAG 要用 LLM 逐篇抽实体关系,图谱构建成本比向量索引高得多,查询延迟也更大,所以使用 GraphRAG 之前一定要评估是否必要!
GraphRAG 的伪代码实现如下:
python
# 1. 离线阶段:用 LLM 从每篇文档里抽取实体和关系,构建知识图谱
for each document:
entities, relations = LLM.extract("请抽取文中的实体和关系", document)
knowledge_graph.add(entities, relations)
# 2. 用 Leiden 算法做社区划分,并为每个社区生成摘要(用于回答全局性问题)
communities = leiden_algorithm(knowledge_graph)
for each community:
summary = LLM.summarize(community.entities, community.relations)
# 3. 在线阶段:先定位相关实体,沿关系遍历 2 跳拿子图
relevant_entities = knowledge_graph.search("张三")
subgraph = knowledge_graph.traverse(relevant_entities, hops=2)
# 4. 把子图和社区摘要作为上下文交给大模型生成回答
answer = LLM.generate(subgraph + community_summaries + query)
Text-to-SQL RAG
如果我们的数据本身就是结构化的表格,比如销售数据、用户行为日志、财务报表这些,就不合适传统的 RAG 了,因为对表格数据做 embedding 是非常低效的。
用户问:"上个月销售额最高的产品是哪个?"
这本质上就是一条 SQL,向量搜索对这种聚合、排序、筛选类的需求完全没招。
Text-to-SQL RAG 的做法是让 LLM 直接把自然语言翻译成 SQL,执行查询,再把查询结果作为上下文来回答:
这个方案适合所有数据分析类需求,比如 BI 看板问答、数据库运维助手、财务报表查询等等,本质上是用 LLM 替代了手写 SQL。
不过要特别提醒,生产环境中绝对不能让 LLM 生成的 SQL 直接执行,必须配备只读权限控制、SQL 语法审计、沙盒隔离等安全措施,防止 SQL 注入。
Text-to-SQL RAG 的伪代码实现很简单:
python
# 1. 准备表结构(schema)作为提示,让 LLM 知道有哪些表、字段
schema = "表 sales: product(产品名), amount(金额), month(月份), region(地区)"
# 2. 让 LLM 把自然语言问题翻译成 SQL
sql = LLM.generate("根据以下表结构,将问题转为 SQL:\n" + schema + "\n问题:" + query)
# 3. 在数据库中执行 SQL,拿到结构化结果
result = database.execute(sql)
# 4. 把查询结果喂给 LLM,让它用自然语言组织成最终回答
answer = LLM.generate("查询结果:" + result + "\n请用自然语言回答:" + query)
到这里,我们已经讲了很多种 RAG 方法了。有的小伙伴可能已经注意到一个问题:前面提到的大多数方法都是预定义好的 pipeline,流程是死的,不管什么问题进来,处理方式都差不多。
但现实世界的问题千变万化。有的需要搜向量库,有的该查数据库,有的应该搜 Web,甚至有的根本不需要检索。
好复杂啊...... 有没有一种方法能让系统自己判断该怎么做?
答案就是 Agentic RAG。
智能体驱动 RAG
Agentic RAG
前面每种方法都有自己擅长的场景:Hybrid Search 擅长术语密集的文档,GraphRAG 擅长多跳推理,Text-to-SQL 擅长结构化数据。
但在一个真实的系统中,这些场景可能同时存在。
比如用户问 "张三上个月的考勤记录" 得查数据库,问 "公司的远程办公政策" 得搜文档,问 "张三属于哪个部门的哪个中心" 得走知识图谱,但是为每种问题硬编码一条 pipeline 太麻烦了。
Agentic RAG 的做法是让一个 AI Agent 来自动调度,根据问题自主决定每一步该怎么做。给这个 Agent 配备一组检索工具,它会先搜搜看 → 看看结果够不够 → 不够就换个方式 / 换个关键词再搜 → 结果够了就生成回答。
Agentic RAG 虽然是很灵活的方案,但是代码实现很简单,核心是 Agent Loop 循环:
python
# 1. 给 Agent 配一组工具(向量检索、Web 搜索、SQL 查询、图谱遍历......)
tools = {
"vector_search": query -> vector_store.search(embed(query)),
"web_search": query -> search_engine.search(query),
"sql_query": query -> database.execute(LLM.to_sql(query)),
"graph_traverse": query -> knowledge_graph.traverse(query),
}
# 2. 进入 ReAct 循环:思考 → 行动 → 观察,直到信息足够为止
context = []
while true:
# 让 LLM 基于已知信息,决定下一步用哪个工具,或者直接回答
thought = LLM.reason("问题:" + query + "\n已知信息:" + context +
"\n我应该使用哪个工具?还是信息已经足够可以回答了?")
# 决定作答,跳出循环
if thought.action == "answer":
return LLM.generate(context + query)
# 否则调用对应工具,把结果追加到上下文,进入下一轮
result = tools[thought.tool](thought.tool_input)
context.append(result)
以前 Agent 的概念刚火起来的时候,大家还在争论 "Agent 自主决策靠不靠谱"。
到了今天,Agentic RAG 已经是比较主流的生产范式了,知名的 AI 编程工具 Cursor 用的就是这种方式,AI 自主决定使用什么方式来搜集信息:
Multi-Agent RAG
单个 Agent 处理复杂任务时,可能有个问题:当它要同时兼顾理解意图、选择策略、验证质量、生成答案时,Prompt 变得又长又复杂,决策质量就会下降。这就像一个人又当程序员、又教人打篮球、又当说唱歌手,忙不过来。
Multi-Agent RAG 的做法是拆分为多个专职 Agent,各自负责一些任务。
比如 Router Agent 负责分发、各 RAG Agent 负责对应领域的检索和推理、Verification Agent 负责质检、Generation Agent 负责润色输出。
这种方案适合数据源多、权限复杂、语言多样的企业级知识库。每个环节可以独立优化和扩展,不会牵一发而动全身。
Multi-Agent RAG 的代码示例如下:
python
# 1. Router Agent 先识别问题意图,决定分给哪个专职 Agent
intent = router_agent.analyze(query)
# 2. 各领域 Agent 各管一摊,独立完成检索和推理
if intent == "document_qa":
raw_answer = doc_agent.run(query)
else if intent == "data_analysis":
raw_answer = sql_agent.run(query)
else:
raw_answer = graph_agent.run(query)
# 3. Verifier Agent 做质检,发现问题则提出修改建议
verified = verifier_agent.check(query, raw_answer)
if not verified.passed:
raw_answer = verifier_agent.suggest_fix(raw_answer)
# 4. Writer Agent 做最终润色,统一输出风格
final_answer = writer_agent.polish(query, raw_answer)
RAG 能力扩展
多模态 RAG(Multimodal RAG)
传统 RAG 只处理文本,但现实中的企业文档里面充斥着大量的图表、流程图、架构图、产品照片等。
如果用纯文本 RAG 去处理一份真实文档,那些流程图、架构图里的信息就全丢了。
多模态 RAG 的做法是把图片、表格和文本统一到一个向量空间里,这样检索时就能跨模态匹配:
最后一步生成阶段,需要用视觉语言模型来处理混合模态的上下文,因为普通的纯文本 LLM 看不懂图片。
多模态 RAG 的代码实现比较复杂:
python
// 离线:文本和图片统一编码到同一个向量空间
for each page in document:
text_chunks = extract_text(page)
images = extract_images(page)
tables = extract_tables(page)
for each chunk in text_chunks:
index.insert(text_encoder.encode(chunk), chunk, type="text")
for each image in images:
index.insert(vision_encoder.encode(image), image, type="image")
for each table in tables:
index.insert(table_encoder.encode(table), table, type="table")
// 在线:统一检索,跨模态匹配
results = index.search(text_encoder.encode(query), k=5)
// results 中可能混合了文本块、图片、表格
answer = vision_LLM.generate(results + query) // 用视觉语言模型处理混合内容
Speculative RAG
普通的 RAG 还有一个问题:如果把检索到的所有文档都塞进同一个 Prompt,不仅增加了推理延迟、响应速度,而且如果某个文档是噪声,整个生成都会被带偏。
Speculative RAG(假设性检索增强生成)借鉴了推测性解码(Speculative Decoding)的思想,核心目标是降低延迟,把检索到的文档分成多个子集,用多个专家小模型从每个子集 并行 生成候选草稿,最后由一个更强的大模型做一次验证,选出最佳答案。
这就有点像团队共同做个大项目,多位前端和后端开发一起干活和仔细验证,最后产品经理只需要简单验证就好,能大幅缩短工作总时长,有问题也更容易发现。
Speculative RAG 的实现代码如下:
python
# 1. 检索阶段召回较多的候选文档
docs = retriever.search(query, k=15)
# 2. 把候选文档拆成 n 个子集,准备并行处理
subsets = split_into_subsets(docs, n=5)
# 3. 多个小模型并行生成候选草稿,每个草稿只看一部分文档
drafts = parallel_run(
for each subset in subsets:
draft = small_LM.generate(subset + query)
return { draft, subset, confidence }
)
# 4. 大模型做一次验证,从多个草稿中选出最佳答案
best = large_LM.verify_and_select(drafts, query)
return best.answer
方法选择
看到这里,你可能已经被这十几种 RAG 方案搞晕了。
我到底该用哪一种啊啊啊啊?!
没事,我给大家梳理了一张表格,直接根据自己的项目情况来选方案:
| 你的情况 | 推荐方案 |
|---|---|
| 标准文本知识库,追求基本可用 | Naive RAG |
| 用户提问风格多变、口语化 | Multi-Query RAG 或 HyDE |
| 生产环境,追求检索质量 | Hybrid Search + Reranking |
| 对准确率要求高,不能容忍幻觉 | Corrective RAG 或 Self-RAG |
| 查询复杂度差异大 | Adaptive RAG 路由 |
| 需要跨文档多跳推理 | GraphRAG |
| 数据以结构化表格为主 | Text-to-SQL RAG |
| 文档包含大量图表/图片 | 多模态 RAG |
| 多数据源、多类型混合 | Agentic RAG / Multi-Agent RAG |
| 延迟敏感 | Speculative RAG |
对于正在动手搭建 RAG 系统的初学者来说,我建议 从简单开始,逐步完善。
先跑通 Naive RAG,发现哪个环节出了问题,就针对性地选用前面的 RAG 方案。
千万别一上来就搞 Multi-Agent + GraphRAG + 多模态全家桶,不仅实现成本高,效果也不一定更好。
那怎么知道 RAG 系统效果好不好呢?
其实有个评估框架 RAGAS,就是用来做这个的。它有四个核心指标:
- 忠实度(回答有没有瞎编)
- 答案相关性(答的是不是你问的)
- 上下文精确率(搜到的有多少是有用的)
- 上下文召回率(该搜到的搜到了吗)
这样你就能先评估效果再优化,而不是仅凭感觉调参数。
除此之外,RAG 相关的主流技术还有编排框架 LangChain / LangGraph、LlamaIndex、一体化平台 Dify 、RAGFlow、向量数据库 Chroma、Milvus、Qdrant 等,大家感兴趣的话可以自行学习了解一下~
OK,这篇文章写了快 1 万字,把 16 种 RAG 的实现和优化方案都讲完了,希望对大家有所帮助。
我是鱼皮,持续分享 AI 编程干货,这篇文章也会收录到我免费开源的 《AI 编程零基础入门教程》,GitHub Star 数已经破万,从零开始带你学会用 AI 开发上线自己的产品。
开源仓库:https://github.com/liyupi/ai-guide
学会的话欢迎点赞收藏关注哦,也欢迎评论区聊聊:你用过 RAG 么?最喜欢那种 RAG 方案?