从RAG到Agentic RAG:知识检索管道原理与实战优化
1. 为什么 Agent 的知识不能只靠"记性":记忆体系与 RAG 的定位
AI Agent 系列写到第四篇,前面几篇我们一步步把 Agent 的骨架搭了起来,让它有了目标拆解的能力、调用工具的能力,也让它知道怎么跟外部系统打交道。但我发现一个很现实的问题:很多人在本地跑通了一个 Agent,问它通用知识它头头是道,一问你自己公司的业务细节,它就立刻卡壳了。比如你问"我们内部 OA 系统的加班审批流程是什么",它要么胡编一通,要么直接告诉你它不知道。
这个问题的本质,在于 Agent 的知识来源。我们今天要聊的知识获取管道——RAG,解决的就是这个事。
RAG,Retrieval-Augmented Generation,检索增强生成。名字听起来学术,其实理解起来很简单:它就是在模型回答之前,先帮模型"查资料",然后把查到的资料和用户的问题一起交给模型,让模型基于资料作答。这让模型不再是凭空硬答,而是有一个"随身知识库"可以随时翻阅。这篇文章适合两类人:一类是刚学完 Agent 基础、准备给 Agent 接私有数据的开发者;另一类是已经在做 RAG 但经常遇到"检索结果不对""回答幻觉明显"这些问题,想系统梳理一遍原理和排查思路的工程师。
在展开之前,先想清楚一个前提:Agent 到底需要几种记忆?
我把 Agent 的记忆体系分成四个层次,理解了这个,你就能明白 RAG 在整个架构里的位置。
| 记忆层次 | 载体 | 特点 | 典型问题 |
|---|---|---|---|
| 参数记忆 | 模型权重 | 训练时固化,通用知识 | 有截止日期,无法更新 |
| 上下文记忆 | 对话窗口 | 当前会话内的信息 | 长度有限,会话结束即失 |
| 外部知识库 | 向量数据库 | 动态更新,可追溯 | 需要检索,有召回失败风险 |
| 外部工具数据 | API / 数据库 | 实时查询,结构化 | 依赖接口,需要工具调用能力 |
这里的第三层——外部知识库,就是 RAG 的用武之地。为什么不能只靠第一层和第二层?原因很简单:参数记忆是模型在训练时灌进去的,它有明确的截止时间,而且永远学不会只有你公司才有的私有文档;上下文记忆虽然灵活,但受限于窗口长度,你不可能把一个几千页的运维手册全塞进对话框里。
RAG 的定位,就是给 Agent 接一条通往任意规模知识库的标准化管道。它有三个其他方案替代不了的价值:知识实时可更新,你改了文档它就跟着变;回答有据可循,可以回溯到原文片段;私有数据不出内网,敏感信息不必送进模型训练。这三条,恰好是企业在实际落地 Agent 时最先提出的三个硬性要求。
2. 三段式管道拆解:索引、检索、生成各自负责什么
RAG 被称为"管道",是因为它的数据流是固定方向的,每一段都有明确的输入输出。整条管道分三段:索引、检索、生成。我在刚接触这个概念时总觉得它玄乎,后来用一个生活化的类比就串起来了:这就像一个刚入职的实习生,你不可能让他直接回答老板的问题之前,就把公司所有文档背下来。更靠谱的做法是,先请一个图书管理员把所有文档整理好放进档案馆,老板问问题的时候,管理员去档案馆把最相关的几页文件挑出来,实习生再基于这几页文件写答复。
这个类比里的"整理归档"就是索引,"挑文件"就是检索,"写答复"就是生成。下面一段一段拆开看。
2.1 索引:把杂乱文档变成可检索的向量数据库
索引阶段,做的是从原始文档到向量库的加工。流程固定在四步:清洗、切分、向量化、入库。
清洗是很多人最容易跳过的一步,但恰恰是最影响最终效果的一步。PDF 转出来的文本经常带页眉页脚、目录跳转、乱码,Word 文档里可能有批注和修订痕迹,网页抓下来的内容带着 HTML 标签和导航链接。这些噪声如果不清掉,后面切出来的 chunk 里就会混着大量废话,检索出来的片段质量自然上不去。我的习惯是,先用工具把文档转成纯文本,再写一个清洗脚本做三件事:去空白字符、去重复的页眉页脚、把全角符号统一成半角。这一步花的时间,通常决定了整个 RAG 的天花板。
切分,是把长文档切成一个个适合检索和生成的小块。为什么要切?两个原因。第一,向量检索的单元是小块而不是整篇文档,如果拿整篇文档去比对相似度,命中精度会非常差;第二,模型生成时能接收的上下文有限,你不可能把一本手册全塞进去,只能挑最相关的几块。
向量化,就是把每个切好的文本块用 Embedding 模型转成向量。这里的核心原理是:模型把一段文字映射到高维空间里的一个点,语义相近的文字在高维空间里距离也近。比如"加班审批流程"和"如何提交加班申请"意思接近,它们的向量在空间里就挨得很近;而"加班审批流程"和"附近的外卖推荐"就离得很远。这一步产出的向量要存进向量数据库,常见的有 Chroma、Milvus、Qdrant、pgvector,还有国内用的比较多的 Elasticsearch 的向量检索能力。
入库阶段有个经常被忽略的点:入库时不只要存向量和文本,还要把元数据一同写进去。文档来源、文档名称、章节路径、更新时间,这些字段在后续的过滤检索和回答溯源里会派上大用场。我在做知识库时一定会带上这些元数据,宁可多存几个字段,也不要等到排查问题时发现无从溯源。
2.2 检索:把用户问题变成查询,从向量库里捞出相关内容
检索阶段,做的事情是把用户的问题走一遍同样的向量化流程,然后用相似度算法把知识库里最相关的若干文本块捞出来。
计算相似度最常用的算法是余弦相似度。两个向量的夹角越小,余弦值越接近 1,表示语义越接近。实际应用时,向量数据库底层会做近似最近邻搜索(ANN),不会真的拿你的查询向量和库里所有向量逐个算一遍,那样大数据量下性能扛不住。ANN 算法(比如 HNSW、IVF)通过建立索引结构,把搜索范围大幅缩小,在牺牲很小精度的前提下把速度提上去。
检索结果的排序依据是相似度分数,但我们通常不是只看排名第一的那一条,而是取 Top-K 条一起返回。K 的值一般取 3 到 10 之间,具体要根据 chunk 的大小和模型上下文长度来调。这里有一个常见的认知误区:不是 K 越大越好。K 太大,会把一堆相关性一般的片段塞进上下文,反而稀释了真正有用的信息,甚至导致模型"看花了眼",生成质量下降。K 太小,又可能漏掉关键内容。我在实际项目中通常先把 K 设成 5,然后根据测试集的命中率来调整。
2.3 生成:让模型"拿着资料"回答问题
生成阶段,是把检索到的文本块和用户原始问题拼装成一个带指令的 Prompt,交给大模型生成最终回答。这一步的关键,不是模型本身,而是你如何设计这个 Prompt。
一个合格的 RAG 生成 Prompt 至少要有三部分:系统角色约束、检索到的上下文、用户问题。系统角色约束里必须写明一句话——"请仅依据下面提供的上下文内容回答用户问题,如果上下文中没有相关内容,请直接回答'未在资料中找到相关信息',不要编造。"这句话看着简单,却直接决定了回答的忠实度,不加这句话,模型八成会自己脑补。
我在实际工作中还会加两个额外要求:一是回答中涉及的事实点要尽量标注出来自哪段上下文;二是如果有多段上下文互相矛盾,要如实说明"资料中存在不一致的描述"。这两条能极大缓解 RAG 系统的幻觉问题。
到这里,三段式管道的主干就清楚了。但具体落地时你会发现,每一段都有大量可以优化的细节,而其中最影响最终体验的,是中间那段检索的质量。下一章专门来聊这件事。
3. 检索质量才是真天花板:chunk、Embedding 与混合检索的取舍
很多刚上手 RAG 的人会有一种错觉:只要把文档切一切、塞进向量库,然后调接口检索,系统就能工作。实测下来,系统确实"能工作"——但回答质量经常让人血压升高。问题几乎都出在检索环节。检索环节决定了模型能看到什么,而模型只能基于看到的东西作答。所以,把检索质量打磨好,是整个 RAG 系统投入产出比最高的事情。
这一节我把检索质量相关的三个核心决策点逐一展开:chunk 怎么切、Embedding 怎么选、怎么组合检索策略。
3.1 chunk 切分:文本块的粒度决定召回精度
chunk 切分在工程上是最容易被随手糊弄过去的一环,但它的影响极其深远。切得太小,每个块语义不完整,检索容易碎片化;切得太大,块里信息太杂,相似度计算被大量无关内容干扰,关键信息反而被淹没。
我实测过的切分策略有四种,按推荐程度排序:
- 固定长度切分(最基础):按 token 数或字符数硬切,比如每 512 个 token 切一块,相邻块之间保留 50 个 token 的重叠。优点是实现简单,缺点是容易在句子中间切断,最常见的就是把一个完整的表格数据从中间劈开。
- 递归字符切分(推荐):先按段落分隔符切,如果切出来的块还是太大,再按句子边界切,最后按单词/字切。这种策略能在尽量保持语义完整的前提下控制块的大小,是多数项目里性价比最高的方案。
- 结构化切分(进阶):针对 Markdown、HTML、代码这类有明确层级结构的文本,按照标题层级来切分。比如先按一级标题分章节,再按二级标题分子节。这样切出来的每个块都有清晰的上下文,检索结果天生就带着章节归属。
- 语义切分(最重):用模型来判断句子间的语义边界,在语义断裂处切分。效果最好,但计算成本高,适合预算充足、对精度要求极高的场景。
chunk 大小怎么定?我的建议是:先按语言来粗定。中文场景下,每块 300~500 个汉字是一个不错的起点;英文场景下,按 200~400 个 token 来。然后根据实际检索效果回调,不要一开始就在参数上纠结,先把管道跑通,再拿真实问题去测。
3.2 Embedding 选型:没有最好的模型,只有最合适的场景
Embedding 模型直接决定了"语义相近"这件事在你这套系统里成立不成立。选错模型,后面所有的检索优化都事倍功半。
我列一个当前我用过的几类方案对比:
| 方案 | 维度 | 适合场景 | 部署方式 | 我的评价 |
|---|---|---|---|---|
| OpenAI text-embedding-3-large | 3072(可降维) | 通用英文效果极佳,多语种可用 | API 调用 | 效果好,但数据要出网,不适合私有化场景 |
| 开源 BGE 系列(如 bge-large-zh) | 1024 | 中文效果好,支持多语言 | 本地部署 | 私有化首选,国产硬件上也跑得动 |
| text2vec-large-chinese | 1024 | 中文语义匹配老牌选手 | 本地部署 | 中文效果扎实,迭代相对慢 |
| M3E 系列 | 1024 | 中文场景,训练数据覆盖多种中文任务 | 本地部署 | 轻量,适合快速验证 |
选型时除了看模型效果,还要看三个工程因素:向量维度会影响存储成本和检索速度,维度不是越高越好;推理速度决定你的索引和检索的吞吐;许可证和部署环境决定能不能落地。我的经验是,一开始别急着上最强模型,先用一个开源部署难度低的模型把整套管道跑通,等业务验证了 RAG 确实有用,再回来评估要不要换更好的模型。
3.3 混合检索:向量检索不是万能的
如果说前面两小节是地基,那这一小节是真正的分水岭——很多人做到向量检索就觉得 RAG 完工了,直到遇到一个尴尬场景:用户问"KB-2041 型号的说明书有没有",向量检索召回的结果乱七八糟。
为什么?因为向量检索擅长语义匹配,但不擅长精确匹配。像产品型号、工单编号、设备序列号这种"编号类"文本,语义相近但字面完全不同的句子检索出来的是两回事。这时候,传统的关键词检索反而更可靠——关键词检索就是你在搜索框里输入"KB-2041",系统按字面匹配去搜,编号这种特征能精确命中。
解决方案就是混合检索:同时跑关键词检索和向量检索,再把两路结果融合排序。关键词检索我用 BM25 算法,它基于词频和文档长度做加权打分;向量检索走余弦相似度。融合的方式最简单的有加权求和,比如向量分数占 0.7、BM25 分数占 0.3,也可以先各取前 20 条再做交叉重排。
实测下来,混合检索比纯向量检索的命中率有明显提升,尤其是在知识库里混着大量编号、代码、人名、地名这类实体信息时。加了关键词检索之后,还有一个额外的好处:用户可以靠"记不全但记得片段的词"找回内容,体验比纯向量检索亲切得多。
3.4 重排:把分数可信度拉回来的最后一道闸
混合检索拿到的是一个候选集合,通常有几十条。直接把这几十条全塞给模型显然不行,上下文会被撑爆。这时候需要重排(Rerank)——用一个更精细的模型把候选结果按相关性精排,只保留最前面的几条。
重排模型的典型实现是 Cross-Encoder 结构:它不是把问题和文档分别编码成向量算相似度,而是把"问题 + 文档"拼接成一句完整文本,和模型一起编码,直接输出一个相关性分数。这个过程的精度比向量相似度更高,但计算量大,所以把它放在第一道粗筛之后、精排候选集规模只有几十条时,这个开销完全可以接受。
用重排模型之前,我建议先调 beam 数量:第一道检索尽量多召回,比如 Top 50,让重排模型在足够大的候选池里挑,然后再取重排后的前 3 到 5 条进上下文。我见过不少人直接把 Top 5 丢给模型,遇到长尾问题答不好,加了一道重排后效果立竿见影。
4. 从单次查资料到带规划地查资料:Agentic RAG 的方向
前面讲的三段式管道,是 RAG 的经典形态。它在多数问答场景里足够好用,但当你把 RAG 接入 AI Agent 之后,会发现一个更况味的问题:用户的问题常常是复杂的、多跳的,一轮"查资料—回答"根本搞不定。
举一个我真实遇到过的例子。用户问:"我们二季度的售后工单为什么比一季度高这么多,主要是什么产品引起的?"这个问题要回答,至少需要三步:先检索"二季度售后工单统计",再检索"主要产品销量对比",还可能得检索"具体产品已知的售后问题记录"。一辆经典的 RAG 管道,拿着这个 query 去检索,大概率只能返回其中最相关的某个片段的答案,而凑不齐完整的因果链。
这时候,RAG 就需要升级成 Agentic RAG——让 RAG 管道具备"规划—执行—再规划"的能力。说白了,就是把检索本身当成 Agent 可以调用的工具,让 Agent 自己决定什么时候查、查什么、查几次,而不再是从头到尾只做一次固定检索。我在实际项目中,梳理出了 Agentic RAG 的四个实用方向:
- 检索路由:让 Agent 先判断,用户这个问题到底需不需要走知识库。有些问题靠模型自身知识就能回答(比如"什么是递归算法"),有些问题必须走知识库(比如"我们公司的报销标准是什么")。路由可以是一个轻量分类器,也可以由 Agent 根据 query 与知识库摘要的描述来决定。这一步能省掉大量无效检索,直接提升系统速度。
- 多跳查询拆解:把用户的复杂问题拆成多个子查询,逐个子查询走检索,然后再把多轮的检索结果汇总起来生成最终答案。刚才提到的售后工单例子,就是典型的拆解场景。拆解工作可以由 Agent 自主完成,也可以预设几个固定的子问题模板,我在项目里更推荐后者,可控性高得多。
- 动态改写查询:第一轮检索结果不满意时,Agent 分析哪些关键词没命中,改写 query 重新检索,形成一个"检索—评估—再检索"的循环。这比前端强制要求用户把问题问清楚要聪明得多。
- 多路知识源配合:知识库不止向量库一个来源,还可以挂关系型数据库、API 接口、Wiki、图谱。Agent 可以根据问题的性质,决定从哪个管道获取知识。比如查结构化财务数据就查数据库,查病历就查内部 Wiki,查产品关联关系就查知识图谱。
最后一个方向里,我想多说一句知识图谱和 Ontology(本体)的应用。纯向量检索很难回答"哪些产品跟哪些业务线有关系"这类关系型问题,因为关系是显式的、结构化的。把知识库里的实体和关系抽取出来建成本体,Agent 就能完成"在知识之间跳跃"的推理,这也正是最近一段时间 GraphRAG、Ontology RAG 这类方案逐渐被讨论的原因。它不是取代向量 RAG,而是在向量 RAG 之上补一层关系推理能力,两者是配合关系,不是替代关系。
做 Agentic RAG 时有一个工程上容易失控的点:循环检索的次数如果没有上限,single query 会在多个子查询之间来回跳,响应时间成倍拉长。我的做法是给 Agent 的检索工具加两个约束:单次任务最多执行 N 次检索(N 通常设为 3~5),每次检索必须携带"目标说明"字段,Agent 在日志里能看清每次检索是为了回答问题的哪一部分。加了这两个约束,整个链路的可观测性会强很多,排查问题也快得多。
5. 我踩过的坑:检索不到、答非所问、幻觉的排查链路
这一篇最后,我想把实践中最容易踩的坑汇总一下,附带完整的排查思路。这些坑不一定都能从教科书里看到,但只要你做 RAG,大概率会碰到其中一两个。
5.1 坑一:调了检索接口,结果永远是相似度最高的那几段,但答案就是不对
这个坑的典型表现:你问"加班审批的流程",系统返回的片段确实聊到了"加班",但内容其实是"加班补贴的发放标准",答非所问。
排查链路从数据源头开始:第一步把命中的 chunk 打印出来,看文本片段本身是否的确包含了问题的答案。如果原文里这块内容本身就答不对题,问题出在检索召回阶段,是 chunk 粒度和切分方式不合适。如果原文里包含答案但向量检索没召回,那就是切分时关键句被拆到了两个块里,或者 embedding 模型没能理解同义改写。
我踩过之后学到的经验是:不要只调 Top-K,要先去啃切分策略。把"加班审批流程"相关的内容手工标出来,检查它在库里被切成了几块、每块是否完整、能否被检索回来。切分问题不解决,后面的重排和调参都是白费。
5.2 坑二:检索返回了一堆内容,但答案里还是出现原文没有的信息
这比检索不到更危险——模型开始脑补了。这种情况十有八九是生成 Prompt 里的约束不够硬。我见过很多项目的 Prompt 写的是"请参考以下资料回答问题",这个"参考"字眼太软了,模型会把它当成可用可不用的提示。
排查思路:第一步,检查 Prompt 里的系统指令是否明确写死了"只能基于给定上下文作答";第二步,看上下文里是否混入了历史对话残留,导致模型分不清哪个是资料、哪个是闲聊;第三步,检查检索结果里是否有矛盾片段,模型被冲突信息逼急了会自己编一个"合理"答案。
我自己的对策是:在生成阶段做"指导性约束 + 检查钩子"双重保险。约束就是那几句强指令,检查钩子是要求模型回答完以后,输出它右侧引用的资料的索引编号。然后我用一个小脚本比对:回答里出现的引文编号,是否全部都存在于本次上下文里。不在?那就是模型编了,直接拦截。
5.3 坑三:知识库数据很干净,但总有一些问题怎么检索都召回失败
这种情况我称之为"长尾撞脸",最常见于用户用一种模糊的、口语化的方式提问,而知识库里用的是严谨的书面术语。比如用户问"打印机老卡纸怎么办",知识库里写的是"设备进纸路径堵塞故障处理"。
排查链路:第一步,把用户的 query 和库里的文档做同义改写比对,确认是术语不匹配;第二步,检查是否需要引入同义词扩展,在检索前先把 query 里口语化词汇替换成知识库里的书面语;第三步,检查是否需要给知识库里的核心文档提前打标签,或者用混合检索的 BM25 部分补充精确匹配。
这里有一条实打实的捷径:把历史问题日志里被判定为"未命中"的 query 收集起来,定期用它们回放测试检索效果,并针对高频未命中 query 手工补标签或者补几条标准 Q&A 进库。这是 RAG 系统上线后最实用的一种迭代方式,比反复调 embedding 模型参数见效快得多。
5.4 坑四:本地部署,文档几万篇之后,检索越来越慢
这个坑很多人是等上线后才发现的:文档量一上来,向量检索延迟从 100ms 涨到 2000ms,用户体验直线下降。
排查链路从数据库的索引类型看起:第一步,确认向量索引用的是 HNSW 还是 IVF,普通暴力扫描肯定扛不住;第二步,检查向量维度是否有必要用那么高,有时候从 3072 维降到 1024 维速度快五倍,精度损失却很小;第三步,看看是不是可以把知识库按业务域拆分出多个 collection,在做检索时先按业务域路由一次,再进小库检索。
在这个问题上我的实测结论是:几万篇文档级别,本地部署的 Milvus 配合 HNSW 索引完全能支撑 500ms 内的检索,如果远慢于这个值,优先排查索引参数和查询调用的重复向量化。另外一个容易被忽略的小优化:知识库更新时只对新文档做增量向量化,不要每次全量重跑。
5.5 怎么系统性地评估一套 RAG 效果:hit rate 和 MRR
排查坑的前提,是你得先有一种客观手段衡量 RAG 的好坏。我强烈建议每个 RAG 项目上线前建立一套小规模的评测集。不用很多,50 到 100 条真实问题和对应的标准答案片段就够。
评测指标最核心的有两个:Hit Rate(命中率)和 MRR(Mean Reciprocal Rank)。Hit Rate 很简单,就是所有问题里,正确答案出现在检索结果 Top-K 中的比例。MRR 会进一步惩罚"答案排在越后面"的情况——如果标准答案排在第一位,贡献 1 分;排在第二位贡献 1/2;排在第三位贡献 1/3,取所有问题的平均值。这两个指标能帮你快速判断:改动切分策略后,检索质量到底是提升还是退步了。
我实际操作时有个习惯化的流程,在这里分享给各位:每次调完参数,固定评测集跑一遍,记录 Hit Rate 和 MRR 两个数;同时把答错的案例单独摘出来看——分析每个答错案例属于上面哪一类坑,然后按比例排优先级处理。不基于评测指标去调参,就是在碰运气;不带案例分析的评测,调参容易调出自欺欺人的结果。这两句话是我做了几个 RAG 项目之后总结出来的最核心心得。
最后再分享一个小技巧:如果你要用 RAG 做知识库问答,还得记住知识获取管道之外的一条铁律——永远保留"查不到就直说"的出口。给模型在 Prompt 里写清楚"未找到相关内容时如何应对",让系统敢于承认不知道,比强行给答案要更得人心。把这一步做扎实了,再谈 RAG 的进阶玩法,地基才算真正稳了。