RAG从能用变好用的六道分水岭:检索、Agentic与工程化实战

发布时间:2026/10/7 2:25:08

RAG从能用变好用的六道分水岭:检索、Agentic与工程化实战 1. 先搞清楚烂大街的到底是什么RAG 这个词这两年被聊得太多多到很多人一听就烦。随便打开一个技术社区满屏都是“手把手教你搭 RAG”“三行代码实现知识库问答”“RAG 从入门到精通”。教程的套路也高度雷同文档切块、向量化、存进向量库、检索 Top-K、拼进 Prompt、丢给大模型。这套流程跑通一遍快的话一个下午慢的话一周。于是很多人得出一个结论RAG 没什么技术含量就是个流水线。我一开始也这么觉得。直到真正把一套 RAG 系统放到生产环境里面对真实用户的提问、真实的文档、真实的业务场景才发现那条流水线只是起点。流水线能跑通不代表能跑好。跑通和跑好之间隔着六道分水岭。这六处才是决定一套 RAG 系统是“玩具”还是“工具”的关键。这篇文章不打算再教你搭那条流水线网上已经够多了。我想聊的是那条流水线之外的东西——那些让 RAG 从“能用”变成“好用”的分水岭。适合已经跑通过基础 RAG、正在被检索质量折磨、或者准备把 RAG 往生产环境推的人。如果你还在纠结怎么装向量库这篇文章可以先收藏等你跑通第一版再回来看。2. 分水岭一检索质量从“找得到”到“找得准”2.1 为什么 Top-K 检索经常“答非所问”基础 RAG 的检索逻辑很简单把用户问题向量化去向量库里找余弦相似度最高的 K 个块。这个逻辑在文档结构规整、问题指向明确的时候表现还行。但真实场景里用户的问题往往模糊、口语化、甚至带着错别字而文档里的表述是正式的、专业的。两者之间的语义鸿沟靠一个向量相似度很难填平。我遇到过最典型的情况用户问“这个东西坏了怎么修”文档里写的是“设备故障排除流程”。向量模型能捕捉到“坏”和“故障”的语义关联但检索出来的块可能包含大量无关的故障类型真正相关的那一段反而排在后面。Top-K 取 5 的时候可能只有 1 到 2 个块是真正有用的剩下的都是噪声。这些噪声拼进 Prompt不仅浪费 token还会干扰大模型的判断。更麻烦的是向量检索对“精确匹配”不敏感。用户问一个具体的型号、编号、人名向量模型可能把它泛化掉检索出一堆语义相近但实体不对的块。这时候就需要引入关键词检索作为补充也就是常说的混合检索。2.2 混合检索的实操配置与参数取舍混合检索的核心思路是向量检索负责语义召回关键词检索负责精确召回两路结果融合后重排。我目前用的方案是 BM25 加向量检索融合方式用 Reciprocal Rank Fusion简称 RRF。RRF 的公式很简单对每个文档计算它在每一路检索结果中的排名倒数之和。排名越靠前得分越高。这个方法的优势是不需要调权重两路检索的分数尺度不一致也没关系只看排名。具体参数上我一般这样配参数取值说明向量检索 Top-K20召回阶段宁多勿少BM25 Top-K20同上RRF 融合后保留10送入重排重排后保留3-5最终拼入 Prompt这里有个经验召回阶段一定要多取。很多人为了省资源向量检索只取 5 个BM25 也只取 5 个融合后剩 3 个。这样做的后果是真正相关的块可能在召回阶段就被漏掉了后面重排再厉害也救不回来。召回是“宁可错杀一千”重排是“精准打击”两个阶段的策略要分开。BM25 的实现可以用 rank_bm25 这个库轻量够用。如果文档量大可以考虑 Elasticsearch 或者 OpenSearch 自带的 BM25性能更好。向量检索这边Milvus、Qdrant、Weaviate 都支持选哪个看团队熟悉度。我个人的偏好是 Qdrant部署简单过滤功能强适合中小规模场景。2.3 重排模型的选择Cross-Encoder 还是 LLM融合后的结果需要重排。重排模型分两类Cross-Encoder 和 LLM 重排。Cross-Encoder 的代表是 BGE-Reranker 系列中文场景下 bge-reranker-base 和 bge-reranker-large 都表现不错。它的原理是把 query 和 document 拼在一起送进模型输出一个相关性分数。因为 query 和 document 之间有充分的交叉注意力精度比双塔向量模型高很多。缺点是慢每个候选块都要单独跑一次10 个块就是 10 次推理。但在 10 个候选的规模下延迟可以接受GPU 上大概几十毫秒。LLM 重排则是让大模型直接判断每个块和问题的相关性输出排序。优点是灵活可以处理复杂的相关性判断比如“这个块是否回答了问题”而不是“这个块是否和问题相关”。缺点是贵而且延迟高。我一般只在 Cross-Encoder 效果不够好的场景下才用 LLM 重排比如问题需要多跳推理的时候。实测下来bge-reranker-large 在大多数中文场景下已经够用。如果你的场景对延迟极其敏感可以用 bge-reranker-base精度损失不大速度快一倍。注意重排模型的输入长度有限制一般是 512 token。如果你的文档块超过这个长度需要截断。截断策略上我建议保留块的开头部分因为关键信息通常在段首。3. 分水岭二上下文检索别让块“孤立无援”3.1 块切分的两难切太碎丢上下文切太大丢精度文档切分是 RAG 里最容易被低估的环节。很多人直接用 LangChain 的 RecursiveCharacterTextSplitter设个 chunk_size500overlap50就完事了。这样切出来的块单独看可能语义完整但放到整个文档里它可能只是某个章节的一小部分缺少必要的背景信息。举个例子。一份产品手册里有一段“该功能在 v2.3 版本后默认开启如需关闭请修改配置文件中的 enable_feature 字段。”这个块单独看用户知道要改配置但不知道这个功能是什么、为什么要关、改了之后有什么影响。如果检索时只召回这个块大模型只能基于这个块回答结果就是“要关闭请修改 enable_feature 字段”用户还是一头雾水。这就是块切分的两难切太碎上下文丢失切太大检索精度下降因为一个块里混了太多主题向量表示被稀释了。3.2 Contextual Retrieval 的落地方法Anthropic 提出的 Contextual Retrieval 思路很直接在把每个块存入向量库之前先用大模型给这个块生成一段上下文说明然后把说明和块拼在一起做向量化。这样检索时块本身就携带了必要的背景信息。具体操作上我一般这样做对每个块把整个文档的摘要或者所在章节的标题作为上下文让大模型生成一句话说明这个块在讲什么。比如上面那个例子生成的上下文可能是“本段出自产品手册的‘功能开关’章节介绍 v2.3 版本后默认开启的某功能如何关闭。”然后把这个说明拼在块前面一起向量化。这个方法的成本在于每个块都要调一次大模型。如果文档有几千个块成本不低。优化方案是批量处理把多个块拼成一个 Prompt让大模型一次性生成多个块的上下文说明。实测下来批量处理能把成本降到原来的三分之一左右。另一个更轻量的方案是不调大模型直接用规则生成上下文。比如把块的父级标题、所在章节的路径拼在块前面。这个方法零成本效果虽然不如大模型生成的好但在文档结构清晰的情况下已经够用。我现在的做法是混合结构清晰的文档用规则结构混乱的文档用大模型。3.3 元数据过滤被忽视的检索加速器除了上下文元数据也是提升检索质量的重要手段。每个块除了文本内容还可以附带元数据比如来源文件、章节、页码、创建时间、文档类型等。检索时可以先按元数据过滤再做向量检索这样能大幅缩小搜索范围提升精度和速度。举个例子。用户问“2024 年的销售政策是什么”如果你在向量检索前先按“文档类型政策”和“年份2024”过滤检索范围可能从几万个块缩小到几百个块精度自然提升。元数据的提取可以在文档入库时用规则或大模型完成一次性成本长期受益。实操心得元数据字段不要太多3 到 5 个核心字段就够。字段太多维护成本高而且用户查询时不一定用得上。我一般保留来源、章节、日期、类型这四个。4. 分水岭三Agentic RAG让检索学会“自己想办法”4.1 单次检索的天花板在哪里基础 RAG 的检索是单次的用户问一个问题系统检索一次拿到结果生成回答。这个模式在简单问题上够用但遇到复杂问题就捉襟见肘。比如用户问“对比一下 A 产品和 B 产品在续航和价格上的差异。”这个问题需要检索 A 产品的信息、B 产品的信息然后对比。单次检索很难同时召回两个产品的完整信息因为向量检索倾向于召回和 query 最相似的块而 query 里同时包含 A 和 B检索结果可能偏向其中一个。再比如多跳问题“我们公司去年营收最高的产品线它的主要竞争对手是谁”这个问题需要先查营收最高的产品线再查这个产品线的竞争对手。两步之间有依赖关系单次检索无法完成。4.2 Agentic RAG 的三种典型模式Agentic RAG 的核心思路是让大模型自己决定什么时候检索、检索什么、检索几次。目前我实践过的有三种模式第一种是查询改写。用户的问题可能表述不清先让大模型把问题改写成更适合检索的形式。比如“这个东西怎么弄”改写成“XX 功能的操作步骤”。这个模式成本低效果立竿见影我基本在所有项目里都会加。第二种是多轮检索。大模型先检索一次根据结果判断信息是否足够。如果不够生成新的查询再检索直到信息足够或达到最大轮数。这个模式适合多跳问题但要注意控制轮数一般 3 轮以内否则延迟和成本都会失控。第三种是工具调用。把检索封装成一个工具大模型根据需要调用。除了检索还可以调用计算器、数据库查询、API 等工具。这个模式最灵活但也最复杂适合有明确工具生态的场景。4.3 用 LangGraph 搭一个最小可用的 Agentic RAGLangGraph 是目前搭 Agentic RAG 比较顺手的框架。它的核心概念是状态图每个节点是一个处理步骤边是步骤之间的流转条件。下面是一个最小可用的 Agentic RAG 的状态图设计from langgraph.graph import StateGraph, END from typing import TypedDict, List class RAGState(TypedDict): question: str rewritten_query: str documents: List[str] answer: str retry_count: int def rewrite_query(state: RAGState): # 调用大模型改写查询 rewritten llm.invoke(f改写以下问题以便检索{state[question]}) return {rewritten_query: rewritten} def retrieve(state: RAGState): # 混合检索 docs hybrid_retrieve(state[rewritten_query]) return {documents: docs} def grade_documents(state: RAGState): # 判断检索结果是否足够 if is_sufficient(state[documents], state[question]): return generate elif state[retry_count] 3: return rewrite else: return generate def generate(state: RAGState): answer llm.invoke(build_prompt(state[question], state[documents])) return {answer: answer} graph StateGraph(RAGState) graph.add_node(rewrite, rewrite_query) graph.add_node(retrieve, retrieve) graph.add_node(generate, generate) graph.set_entry_point(rewrite) graph.add_edge(rewrite, retrieve) graph.add_conditional_edges(retrieve, grade_documents, { rewrite: rewrite, generate: generate }) graph.add_edge(generate, END) app graph.compile()这个图的核心是grade_documents这个条件边。它判断检索结果是否足够如果不够就回到 rewrite 节点重新改写查询最多重试 3 次。这个设计让系统有了“自己想办法”的能力而不是一次检索定生死。注意重试次数一定要设上限。我见过有人不设上限结果遇到一个检索不到的问题系统陷入死循环烧了一堆 token。3 次是个比较稳妥的值超过 3 次还检索不到说明知识库里可能真的没有直接让大模型基于已有信息回答或者承认不知道。5. 分水岭四GraphRAG当文档之间的关系比文档本身更重要5.1 向量检索的盲区关系型问题向量检索擅长找“相似的块”但不擅长找“有关联的块”。这两者有什么区别相似是指语义相近关联是指逻辑上有连接。举个例子。一份技术文档里A 模块的说明在第三章B 模块的说明在第七章但 A 和 B 之间有依赖关系A 的配置会影响 B 的行为。用户问“A 和 B 怎么配合使用”向量检索可能召回 A 的块和 B 的块但这两个块里都没有提到对方大模型拿到两个孤立的块很难给出正确的配合方式。这就是向量检索的盲区它能看到点但看不到点之间的线。而很多问题恰恰需要沿着线去找答案。5.2 GraphRAG 的构建流程与成本控制GraphRAG 的思路是先把文档里的实体和关系抽出来构建一个知识图谱然后基于图谱做检索。检索时不仅召回实体本身还召回和实体相关的其他实体沿着关系边扩展。构建流程一般分三步第一步是实体抽取。用大模型从每个块里抽出实体和关系。比如从“A 模块的配置文件在 config 目录下”抽出实体“A 模块”“config 目录”关系“配置文件位于”。这一步的成本最高因为每个块都要调一次大模型。第二步是图谱构建。把抽出的实体和关系去重、合并构建成图。实体作为节点关系作为边。这一步可以用 Neo4j 或者 NetworkX小规模用 NetworkX 就够。第三步是图检索。用户提问时先从问题里识别实体然后在图里找到对应节点沿着边扩展一到两跳把相关的实体和关系作为上下文拼进 Prompt。成本控制是关键。全量构建图谱的成本很高我的做法是分层构建核心文档全量构建边缘文档只构建实体不构建关系或者干脆不构建。另外实体抽取可以用小模型比如 Qwen 的 7B 版本成本比 GPT-4 低一个数量级效果在实体抽取任务上差距不大。5.3 GraphRAG 和向量 RAG 的混合使用GraphRAG 不是替代向量 RAG而是补充。我的做法是两路并行向量检索负责语义召回图检索负责关系召回两路结果融合后重排。这样既能找到相似的块也能找到关联的块。融合策略上我一般给图检索的结果更高的权重因为关系型信息更难获取。但具体权重需要根据场景调没有万能值。建议先各取 10 个候选融合后重排取 5 个看效果再调。实操心得GraphRAG 的构建成本主要在实体抽取。如果预算有限可以先只对高频查询涉及的文档构建图谱低频文档走纯向量检索。这样能用 20% 的成本覆盖 80% 的关系型查询。6. 分水岭五评估体系没有度量就没有优化6.1 为什么你的 RAG 调不动很多人调 RAG 的方式是改一个参数跑几个问题感觉好像好了一点再改一个参数再跑几个问题。这种调法的问题在于没有量化指标全靠感觉。感觉是不可靠的今天觉得好明天可能觉得差而且不同人的感觉不一样。更麻烦的是RAG 系统有多个环节检索、重排、生成每个环节都有参数。改检索参数影响了生成效果你很难判断是检索变好了还是生成变好了。没有评估体系调优就是盲人摸象。6.2 检索评估Hit Rate 和 MRR检索环节的评估相对客观因为我们可以标注“对于这个问题哪些块是相关的”。常用的指标有两个Hit Rate检索结果中是否包含至少一个相关块。这个指标衡量的是“找没找到”。MRRMean Reciprocal Rank相关块的排名倒数的平均值。这个指标衡量的是“相关块排得靠不靠前”。构建评估集的方法是准备 50 到 100 个问题每个问题标注 1 到 3 个相关块。然后跑检索计算 Hit Rate 和 MRR。这两个指标能直观反映检索质量的变化。我一般把 Hit Rate 的目标定在 90% 以上MRR 定在 0.7 以上。低于这个值说明检索环节还有优化空间。6.3 生成评估忠实度和相关性生成环节的评估更主观但也可以用大模型来打分。常用的两个维度忠实度回答是否完全基于检索到的内容有没有编造。这个可以用大模型判断把回答和检索内容一起送进模型问“回答中的每个事实是否都能在检索内容中找到依据”。相关性回答是否切题有没有答非所问。同样可以用大模型打分1 到 5 分。这两个维度可以用 RAGAS 这个框架来自动化它封装了常用的评估指标接入也比较简单。我一般每周跑一次全量评估每次改动后跑一次抽样评估确保改动是正向的。评估维度指标目标值工具检索Hit Rate90%自建脚本检索MRR0.7自建脚本生成忠实度0.85RAGAS生成相关性4.0RAGAS注意评估集要覆盖不同类型的查询包括简单事实查询、多跳查询、对比查询、否定查询等。只测简单查询评估结果会虚高。7. 分水岭六工程化从 Demo 到生产的那段路7.1 延迟优化用户等不了 10 秒Demo 阶段用户问一个问题等 10 秒出答案可以接受。生产环境超过 3 秒用户就开始不耐烦超过 5 秒可能直接关掉。RAG 系统的延迟主要来自三块检索、重排、生成。检索延迟一般在 50 到 200 毫秒向量库的索引类型和硬件决定。重排延迟在 100 到 500 毫秒Cross-Encoder 模型大小决定。生成延迟在 1 到 5 秒大模型和输出长度决定。优化手段上检索可以用 HNSW 索引加速重排可以用更小的模型或者量化生成可以用流式输出让用户先看到部分结果。流式输出对感知延迟的改善非常明显虽然总时间没变但用户看到第一个字的时间从 3 秒降到 1 秒体验完全不一样。7.2 缓存策略别重复回答同一个问题生产环境里用户的问题有大量重复。同样的问题今天问、明天问、不同用户问如果每次都走完整流程浪费资源也浪费时间。缓存是最简单的优化。缓存分两层查询缓存和答案缓存。查询缓存是缓存检索结果同样的问题直接返回缓存的块省掉检索和重排。答案缓存是缓存最终回答同样的问题直接返回省掉整个流程。缓存的失效策略上我一般设 24 小时过期同时监听知识库更新事件有更新就清空相关缓存。缓存键用问题的向量表示而不是原始文本这样语义相同但表述不同的问题也能命中缓存。7.3 监控与告警出问题要能第一时间知道生产系统必须有监控。RAG 系统需要监控的指标包括请求量、延迟分布、错误率、检索命中率、用户反馈。其中用户反馈是最重要的因为其他指标正常不代表用户满意。用户反馈的收集方式一般是在回答下面加“有用/没用”按钮或者让用户直接输入反馈。收集到的反馈要定期分析找出低分回答的共同特征针对性优化。告警方面我一般设三个延迟 P99 超过 5 秒告警、错误率超过 1% 告警、检索命中率低于 80% 告警。告警渠道用企业微信或者钉钉机器人简单直接。实操心得监控不要只看平均值要看 P99。平均值会被大量快速请求拉低掩盖了慢请求的问题。P99 才能反映最差情况而最差情况才是用户真正会遇到的。8. 写在最后一些踩坑之后的体会这套东西我前后折腾了大半年踩过的坑不少。最大的体会是RAG 的优化不是线性的不是每个环节都优化到最好整体效果就最好。有时候检索质量提升了一点但生成环节因为上下文变长反而变差了。所以评估体系一定要先建有了度量才知道改动是正向还是负向。另一个体会是不要追求一步到位。我见过有人一上来就搞 GraphRAG 加 Agentic RAG结果系统复杂到没人能维护效果还不如简单的混合检索。我的建议是分阶段来先把混合检索和重排做好这是性价比最高的优化然后加上下文检索和元数据过滤再考虑 Agentic RAG 和 GraphRAG。每一步都跑评估确认有效再往下走。最后分享一个小技巧如果你的知识库里有大量表格和图片纯文本 RAG 效果会很差。表格可以转成 Markdown 或者 JSON 再切块图片可以用多模态模型生成描述再入库。这块我还在摸索目前表格转 Markdown 的效果比较稳定图片描述的质量参差不齐还在找更好的方案。
延伸阅读

