
1. 为什么“RAG 烂大街”是个伪命题这两年但凡跟大模型沾点边的项目十有八九会挂上 RAG 三个字母。招聘 JD 里写“熟悉 RAG 检索增强”技术分享里讲“我们基于 RAG 搭建了企业知识库”开源社区里各种 RAG 框架层出不穷。于是很多人得出一个结论RAG 已经烂大街了没什么技术含量。我一开始也这么觉得。直到真正把一套 RAG 系统从 demo 推到生产环境被各种脏数据、召回不准、答非所问、上下文超长、多跳推理失败反复教育之后我才意识到一个事实烂大街的从来不是 RAG 本身而是那条“文档切块 → 向量化 → 存向量库 → 相似度检索 → 塞进 prompt”的流水线。这条流水线谁都能搭网上教程一搜一大把零基础半天就能跑通一个能问答的 demo。但 demo 和生产之间隔着六道真正的分水岭。这六处分水岭决定了你的 RAG 是“玩具”还是“能下地干活的工具”。这篇文章我就围绕这六处分水岭把每一处的核心原理、实操要点、踩坑经验掰开揉碎讲清楚。涉及到的关键词包括 RAG、Agentic RAG、Contextual Retrieval、GraphRAG、LangGraph我会尽量用从业者之间交流的口吻把“为什么这么做”讲透而不是只丢一堆配置。先说清楚这篇文章适合谁看如果你已经跑通过最基础的 RAG demo但发现效果不稳定、召回质量差、复杂问题答不上来想搞清楚下一步该往哪优化那这篇就是写给你的。如果你是完全零基础也能看懂因为我会在关键处补基础概念只是信息密度会比较高建议边看边对照自己的项目。2. 分水岭一检索质量从“能召回”到“召回得准”2.1 朴素向量检索为什么经常翻车最基础的 RAG 用的是稠密向量检索把文档切块用 embedding 模型编码成向量查询时把问题也编码成向量算余弦相似度取 top-k。这套逻辑听起来很合理但实际用起来问题一大堆。第一个问题是语义漂移。向量相似度高不代表内容真的相关。我做过一个测试问“公司年假怎么申请”检索出来的 top-3 里居然有一条是“公司年会安排通知”因为“年假”和“年会”在向量空间里离得很近。这种错误在中文场景里特别常见因为中文的 embedding 模型对细粒度语义的区分能力参差不齐。第二个问题是关键词失配。用户问的是“报销流程”但文档里写的是“费用核销指引”如果 embedding 模型没学好这两个词的语义关联就召不回来。反过来用户问一个专有名词、产品型号、错误码向量检索经常抓瞎因为这些词的语义信息很稀疏。第三个问题是top-k 的取舍困境。k 取小了召回不全k 取大了噪声一大堆塞进 prompt 反而干扰模型。我见过太多项目把 k 设成 10 甚至 20结果模型被无关内容带偏答非所问。2.2 混合检索稠密加稀疏才是正解解决上面这些问题最直接的手段是混合检索Hybrid Retrieval稠密向量负责语义匹配稀疏检索BM25、TF-IDF 这类负责关键词精确匹配两路结果融合。BM25 的原理不复杂它本质上是基于词频和逆文档频率的打分对专有名词、型号、错误码这类“低频但关键”的词特别敏感。向量检索抓语义BM25 抓字面两者互补。融合方式常见的有两种一种是加权求和比如 0.7 向量 0.3 BM25另一种是 RRFReciprocal Rank Fusion倒数排名融合。RRF 我实测下来更稳因为它不依赖分数归一化。不同检索器的分数尺度完全不一样向量相似度是 0 到 1BM25 分数可能是 0 到几十直接加权很容易被某一方主导。RRF 只看排名公式是score Σ 1/(k rank)k 一般取 60。这样两路检索的排名信息都能公平地贡献进来。# RRF 融合的简化实现 def rrf_fusion(vector_results, bm25_results, k60): scores {} for rank, doc_id in enumerate(vector_results): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank 1) for rank, doc_id in enumerate(bm25_results): scores[doc_id] scores.get(doc_id, 0) 1 / (k rank 1) return sorted(scores.items(), keylambda x: x[1], reverseTrue)提示混合检索的收益在“查询包含专有名词或精确术语”时最明显。如果你的知识库全是通用叙述性文本收益可能没那么大但加上基本不会亏。2.3 重排序把真正相关的顶上来混合检索解决了“召回”的问题但 top-k 里还是会有噪声。这时候需要重排序Rerank。重排序模型比如 BGE-Reranker、Cohere Rerank 这类是一个交叉编码器它把 query 和每个候选文档拼在一起打分精度比向量相似度高得多但速度慢所以只对 top-k 的候选做精排。我的标准流程是混合检索召回 top-50重排序精排取 top-5再塞进 prompt。这样既保证了召回率又保证了精度。实测下来加了重排序之后答案准确率能提升 15% 到 30%具体取决于知识库的噪声程度。重排序的代价是延迟。交叉编码器对 50 个候选打分可能要几百毫秒到一两秒。如果对延迟敏感可以只对 top-20 做重排或者用更小的重排模型。这里没有标准答案得根据你的场景权衡。2.4 查询改写别让用户的问法限制你用户的问题往往很口语化、很模糊直接拿去检索效果很差。查询改写Query Rewriting就是让 LLM 先把用户问题改写成更适合检索的形式。常见的改写策略有几种。第一种是扩展把“年假怎么请”扩展成“年假申请流程、年假审批步骤、年假天数规定”。第二种是分解把“A 和 B 的区别以及各自适用场景”拆成两个子查询分别检索。第三种是消歧结合对话历史把“它怎么用”里的“它”替换成具体指代的对象。查询改写用 LLM 做成本不高但收益明显。我一般会在检索前加一步改写把口语化问题转成 2 到 3 个检索友好的查询分别检索后合并结果。这一步在 Agentic RAG 里会更进一步后面会讲。3. 分水岭二切块策略从“按字数切”到“按语义切”3.1 固定长度切块的三个致命伤最省事的切块方式是按固定字符数切比如每 500 字一块重叠 50 字。这种方式实现简单但问题很多。第一个致命伤是切断语义单元。一个完整的段落、一个表格、一段代码被硬生生从中间切开前半块和后半块各自都不完整。检索到前半块模型看到的是半句话自然答不好。第二个致命伤是块内主题混杂。500 字里可能前半段讲 A 功能后半段讲 B 功能向量编码后变成一个“四不像”的向量跟 A 和 B 都不太像。第三个致命伤是忽略文档结构。Markdown 的标题层级、PDF 的章节、HTML 的标签这些都是天然的语义边界固定切块完全无视它们。3.2 结构感知切块顺着文档的骨架切结构感知切块Structure-aware Chunking的核心思路是优先按文档的天然结构切结构太大再按语义切语义还太大才按长度切。具体做法是先解析文档结构Markdown 按标题层级切PDF 按章节和段落切代码按函数和类切。每个块尽量保持语义完整。如果某个章节太长再用语义切块模型比如基于句子 embedding 相似度的切分把它切成子块。最后才用固定长度兜底。这样做的好处是每个块都是一个完整的语义单元向量编码更准确检索命中的内容也更完整。代价是实现复杂一些需要针对不同文档格式写解析逻辑。3.3 Contextual Retrieval给每个块补上下文Anthropic 提出的Contextual Retrieval是我这两年觉得最有价值的改进之一。它的核心洞察是一个块被单独拿出来看往往丢失了它在原文中的上下文导致检索和生成都受影响。举个例子文档里有一句“该功能默认关闭需要在配置文件中开启”。单独看这个块你不知道“该功能”是什么功能。Contextual Retrieval 的做法是在切块之后用 LLM 给每个块生成一段简短的上下文说明把块放回原文语境里然后把这段说明拼在块前面一起编码。# Contextual Retrieval 的 prompt 模板示意 context_prompt 以下是整篇文档 document {document} /document 以下是文档中的一个块 chunk {chunk} /chunk 请用一两句话说明这个块在整篇文档中的位置和上下文 以便这个块被单独检索时仍能被正确理解。只输出说明不要重复块内容。 这样做之后每个块都带上了“我是谁、我在哪”的信息检索准确率提升很明显。Anthropic 的实测数据是结合上下文检索和 BM25 混合检索检索失败率能降低 49%再加上重排序能降低 67%。这个数字我自己的项目里没有精确复现但方向是对的提升确实可观。代价是切块阶段要为每个块调一次 LLM成本和时间都上去了。优化手段是用便宜的小模型做这一步或者用 prompt caching 缓存整篇文档的编码只让块内容变化。3.4 块大小和重叠的取舍块大小没有万能值。我的经验是叙述性文档 300 到 500 字技术文档 500 到 800 字代码块按函数粒度。块太小上下文不足块太大噪声多且检索精度下降。重叠overlap的作用是防止边界信息丢失一般设块大小的 10% 到 20%。但重叠不是越多越好重叠太多会导致重复内容被多次召回浪费上下文窗口。我一般设 10%结构感知切块的话重叠可以更少因为边界本身就是语义边界。注意块大小要和你的 embedding 模型的最大输入长度匹配。有些模型只支持 512 token你切 1000 字可能被截断后面的内容等于没编码。4. 分水岭三从 Naive RAG 到 Agentic RAG4.1 Naive RAG 的天花板在哪Naive RAG 的流程是线性的检索一次生成一次结束。这个流程能处理“事实型问答”比如“XX 政策的申请条件是什么”。但遇到下面这些情况就歇菜了。多跳问题“A 公司的 CEO 毕业于哪所大学这所大学在哪个城市”这需要先查 A 公司 CEO再查这个人的毕业院校再查院校所在城市至少三次检索。Naive RAG 一次检索搞不定。需要判断的问题“这份合同有没有风险条款”这需要先检索合同相关条款再判断可能还要检索公司风控标准做对比。检索失败需要重试的问题第一次检索没找到相关内容Naive RAG 就直接说“我不知道”但其实换个查询词就能找到。4.2 Agentic RAG 的核心把检索变成工具Agentic RAG的核心思路是把检索封装成一个工具tool让 Agent 自己决定什么时候检索、检索什么、检索几次。Agent 有了自主决策能力就能处理上面那些复杂场景。用 LangGraph 来实现的话基本结构是一个带条件边的状态图Agent 节点负责决策工具节点负责执行检索条件边根据 Agent 的输出决定是继续检索还是生成答案。LangGraph 的好处是它把 Agent 的执行流程显式建模成图每个节点的输入输出、状态流转都清清楚楚调试起来比黑盒 Agent 容易得多。# LangGraph 构建 Agentic RAG 的骨架示意 from langgraph.graph import StateGraph, END def build_agentic_rag(): graph StateGraph(AgentState) graph.add_node(agent, agent_node) # LLM 决策 graph.add_node(retrieve, retrieve_node) # 执行检索 graph.add_node(generate, generate_node) # 生成答案 graph.set_entry_point(agent) graph.add_conditional_edges( agent, should_retrieve, # 判断是否需要检索 {retrieve: retrieve, generate: generate} ) graph.add_edge(retrieve, agent) # 检索完回到 agent 再判断 graph.add_edge(generate, END) return graph.compile()这个图的关键在于should_retrieve这个条件函数。它根据 Agent 的输出决定下一步如果 Agent 认为需要检索就走 retrieve 节点如果认为信息够了就走 generate 节点。检索完回到 agentAgent 看到新检索的内容再决定是继续检索还是生成。这样就实现了多轮检索和自主决策。4.3 工具设计检索工具不止一个Agentic RAG 里检索工具可以有很多个让 Agent 按需选择。常见的工具有向量检索工具处理语义查询关键词检索工具处理精确匹配SQL 查询工具处理结构化数据API 调用工具查实时数据计算工具做数学运算Agent 根据问题类型选择工具。比如问“上个月销售额是多少”Agent 应该选 SQL 工具而不是向量检索。问“产品手册里怎么配置”应该选向量检索。这种“工具路由”能力是 Agentic RAG 相比 Naive RAG 的一大优势。4.4 循环控制与成本控制Agentic RAG 最大的风险是无限循环。Agent 可能反复检索每次都觉得信息不够停不下来。必须设置最大迭代次数一般 3 到 5 次就够了。超过就强制生成哪怕信息不全。另一个风险是成本失控。每次检索加一次 LLM 调用多轮下来 token 消耗是 Naive RAG 的好几倍。我的做法是简单问题走快速路径一次检索直接生成复杂问题才走 Agent 路径。判断复杂度可以用一个轻量分类器或者让 Agent 自己判断。实操心得Agentic RAG 不是越复杂越好。我见过有人把所有问题都走 Agent 多轮检索结果延迟从 2 秒涨到 15 秒用户体验反而下降。先用简单流程覆盖 80% 的问题剩下 20% 再上 Agent。5. 分水岭四GraphRAG 与知识图谱的引入5.1 向量检索搞不定的“全局性问题”有一类问题向量检索天生搞不定叫全局性问题。比如“这份报告的主要主题有哪些”“整个知识库涉及哪些产品线”“A 和 B 之间有什么关联”。这类问题需要综合整个语料的信息而不是检索几个相关块。向量检索的机制决定了它只能找到“和 query 最像的几个块”无法做全局汇总。你问“主要主题”它给你几个碰巧包含“主题”二字的块答非所问。5.2 GraphRAG 的思路先建图再检索GraphRAG的思路是先用 LLM 从文档里抽取实体和关系构建知识图谱再对图谱做社区检测和摘要检索时既可以用图结构做多跳推理也可以用社区摘要回答全局性问题。具体流程分几步。第一步是实体和关系抽取让 LLM 读每个块抽出实体人、组织、产品、概念和它们之间的关系。第二步是图谱构建把实体作为节点关系作为边构建图。第三步是社区检测用 Leiden 这类算法把图划分成社区每个社区是一组紧密相关的实体。第四步是社区摘要让 LLM 给每个社区生成摘要。检索时局部问题走实体检索全局问题走社区摘要。这样做的好处是全局性问题可以通过读取社区摘要来回答多跳问题可以沿着图的边做推理。代价是构建成本高一个中等规模的知识库建图可能要跑几个小时甚至更久token 消耗也大。5.3 GraphRAG 和向量 RAG 怎么选不是所有场景都需要 GraphRAG。我的判断标准是场景特征推荐方案事实型问答为主向量 RAG 混合检索需要多跳推理GraphRAG 或 Agentic RAG需要全局汇总GraphRAG知识库频繁更新向量 RAG图维护成本高实体关系密集GraphRAG预算和延迟敏感向量 RAGGraphRAG 不是银弹它的构建和维护成本很高。如果知识库更新频繁图很难保持最新。我的建议是先用向量 RAG 加混合检索加 Agentic 把效果做到位确实遇到全局性和多跳问题解决不了再考虑上 GraphRAG。5.4 Ontology RAG给图谱加一层本体Ontology RAG是在 GraphRAG 基础上更进一步引入本体ontology来规范实体和关系的类型。比如定义“产品”是一个类“属于”是一种关系“产品 A 属于产品线 B”就是一个符合本体的三元组。本体的好处是让图谱结构更规范检索时可以做类型约束的查询。比如“查询所有属于产品线 B 的产品”有本体的话可以直接按类型查没有本体就只能靠字符串匹配。代价是需要预先设计本体这需要领域知识不是自动能搞定的。6. 分水岭五评估体系没有度量就没有优化6.1 为什么 RAG 评估这么难RAG 的效果评估比传统 NLP 任务难得多。传统任务有标准答案对错分明。RAG 的答案往往是开放式的而且涉及检索和生成两个环节出错了你都不知道是检索没召回到还是召回到了但生成没用好。我见过太多团队凭感觉调 RAG今天觉得这个 chunk size 好明天觉得那个 rerank 模型强调来调去没有方向。根本原因是没有建立评估体系。6.2 检索和生成分开评估评估要拆成两块检索质量和生成质量。检索质量的核心指标是 Recallk 和 MRRMean Reciprocal Rank。Recallk 衡量前 k 个结果里有没有包含正确答案所在的块MRR 衡量正确答案排在第几位。这两个指标需要标注数据每个测试问题人工标出哪些块是相关的。生成质量的指标包括忠实度Faithfulness和答案相关性Answer Relevance。忠实度衡量答案是否完全基于检索到的内容没有编造答案相关性衡量答案是否切题。这两个指标可以用 LLM 来打分也就是所谓的 LLM-as-a-Judge。# 用 LLM 评估忠实度的 prompt 示意 faithfulness_prompt 基于以下检索内容回答问题然后评估答案是否完全基于检索内容。 检索内容 {context} 问题{question} 答案{answer} 请判断答案中的每个陈述是否都能在检索内容中找到依据。 输出格式{{faithful: true/false, unsupported_claims: [...]}} 6.3 构建评估集的实操方法评估集不用很大50 到 100 个问题就能反映问题。构建方法是从真实用户问题里采样或者让领域专家出题。每个问题标注标准答案和相关的块。我一般会分几类问题简单事实型、多跳推理型、全局汇总型、边界情况知识库里没有答案的。每类都要有这样才能全面评估。评估要定期跑每次改动换 embedding 模型、调 chunk size、加重排序都跑一遍看指标变化。没有评估集所有优化都是盲调。6.4 常见评估陷阱第一个陷阱是测试集和训练集重叠。如果你用知识库里的内容出题又用同样的内容检索指标会虚高。测试问题应该用真实用户问法而不是文档里的原句。第二个陷阱是只看平均分。平均分高不代表没问题可能简单问题全对难问题全错。要分类看指标。第三个陷阱是LLM 评委的偏见。LLM 打分会有偏好比如偏好长答案、偏好流畅的表达。最好用多个 LLM 交叉验证或者人工抽检校准。7. 分水岭六工程化与生产落地7.1 从 demo 到生产的鸿沟demo 能跑通不代表能上线。生产环境要考虑的东西多得多并发、延迟、成本、监控、降级、数据更新。我见过太多项目 demo 惊艳上线就崩。用户一多检索延迟飙升知识库一更新索引重建要几个小时模型一抽风整个服务不可用。这些都是工程问题不是算法问题但决定了 RAG 能不能真正落地。7.2 索引更新策略知识库不是静态的文档会增删改。索引更新有几种策略全量重建简单粗暴但耗时长适合小知识库或低频更新增量更新只处理变化的文档需要追踪文档变更双索引切换维护新旧两套索引重建完成后原子切换避免服务中断我的建议是小知识库用全量重建加定时任务大知识库用增量更新加双索引切换。切换时要注意 embedding 模型版本一致否则新旧向量不在同一空间检索会乱。7.3 缓存与降级缓存能大幅降低延迟和成本。常见的缓存层有查询缓存相同问题直接返回缓存答案检索缓存相同查询直接返回缓存检索结果embedding 缓存相同文本不重复编码降级策略是保命用的。检索服务挂了降级到关键词检索LLM 服务超时降级到返回检索原文让用户自己看。有降级总比整个服务挂掉强。7.4 监控指标生产环境必须监控这些指标指标说明告警阈值建议检索延迟 P99检索环节的尾部延迟 2s生成延迟 P99生成环节的尾部延迟 10s检索命中率有检索结果的查询占比 90%空答案率返回“不知道”的占比 20%token 消耗每查询平均 token突增 50%用户反馈点赞点踩比例点踩率 15%这些指标能帮你快速定位问题。检索延迟高可能是向量库负载高空答案率高可能是检索召回差token 消耗突增可能是 Agent 循环失控。7.5 一个完整的生产架构参考把前面讲的整合起来一个生产级 RAG 架构大概是这样查询进来先过查询改写生成多个检索查询。然后走混合检索向量和 BM25 并行召回RRF 融合。融合结果过重排序取 top-k。如果是复杂问题走 Agentic 流程多轮检索。检索结果塞进 prompt调 LLM 生成。生成结果过忠实度检查不通过就重试或降级。全程有缓存、有监控、有降级。这套架构不便宜但能扛住生产环境的考验。具体要不要全上看你的场景和预算。核心是每一层都要有明确的收益不要为了架构而架构。8. 六处分水岭的优先级与落地建议讲了这么多最后说说落地顺序。如果你现在手上有一个效果一般的 RAG 项目想优化我建议按这个优先级来第一优先是评估体系。没有评估后面所有优化都是盲调。先花一周时间建评估集把检索和生成的指标跑起来。第二优先是混合检索加重排序。这是投入产出比最高的一步改动不大效果提升明显。先把召回和精度做上去。第三优先是切块策略优化。结构感知切块加 Contextual Retrieval能显著提升检索质量。这一步需要一些工程投入但值得。第四优先是查询改写。用 LLM 改写查询成本低收益高尤其是用户问法口语化的场景。第五优先是Agentic RAG。处理复杂问题需要但会增加延迟和成本建议在简单流程跑通后再上。第六优先是GraphRAG。构建成本最高只在确实需要全局汇总和多跳推理时上。这个顺序不是绝对的得根据你的具体场景调整。但核心逻辑是先度量再优化召回再优化切块再优化查询最后才上复杂架构。很多团队一上来就搞 GraphRAG、搞 Agent结果基础没打好效果反而更差。我在实际项目里踩过最大的坑就是过早引入复杂架构。当时觉得 Agentic RAG 很酷把所有查询都走 Agent 多轮检索结果延迟爆炸用户流失。后来退回来先用简单流程覆盖大部分场景只对复杂问题走 Agent体验才回来。RAG 的优化是个渐进过程每一步都要有数据支撑别凭感觉上架构。最后分享一个小技巧如果你的知识库里有大量表格和图片纯文本 RAG 会丢信息。表格可以转成 Markdown 或结构化文本再切块图片可以用多模态模型生成描述再入库。这块展开又是另一个话题了但值得单独花时间处理因为很多企业知识库的核心信息就在表格里。