RAG 基础与五种范式演进:从链式检索到自主决策
RAG 基础与五种范式演进:从链式检索到自主决策
RAG 学习 Day 1-2 上半部分。
关键字:RAG 五组件 / Naive → Advanced → Modular → Graph → Agentic / GraphRAG / Leiden 社区检测 / Local vs Global / 范式选型决策树。
📌 写在前面
RAG 这两个字现在有点被用滥了。什么"我们做了 RAG 系统",什么"RAG 落地最佳实践",翻开一看全是"文档切块 → 向量化 → 检索 → 拼 prompt"。
这套四步流程没错,但它只是起点。
我花了两天把 RAG 的五种范式过了一遍(Naive / Advanced / Modular / Graph / Agentic),最大的感受是:
RAG 不是一种技术,是一条谱系。选错范式的代价,比调错参数大 10 倍。
这篇讲基础和范式演进。工程化那部分(分层评估 / 查询转换 / 分块策略 / Agentic RAG 实现)单独一篇。
📌 一个问题:什么时候你需要 RAG
你的四种处境
| # | 处境 | 你现在怎么做 | 痛在哪 |
|---|---|---|---|
| 1 | 模型不知道你公司的内部规范 | 每次手动复制粘贴到对话框 | Token 贵、复制粘贴烦、模型还是记不住 |
| 2 | 知识每周都在变 | 微调 / 重新训练 | 又慢又贵,改一个字要重训一次 |
| 3 | 答案需要溯源 | 让模型"别编" | 说不清,它还是编 |
| 4 | 数据量远超上下文窗口 | 硬塞 | 塞不进去,塞进去也不准 |
RAG 怎么解决
1. 内部规范 → 检索进来,模型不用记,检索就行
2. 知识在变 → 改向量库,不改模型权重
3. 要溯源 → 检索结果就是引用来源,天然可解释
4. 数据超窗口 → 只检索最相关的 top-k 块判断标准
知识是你的私有的、动态的、需要溯源的 → 上 RAG。
知识是通用常识、静态的、不需要溯源 → 直接问模型。
📌 为什么优先 RAG,不选微调
两条路的对比
| 维度 | RAG | 微调(Fine-tuning) |
|---|---|---|
| 知识更新 | 改向量库,分钟级 | 重新训练,小时到天级 |
| 成本 | 向量库 + 检索开销 | GPU + 训练数据标注 |
| 溯源 | ✅ 天然有(检索到的原文) | ❌ 知识融进权重,溯不了 |
| 可解释 | ✅ 能指出依据哪几段 | ❌ 黑盒 |
| 时效性 | ✅ 实时(查的是最新数据) | ❌ 训练完就固定 |
| 可逆性 | ✅ 删掉向量库就完事 | ❌ 需要重训才能改 |
| 防幻觉 | ✅ 有据可依 | ⚠️ 可能学到了错误模式 |
一句话结论
知识不进权重,进索引。 —— 这是 RAG 的根本主张。
什么情况该选微调
| 情况 | 为什么 RAG 不行 |
|---|---|
| 改变模型的输出风格(固定话术) | 风格是能力,不是知识 |
| 让模型学会新技能(写某种格式代码) | 技能需要反复训练内化 |
| 优化特定任务的准确率 | 权重调整比检索更直接 |
RAG 解决"知道什么",微调解决"会做什么"。两者不冲突,可以叠加。
📌 为什么不能把上下文塞满
这是最反直觉的一条:塞得越多,反而越不准。
4 个代价
| 代价 | 说明 | 实际影响 |
|---|---|---|
| 计算时间长 | 输入 token 越多,prefill 越慢 | 首字延迟从 500ms → 几秒 |
| Token 成本高 | 输入 token 按量计费 | 长文档问答成本翻几倍 |
| 准确性会降低 | 上下文越长,注意力越分散 | 中间信息容易被忽略 |
| 评估复杂度高 | 输出受太多输入影响,没法归因 | 效果变差你不知道是哪段干扰的 |
最关键的是第三条
这叫 "lost in the middle" —— 长上下文里,模型对开头和结尾的信息记得牢,中间部分容易忽略。
所以"把所有文档都塞进去"是反模式。正确做法是:检索出最相关的 top-k 块,只给这几块。
💎 第一章 · RAG 系统的 5 个组件
一、系统结构
用户问题 Query
↓
[索引 Indexing] ← 离线构建(文档 → 向量库)
↓
[检索 Retrieval] → 找出最相关的 k 个块
↓
[增强 Augment] → 把检索结果和原问题拼成新 prompt
↓
[生成 Generation] → 模型基于增强后的 prompt 回答| 组件 | 职责 | 离线/在线 | 主要技术 |
|---|---|---|---|
| Query | 用户输入 | 在线 | — |
| Indexing | 把文档变成可检索的向量 | 离线 | 解析 / 切块 / Embedding / 向量库 |
| Retrieval | 找出最相关的块 | 在线 | 向量检索 + 过滤 + 重排 |
| Augment | 拼 prompt(增强上下文) | 在线 | 拼接模板 + 上下文压缩 |
| Generation | 模型生成答案 | 在线 | LLM 调用 |
二、两个工程部分
索引侧(离线)
文档 → 文件解析 → 切块(Chunking)→ Embedding 向量化 → 插入向量数据库查询侧(在线)
Query → 向量库检索 → [可选:过滤/重排/压缩] → 拼 Prompt → 模型生成 → 答案为什么这么分? 因为 Embedding 是耗时的(一个 100 页 PDF 可能几分钟)。不能每次提问都重新 embed 一遍全库。
三、完整实现
索引侧(离线)
# ============ 索引侧:文件 → 向量库 ============
from pathlib import Path
from langchain.document_loaders import PyPDFLoader, UnstructuredWordDocumentLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Qdrant
from qdrant_client import QdrantClient
def build_index(source_dir: str, vector_db_path: str = "./qdrant_db"):
"""离线索引:文件解析 → 切块 → 向量 → 入库"""
# ---------- 1. 文件解析 ----------
documents = []
for file_path in Path(source_dir).rglob("*"):
if file_path.suffix == ".pdf":
loader = PyPDFLoader(str(file_path))
elif file_path.suffix == ".docx":
loader = UnstructuredWordDocumentLoader(str(file_path))
else:
continue
docs = loader.load()
for doc in docs:
doc.metadata["source"] = str(file_path) # ★ 溯源信息
documents.extend(docs)
# ---------- 2. 切块 ----------
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
# ★ 按语义边界优先级排序
separators=["\n\n", "\n", "。", "!", "?", " ", ""],
)
chunks = splitter.split_documents(documents)
# ---------- 3. 向量化 ----------
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# ---------- 4. 插入向量数据库 ----------
client = QdrantClient(path=vector_db_path)
vector_store = Qdrant.from_documents(
documents=chunks,
embedding=embeddings,
collection_name="knowledge_base",
client=client,
)
return vector_store查询侧(在线)
# ============ 查询侧:Query → 答案 ============
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
def query_knowledge_base(user_query: str, vector_store):
"""在线查询:检索 → 增强 → 生成"""
llm = ChatOpenAI(model="qwen-max", temperature=0)
retriever = vector_store.as_retriever(
search_type="similarity",
search_kwargs={"k": 4}, # 取最相关的 4 块
)
# RetrievalQA 自动把检索结果拼进 prompt
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=retriever,
return_source_documents=True, # ★ 返回溯源信息
)
result = qa_chain({"query": user_query})
return {
"answer": result["result"],
"sources": [doc.metadata["source"] for doc in result["source_documents"]],
}四、4 行决定成败的代码
| 行 | 参数 | 调错了会怎样 |
|---|---|---|
chunk_size=1000 | 块大小 | 太大→噪声多,太小→上下文断裂 |
chunk_overlap=200 | 重叠 | 0→切在句子中间,丢失语义 |
search_kwargs={"k": 4} | 检索条数 | 太多→噪声干扰,太少→信息不全 |
return_source_documents=True | 溯源 | 关掉→无法解释答案来源 |
💎 第二章 · 三种基础范式的演进
五、Naive RAG(原始链式结构)
结构
Query → [Embedding] → 向量检索 top-k → 拼 Prompt → LLM → 答案3 个典型问题
| 问题 | 现象 | 后果 |
|---|---|---|
| 意图不匹配 | 用户问"怎么申请",检索到"申请流程说明" | 检索到的不是答案所需 |
| 查询词太模糊 | 用户问"这个",向量检索命中不相关 | 召回跑偏 |
| 噪声块混入 | top-4 里有 2 块不相关 | 模型被干扰,甚至答错 |
根本原因:查询原样拿去检索,检索结果原样塞给模型。中间什么都没做。
六、Advanced RAG(高级 RAG)
结构:在检索前后加处理
┌─── 预检索(Pre-Retrieval)───┐
Query → [意图路由/查询改写] → 向量检索 → [过滤/重排/压缩] → 拼 Prompt → LLM
└─── 后检索(Post-Retrieval)───┘预检索(检索之前做什么)
| 技术 | 做什么 | 解决什么 |
|---|---|---|
| 意图路由 | 判断问题类型,分流到不同策略 | 多意图混杂 |
| 查询改写 | 把口语化 query 改成更适合检索的形式 | 查询词不匹配文档用词 |
查询改写样例:
原始 query:「那个报销怎么弄」
改写后:「报销流程 报销申请步骤 财务报销系统操作指南」为什么有效:用户用日常词,文档用专业词。Embedding 匹配不上,改写就是搭桥。
后检索(检索之后做什么)
| 技术 | 做什么 | 解决什么 |
|---|---|---|
| 过滤 | 按元数据/阈值筛掉不相关的块 | 噪声混入 |
| 重排 | 用更精确的模型对初步结果重新打分排序 | 粗排不准 |
| 上下文压缩 | 压缩/精简检索结果,减少 token | 上下文太长 |
def post_process(docs, score_threshold=0.5, top_n=3, reranker=None):
"""检索后的处理流水线"""
# ---------- 1. 过滤 ----------
filtered = [d for d in docs if d.metadata.get("score", 0) >= score_threshold]
# ---------- 2. 重排 ----------
if reranker:
pairs = [(query, d.page_content) for d in filtered]
scores = reranker.predict(pairs) # 精确但慢
filtered = [d for d, s in sorted(zip(filtered, scores), key=lambda x: -x[1])]
# ---------- 3. 上下文压缩 ----------
return filtered[:top_n]粗排 vs 精排的分工
| 向量检索(粗排) | Rerank(精排) | |
|---|---|---|
| 原理 | Embedding 余弦相似度 | Cross-encoder 联合编码 |
| 速度 | 快(毫秒级,扫百万级) | 慢(秒级,只扫 top-100) |
| 精度 | 粗 | 细 |
| 用法 | 全库召回 top-100 | 100 条里精选 top-3 |
为什么 Cross-encoder 更准?
Bi-encoder(向量检索):
query 单独编码 → [0.3, 0.7, ...]
doc 单独编码 → [0.5, 0.2, ...]
最后算余弦相似度 ← ★ 中间隔了一层,各自编码时不知道对方存在
Cross-encoder(rerank):
[query, doc] 一起编码 → 注意力交互 → 直接输出相关性分数
← ★ 它们在每一层 attention 里都能互相看两阶段不是冗余,是分工。
Advanced RAG 的价值与代价
Naive: Query → 检索 → 生成
Advanced: Query → [改写] → 检索 → [过滤+重排+压缩] → 生成
↑ 预检索 ↑ 后检索收益:召回率↑、噪声↓、token↓、准确率↑
代价:链路变长,延迟↑,复杂度↑,每一步都可能引入新问题(改写可能改歪、过滤可能滤掉、重排可能排错)。
七、Modular RAG(模块化 RAG)
核心思想
不是一条固定流水线,而是一堆可插拔模块 + 一套组装规则
模块库:
[意图识别] [查询改写] [多路检索] [重排] [过滤] [压缩]
[反思/校验] [重试] [引用生成] [缓存]
↓
自由组合:
简单问答 = 检索 + 生成
深度研究 = 改写 + 多路检索 + 重排 + 反思 + 重试 + 生成
客服问答 = 意图路由 + 检索 + 过滤 + 生成两种典型模式
| 模式 | 做什么 | 解决什么 |
|---|---|---|
| 自迭代优化 | 检索→生成→检查答案→不达标就改 query 再检索 | 一次检索不够准时,自己迭代 |
| 自适应框架 | 运行时根据情况选择哪些模块、什么顺序 | 不同 query 走不同路径 |
自迭代优化样例
def self_iterative_rag(query: str, vector_store, llm, max_iterations=3):
"""检索不达标就改 query 再检索,最多迭代 N 次"""
current_query = query
for i in range(max_iterations):
# ---------- 1. 检索 ----------
docs = vector_store.similarity_search(current_query, k=4)
if not docs:
current_query = f"{query} 相关说明 详细步骤"
continue
context = "\n".join(d.page_content for d in docs)
# ---------- 2. 生成 ----------
answer = llm.invoke(
f"根据以下资料回答问题,资料不足请明确说\"资料不足\":\n"
f"{context}\n\n问题:{query}"
).content
# ---------- 3. 自检 ----------
sufficient = llm.invoke(
f"判断答案是否真的回答了问题,只回 yes 或 no:\n"
f"问题:{query}\n答案:{answer}"
).content.strip().lower() == "yes"
if sufficient:
return answer, i + 1
# ---------- 4. 不够 → 改 query 再来 ----------
current_query = llm.invoke(
f"针对这个问题,生成一个更精准的检索关键词(20字内):\n{query}"
).content
return answer, max_iterations # ★ 迭代到上限也返回,不死循环关键:每次迭代都在改 query,不是重复同样的检索。
为什么要设 max_iterations? 防止"改 query 越改越歪,永远找不到"——自动机制必须有出口。
代价
模块 N 个,组合 N² 种 —— 不是模块越多越好。
💎 第三章 · GraphRAG:为什么传统 RAG 会逻辑断裂
八、传统 RAG 的翻车现场
你问:"《三国演义》里出现次数最多的人物是谁?"
传统 RAG 会怎么翻车?
用户 query → 向量检索 top-k 个最相关的段落
召回的可能是什么:
《三国演义》第三十回 诸葛亮智取华容道……
《三国演义》第三十一回 孙策激战太史慈……
……(全是按语义相似度召回的段落)
↓
拼进 prompt 塞给模型
↓
模型答:"从上述资料看,出现最多的是诸葛亮和曹操,具体次数无法确定。"问题出在哪?三个都致命:
| 问题 | 说明 |
|---|---|
| 召不回全量数据 | top-k 只取 4-8 段,根本没覆盖全书所有提及 |
| 无法聚合统计 | 模型拿到零散段落,它没法做"统计次数"这种操作 |
| 逻辑断裂 | 每段都是完整句子,但段与段之间的因果和演进关系断了 |
根本原因:传统 RAG 检索的是文本块(段落),而问题问的是实体(人物)。
粒度不匹配 —— 段落里埋着实体,但你拿到的是段落。
九、GraphRAG 怎么解决
换个粒度:不检索段落,检索图。
解析《三国演义》
↓
实体抽取:诸葛亮、曹操、孙权、刘备……(4000+ 实体)
↓
关系抽取:诸葛亮 --师从--> 诸葛亮 --辅佐--> 刘备
曹操 --敌对--> 刘备 关羽 --义兄弟--> 刘备
↓
建图:节点 = 实体,边 = 关系
↓
统计度数/出现次数 → 答案直接算出来段落变成了图的副产品,实体和关系才是主角。
十、适合场景(4 个判据)
| 判据 | 说明 | 例子 |
|---|---|---|
| ① 实体多 | 文档里有大量可识别的实体 | 人名、账号、商品 ID、设备号 |
| ② 关系复杂 | 实体之间有明确的关系边 | 社交好友、交易记录、调用链 |
| ③ 结构较明确 | 关系有稳定的 schema,不是模糊语义 | "follows"、"购买"、"调用" |
| ④ 查询需多步推理 | 要跨多个实体、多条边才能回答 | "谁认识的朋友最多" |
典型适用场景
| 场景 | 为什么适合 |
|---|---|
| 社交关系分析 | 人—人关系是天然的图 |
| 推荐系统数据 | 用户—商品—行为 三元组 |
| 小说/历史人物分析 | 人物多、关系复杂、需要统计溯源 |
| 代码调用图 | 函数—函数调用关系明确 |
| 组织架构分析 | 上下级关系是树形图 |
| 金融资金流向 | 账户—账户转账关系 |
十一、什么时候不要用 GraphRAG
| 反场景 | 为什么传统 RAG 更好 |
|---|---|
| 实体少 | 抽不出多少实体,图是空的 |
| 文档少 | 全部塞进上下文都不够,还建什么图 |
| 重点在语义不在关系 | 问的是"这段话什么意思" |
| 实时性要求高 | 建索引要几小时,文档天天变 |
| 语义相似检索 | "找所有提到支付失败的地方" |
判断口诀:
问的是"谁和谁什么关系"→ GraphRAG;问的是"哪段话说了什么"→ 传统 RAG。
十二、GraphRAG 索引构建:6 个步骤
配置模型 → 实体抽取 → 总结实体摘要 → 关系抽取 → 社区创建 → 社区报告| # | 步骤 | 做什么 | 成本 |
|---|---|---|---|
| 1 | 配置模型 | 配 LLM + Embedding | — |
| 2 | 实体抽取 | 从每个文本块抽实体(LLM 调用) | 高(每块一次) |
| 3 | 总结实体摘要 | 把散落各处的实体描述汇总(LLM 调用) | 高 |
| 4 | 关系抽取 | 抽出实体对之间的边(LLM 调用) | 高(每块一次) |
| 5 | 社区创建 | 社区检测算法把图分层(本地计算) | 中 |
| 6 | 社区报告 | 对每个社区生成摘要(LLM 调用) | 高(每社区一次) |
关键观察:
- 步骤 2/3/4/6 全是 LLM 调用,成本大头在这
- 步骤 5 是纯图算法(Leiden 社区检测),不花钱
- LLM 调用次数 ≈ 文本块数 + 实体数 + 社区数
这就是"成本高不少"的根因 —— 不是算法贵,是 LLM 调用次数多一个数量级。
十三、社区检测(Leiden):GraphRAG 的灵魂
这一步很多人不理解,但它才是 GraphRAG 能做 GlobalSearch 的原因。
为什么需要社区?
一张图有 4000 个实体、10000 条边 —— 直接塞给模型也塞不下,也总结不了。
社区检测把图分层,让每个社区变成一个"小团体"。
Leiden 算法做的事
输入:一张图(节点=实体,边=关系)
↓
识别社区(graph partition)
↓
对每个社区,评估其内部连通性
↓
如果连通性差 → 拆分 → 递归(形成层次结构)
↓
输出:层次化的社区树产出示例(三国场景):
Level 0(最细):单个实体 ── 诸葛亮 / 曹操 / 刘备 / 关羽
Level 1 :小社区 ── 蜀汉阵营 / 曹魏阵营 / 东吴阵营
Level 2 :中社区 ── 三国鼎立 / 联盟与对抗
Level 3 :大社区 ── 魏武篇 / 蜀汉篇 / 吴书每个层级对应不同粒度的"全局视角":
- 想看单个角色 → Level 0
- 想看阵营对抗 → Level 1
- 想看全书脉络 → Level 2/3
十四、社区报告:把社区写成一段人话
社区检测产出的是数字(社区 ID、成员列表),但模型读不懂数字。
社区报告这一步,用 LLM 把每个社区翻译成人类可读的摘要。
## 社区 C-0423:蜀汉核心决策层
**关键实体**:诸葛亮、刘备、关羽、张飞、法正
**关系摘要**:
法正向刘备提出"东和孙权,北拒曹操"的战略,被采纳。
诸葛亮负责执行战略,关羽张飞作为武力支撑。
刘备去世后,诸葛亮辅佐刘禅,权力高度集中。
**争议点**:
关于诸葛亮是否专权,史学界有不同看法。有了社区报告,GlobalSearch 才有东西可汇总 —— 否则问"全书主题",模型手里只有一堆 JSON 节点名。
十五、两种检索模式
| LocalSearch | GlobalSearch | |
|---|---|---|
| 适合的问题 | "诸葛亮和刘备是什么关系?" | "全书反映了什么主题?" |
| 类比 | 查字典 | 读综述 |
| 检索路径 | 找到实体 → 邻居 → 相关原文块 | 社区报告 → 汇总所有社区 |
LocalSearch 实现原理
# ============ LocalSearch ============
# 1. 从 query 里抽实体
query_entities = extract_entities(query) # "诸葛亮和刘备是什么关系?"
# → [诸葛亮, 刘备]
# 2. 在图里找这些实体
nodes = graph.get_nodes(query_entities)
# 3. 扩展邻居(关系边上的实体)
neighbors = graph.get_neighbors(nodes, depth=1)
# → [关羽, 张飞, 法正, 孙权, ...]
# 4. 关联社区
communities = graph.get_communities(nodes)
# 5. 取这些实体/邻居的原始文本块 + 社区报告
source_chunks = graph.get_source_chunks(nodes + neighbors)
community_reports = graph.get_reports(communities)
# 6. 拼 prompt → LLM 生成
context = build_context(source_chunks, community_reports)
answer = llm(f"根据以下上下文回答:{context}\n\n问题:{query}")关键路径:
Query → 实体 → 图上扩展 → 原始块 + 社区报告 → 生成LocalSearch 的本质:先定位到图上的一小块区域,再回到原始文本拿证据。
为什么还要回原始文本? 图上的关系是提炼过的抽象("师从"),原文才有细节(怎么拜的师、什么时候、在哪)。
GlobalSearch 实现原理
# ============ GlobalSearch ============
# 1. 遍历所有社区报告
reports = graph.get_all_community_reports()
# 2. 按 query 筛选相关社区
relevant = [r for r in reports if relevance_score(query, r) > threshold]
# 通常取 5-10 个最相关的
# 3. 全部塞进 context
context = "\n".join(r.content for r in relevant)
# 4. LLM 生成全局回答
answer = llm(
f"以下是知识库不同社区的摘要。请综合它们,"
f"从全局视角回答问题,指出不同社区之间的联系:\n"
f"{context}\n\n问题:{query}"
)关键路径:
Query → 社区报告集(filter)→ 综合 → 全局答案GlobalSearch 的本质:用社区报告做"中间层摘要",代替原文全量进上下文。
关键取舍:两种模式不可调和
| LocalSearch | GlobalSearch | |
|---|---|---|
| 上下文来源 | 原文证据 | 社区摘要 |
| 信息粒度 | 细 | 粗 |
| 可溯源 | 精确到段落 | 只能到社区级 |
GlobalSearch 失去了细粒度溯源。 社区报告是抽象后的摘要,它没说"这句话在哪本第几回"。
| 需求 | 该用 |
|---|---|
| "诸葛亮是哪年出生的?" | LocalSearch(需精确到原文) |
| "三国演义的战争主线是什么?" | GlobalSearch(主题归纳) |
这是不可调和的:精确溯源需要原文,全局归纳需要社区级抽象。没有一种模式能兼得。
十六、成本对比
GraphRAG 建库成本(1000 页文档估算)
| 步骤 | LLM 调用次数(估) | 每次耗时 | 总耗时 |
|---|---|---|---|
| 实体抽取 | ~2000 块 × 1 | 1-3s | 数小时 |
| 实体摘要 | ~500 实体 × 1 | 1-3s | 数十分钟 |
| 关系抽取 | ~2000 块 × 1 | 1-3s | 数小时 |
| 社区报告 | ~200 社区 × 1 | 2-5s | 数十分钟 |
| 合计 | ~5000+ 次 | — | 数小时到一天 |
跟传统 RAG 对比
| 操作 | 传统 RAG | GraphRAG |
|---|---|---|
| 首次索引构建 | 数分钟 | 数小时到一天 |
| 查询响应 | ~1s | ~2-5s(索引后) |
| LLM 成本(建库) | ~1 次嵌入 | ~5000 次 LLM 调用 |
关键差异:
- 传统 RAG:建库便宜,查询便宜
- GraphRAG:建库极贵,查询相对便宜
这是完全不同的成本结构。 选型时要知道:更新频率高 → 建库成本无法承受;不需要全局推理 → 别用。
十七、GraphRAG vs 传统 RAG 完整对比
| 维度 | 传统 RAG | GraphRAG |
|---|---|---|
| 索引对象 | 文本块(段落) | 实体 + 关系 + 社区 |
| 检索粒度 | 段落级 | 实体级 / 社区级 |
| 擅长 | 语义相似、原文细节 | 关系推理、多跳、全局聚合 |
| 不擅长 | 全局总结、因果链、统计聚合 | 纯语义匹配、精确原文溯源 |
| 索引成本 | 低(分钟) | 高(小时到天) |
| 查询延迟 | 低(~1s) | 中(~2-5s) |
| LLM 调用量 | 少 | 极多 |
| 可解释性 | 段落级 | 实体级 + 社区级 |
| 更新成本 | 低(增量更新) | 高(需重建索引) |
💎 第四章 · 五种范式全景
十八、5 种范式的完整谱系
| # | 范式 | 核心能力 | 检索策略 |
|---|---|---|---|
| 1 | Naive RAG | 基础检索 | 固定 |
| 2 | Advance RAG | 语义理解 + 检索增强 | 固定(前后加处理) |
| 3 | Modular RAG | 组件化 | 可配置组合 |
| 4 | Graph RAG | 图数据结构、关系理解 | 固定(Local/Global) |
| 5 | Agentic RAG | 自主决策 | 动态,模型决定 |
十九、5 种范式怎么区分
一个具体需求,看 5 种范式的不同表现
需求:"分析缺陷趋势,找出高频问题类型,给改进建议"
| 范式 | 输出 |
|---|---|
| Naive | 答了一堆"缺陷管理规范""缺陷分级标准" —— 完全没碰到真实数据 |
| Advance | 改写 query 后查到"常见缺陷类型分类" —— 有知识没数据 |
| Modular | 拆子问题,答了分类和根因 —— 知识层面完整,但没数据支撑 |
| Graph | 如果图谱里有历史缺陷数据,能给全局趋势 —— 但前提是数据已建图且不实时 |
| Agentic | NL2SQL 拿数据 + 检索分类标准 + 综合分析 ✅ |
完整对比表
| 维度 | Naive | Advance | Modular | Graph | Agentic |
|---|---|---|---|---|---|
| 索引对象 | 文本块 | 文本块 | 文本块 | 实体+关系+社区 | 按需,多种 |
| 检索策略 | 固定 | 固定 | 可配置组合 | 固定 | 动态,模型决定 |
| 能否调外部工具 | ❌ | ❌ | ❌ | ❌ | ✅ |
| 能否多次检索 | ❌ 一次 | ❌ 一次 | ⚠️ 可迭代 | ❌ 一次 | ✅ 想几次就几次 |
| 能否规划 | ❌ | ❌ | ⚠️ 预设流程 | ❌ | ✅ 运行时规划 |
| 数据实时性 | 离线索引 | 离线索引 | 离线索引 | 离线索引 | ✅ 实时调用 |
| 多智能体 | ❌ | ❌ | ❌ | ❌ | ✅ |
| 延迟 | ~1s | ~2s | ~3-10s | ~2-5s | ~10-60s |
| 成本 | 最低 | 低 | 中 | 高(建库) | 最高 |
| 实现难度 | ⭐ 简单 | ⭐⭐ 中 | ⭐⭐⭐ 中高 | ⭐⭐⭐⭐ 高 | ⭐⭐⭐⭐⭐ 最高 |
二十、5 种范式不是替代关系
┌─────────────────────────────────┐
│ Agentic RAG(范式 5) │
│ 模型自主规划 + 动态工具调用 │
│ ★ 能用下面所有能力 │
└───────────────┬─────────────────┘
│ 可选组合
┌────────────────────────┼────────────────────────┐
↓ ↓ ↓
┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Graph RAG │ │ Modular RAG │ │ Advance RAG │
│ (范式 4) │ │ (范式 3) │ │ (范式 2) │
└───────────────┘ └─────────────────┘ └─────────────────┘
↑ ↑ ↑
└────────────────────────┴────────────────────────┘
│
┌────────────────────┐
│ Naive RAG(范式1)│
└────────────────────┘范式 5 是容器,范式 1-4 是可选的能力插件。
Agentic RAG 的工具池里可以同时放:
vector_search(Advance 能力)graph_search(Graph 能力)- 自定义 workflow(Modular 能力)
二十一、每种范式解决的侧面
| 范式 | 解决的侧面 | 一句话 |
|---|---|---|
| Naive | 能不能找到 | 基础检索能力 |
| Advance | 找得准不准 | 检索质量问题 |
| Modular | 怎么组合 | 流程灵活性问题 |
| Graph | 能不能理解关系 | 数据结构问题 |
| Agentic | 该怎么做 | 决策问题 |
演进方向:
从"固定流程" → "动态决策"
从"被动检索" → "主动规划"
从"单一数据源" → "多工具编排"
从"一次问答" → "多步执行"二十二、选型决策树
① 需要查实时数据或做统计计算吗?
├─ 是 → 需要 Agentic RAG(只有它能调 NL2SQL / API)
│ ↓
│ ② 问题类型多样(有的简单有的复杂)吗?
│ ├─ 是 → 加分类器分流(简单走 Naive/Advance,复杂走 Agentic)
│ └─ 否 → 全量走 Agentic
│
└─ 否 → 需要理解实体间的关系吗("谁和谁什么关系")?
├─ 是 → Graph RAG(前提:能接受离线建图的成本)
└─ 否 → 需要多步组合推理吗?
├─ 是 → Modular RAG(组件可配置)
└─ 否 → 检索效果不好吗?
├─ 是 → Advance RAG(加前后处理)
└─ 否 → Naive RAG(够用了,别过度设计)二十三、过度设计风险
技术选择的最大误区:不是因为复杂就选复杂的。
| 场景 | 常见误选 | 正确选择 | 理由 |
|---|---|---|---|
| FAQ 机器人(50 条固定问答) | Agentic RAG | Naive RAG | 50 条文档直接塞 prompt 都够 |
| 公司内部文档问答(静态) | GraphRAG | Advance RAG | 没实体关系,建图纯浪费 |
| 单个 Agent 处理 5 个固定任务 | Agentic RAG | Modular RAG | 流程已知,不需要自主决策 |
| 实时运维助手 | Naive RAG | Agentic RAG | 必须调 API 拿实时状态 |
过度设计的代价不只是慢和贵:
Agentic RAG 用在简单问题上:
1. 慢了 10-50 倍(用户不耐烦)
2. 成本高 5-10 倍
3. ★ 答案质量可能反而下降
→ 太多工具调用会引入噪声
→ 模型可能在多个工具间"纠结"而不是直接回答
4. 增加了失败点(每个工具都可能出错)第 3 条最容易被忽略 —— 这不是"慢一点但结果一样",而是"可能变错"。
💎 第五章 · 核心洞察
1. RAG 的根本主张:知识不进权重,进索引。 改索引分钟级,改权重小时级。
2. 不能把上下文塞满——塞得越多越不准(lost in the middle),这是最反直觉的一条。
3. 传统 RAG 的根本限制是粒度不匹配 —— 它检索的是段落,问题问的是实体,段落里埋着实体,但模型没法跨段落聚合。
4. 社区检测(Leiden)是 GraphRAG 的灵魂 —— 它把图分层,让"全局视角"成为可能。没有社区报告,GlobalSearch 无从谈起。
5. LocalSearch 和 GlobalSearch 是不可调和的取舍 —— 一个要精确到原文,一个只能到社区级。要溯源用 Local,要全局用 Global。
6. GraphRAG 的成本瓶颈在建库,不在查询 —— 5000+ 次 LLM 调用,数小时到一天。更新频繁的语料库用不起。
7. 判断口诀:问"谁和谁什么关系"用 GraphRAG,问"哪段话说了什么"用传统 RAG。
8. 5 种范式不是替代关系,是"能力叠加"关系 —— 范式 5(Agentic)是容器,范式 1-4 是可选插件。
9. 过度设计的代价包括"答案质量下降" —— 太多工具调用会引入噪声,模型可能在工具间纠结而不是直接回答。
💬 最后一句话
"RAG 不是一种技术,是一条谱系。选错范式的代价,比调错参数大 10 倍。"
"从固定流程到动态决策,从被动检索到主动规划——这就是这五个范式的演进主线。"
"不要因为复杂就选复杂。先问:这个需求真的需要 Agent 吗?"
📚 参考
- 后续篇:《RAG 工程化实战》—— 分层评估 / 查询转换 5 方法 / 分块策略 / Agentic RAG 实现
- 相关技术:
- LangChain / LlamaIndex —— RAG 主流框架
- Microsoft GraphRAG —— 图 RAG 开源实现
- Leiden Algorithm —— 社区检测算法
- Cohere Rerank —— Cross-encoder 重排模型