更多相关文章

2026/10/7 2:20:08

Unity PBR渲染全解析:从传统Blinn-Phong到URP/HDRP的实践指南

我最早在Unity项目里被PBR这个词忽悠过一阵。那时查资料,翻到一篇讲"PBR策略路由"的文章,差点以为Unity和路由器有什么合作,后来才反应过来:渲染圈说的PBR,全名是Physically Based Rendering,跟网…

2026/10/7 3:25:11

宠物商城前后端分离实战:SpringBoot+Vue源码解析与运行部署

宠物商城系统前后端分离实战:从跑起来到改得动,一篇讲透这套可直接运行的源码怎么用我见过太多开源项目,标题写得天花乱坠,下载下来跑三天都起不来。但今天要聊的这套宠物商城网站信息管理系统,其源码落地性相当扎实&a…

2026/10/7 3:25:11

Spring Boot集成MongoDB全指南:配置、建模、索引与调优实战

1. 先说结论:为什么要把 MongoDB 接进 Spring Boot最近在折腾一个数据增长比较快的业务模块,关系型数据库在几张表 join 之后越来越吃力,尤其是文档型数据的读写,字段结构还不固定。于是把目光放到了 MongoDB 上,顺手在…

2026/10/7 3:25:11

Ubuntu鼠标闪烁抖动排查全攻略:从驱动到Wayland的完整修复指南

