ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

RAG从Demo到生产:六道分水岭与Agentic RAG、GraphRAG实战

RAG从Demo到生产:六道分水岭与Agentic RAG、GraphRAG实战

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),另一种是 RRF(Reciprocal Rank Fusion,倒数排名融合)。

RRF 我实测下来更稳,因为它不依赖分数归一化。不同检索器的分数尺度完全不一样,向量相似度是 0 到 1,BM25 分数可能是 0 到几十,直接加权很容易被某一方主导。RRF 只看排名,公式是score = Σ 1/(k + rank),k 一般取 60。这样两路检索的排名信息都能公平地贡献进来。

# RRF 融合的简化实现 def rrf_fusion(vector_results, bm25_results, k=60): 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(), key=lambda x: x[1], reverse=True)

提示:混合检索的收益在“查询包含专有名词或精确术语”时最明显。如果你的知识库全是通用叙述性文本,收益可能没那么大,但加上基本不会亏。

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 RAG

4.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 节点。检索完回到 agent,Agent 看到新检索的内容,再决定是继续检索还是生成。这样就实现了多轮检索和自主决策。

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
预算和延迟敏感向量 RAG

GraphRAG 不是银弹,它的构建和维护成本很高。如果知识库更新频繁,图很难保持最新。我的建议是:先用向量 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 检索和生成分开评估

评估要拆成两块:检索质量和生成质量。

检索质量的核心指标是 Recall@k 和 MRR(Mean Reciprocal Rank)。Recall@k 衡量前 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 或结构化文本再切块,图片可以用多模态模型生成描述再入库。这块展开又是另一个话题了,但值得单独花时间处理,因为很多企业知识库的核心信息就在表格里。

返回列表