鼠标在 Ubuntu 桌面上闪烁、移动时不停抖动,这问题看着不大,但真的能让人瞬间暴躁。我玩 Linux 桌面这么多年,装过数不清的发行版,在 Ubuntu 20.04.1 上就结结实实被这个问题折磨过两回——一次是刚装完系统进桌面就抖&#xff0c…

2026/10/7 3:25:11

Allegro差分对属性设置:手动与自动的工程决策指南

1. 差分对不是“画两根线”那么简单:为什么属性设置决定信号完整性成败在Allegro PCB Designer里,把一对走线标上“DIFF_PAIR”四个字母,远不等于完成了差分对设计。我见过太多项目在高速信号测试阶段突然翻车——眼图闭合、串扰超标、EMI辐射…

2026/10/7 3:25:11

Spark Streaming实时模式深度解析:从微批到持续处理的架构与实践

1. Real-time Mode是什么:先分清两种“实时”在聊Spark Streaming的实时模式之前,必须先把一个被用滥的词挑明白——“实时”。很多团队跟我聊需求时张口就是“我们要实时数仓”,结果一细问,T1报表就算实时。真正做流计算的工程师…

2026/10/7 3:20:11

云上SOC建设实战:从被动响应到主动威胁狩猎的转型之道

云上安全运营中心(SOC)建设:从被动防御到主动狩猎1. 项目概述1.1 我为什么想写这个话题做云上安全这几年,我最深的一个感受是:很多团队的安全运营还停留在"告警响了才动"的阶段。告警中心积压了几千条未处置的告警,SIEM…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/6 4:01:51

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/6 17:46:51

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

ESP32免重刷固件:浏览器直接修改NVS键值实现WiFi配置更新

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

SAP HANA查询结果导出CSV:避开乱码、性能与权限的实用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:05:03

数字后端Placement阶段Density与Congestion控制实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