AI Agent知识管道RAG:原理、实现与工程踩坑

发布时间:2026/11/4 18:22:22
AI Agent知识管道RAG:原理、实现与工程踩坑

大家都知道,一个只会闲聊的 AI Agent 是没有太大价值的。要想让 Agent 真正帮我们干活,它必须能获取到准确、及时、可验证的外部知识。这一篇我要聊的,就是 AI Agent 的知识获取管道,也就是RAG(Retrieval-Augmented Generation,检索增强生成)。这是我对 Agent 系列整理的第四篇,前几篇讲了 Agent 的基本架构、规划能力、工具调用,这一篇集中解决一个核心问题:Agent 的知识从哪里来?尤其是面对私有文档、海量文本、实时更新的数据时,怎么让模型"知道"它原本不知道的事情。我会从原理、实现链路、踩坑经验三个层面展开,适合已经跑通 Agent Hello World、准备把 Agent 接入真实业务数据的读者。

我自己在搭建 Agent 的过程中,最先遇到的就是"知识割裂"问题:模型本身很强,但一问到我们团队内部的接口文档、项目规范、历史决策记录,它就完全哑火。后来我意识到,不是模型不行,是根本没给它接上知识管道。这篇就当作我的一次完整复盘,RAG 基础该补的细节,我会一次性写透。

1. 知识割裂是 Agent 落地的第一道坎:先弄清楚 RAG 在解决什么问题

我们先从根上想一个事:一个基于大语言模型的 Agent,它天生知道的只有训练数据里的知识。训练数据再多,也覆盖不了你们公司的私有文档,覆盖不了昨天刚发生的线上故障记录,覆盖不了某个特定业务系统里的实时字典。参数量再大的模型,也会在这三类问题上表现得像个刚入职的新人。这就是 Agent 落地时一定会撞上的墙——模型的知识边界,不等于业务需要的知识边界。

1.1 三种知识缺口的典型形态

我归纳了一下,实际业务里 Agent 面临的知识缺口大致有三种形态:

  • 私有知识封闭:公司内部的项目方案、接口文档、客户反馈、规章制度。这些内容从来不会出现在大模型的预训练语料里,Agent 想答也答不出来。
  • 时间维度滞后:模型训练数据是有截止日期的。哪怕是最新的模型,对一周前发布的政策、三天前更新的产品价格、昨天刚出的竞品动态,都一无所知。
  • 上下文窗口受限:即便文档可以被塞进上下文,但 Agent 单次对话的上下文窗口是有限的。一个几百页的 PDF,不可能全量进入模型视野,强行塞进去,成本先爆掉,后续响应速度也会拖垮整个交互体验。

这三种形态统一指向一个答案:Agent 需要一套机制,在每次被提问的时候,动态地把"与当前问题最相关的外部知识"抽取出来,注入到生成过程中。这就是 RAG 的核心价值。

1.2 RAG 在 Agent 架构中的真实位置

很多人以为 RAG 只是一个"检索文档再加到 prompt 里"的小技巧,实际远不止。在 AI Agent 的完整架构里,RAG 属于知识层,和 Agent 的记忆层、工具层并列,专门负责供给稳定的外部事实。

我画过一张非常朴素的逻辑图来理解这几层的关系,但这里我们不用图表,用一段描述就能说清楚:Agent 接收用户问题后,先经过意图识别和任务规划,如果规划结果判断需要外部知识,就会走 RAG 管道——先对问题做查询改写,再去检索知识库,拿到候选文档片段,最后把片段和原始问题一起交给大模型生成答案。整个过程对用户是透明的,用户感知到的只是"这个 Agent 好像什么都知道"。这套机制是 Agent 通往真实业务场景的必要基础设施,没有它,Agent 就是个花架子。

1.3 为什么不能直接微调模型解决

有读者会问:既然模型不知道私有知识,为什么不直接微调一个新模型?我的看法是,微调和 RAG 解决的不是一个问题。微调适合改变模型的行为风格和固定的技能模式,比如让模型学会特定的输出格式、特定的语气、特定的工具调用习惯。但微调不适合承载高频更新的事实性知识,原因很简单:

  • 微调一次成本高、周期长,昨天改的文档,今天微调,模型明天才能上线,时效性完全跟不上;
  • 微调后的模型对具体事实的掌握仍然不可控,它可能记住某一段文档内容,也可能"记住"出一个幻觉版本;
  • 多个业务的私有知识全部压进一个模型里,隔离性、权限控制都变得很难做。

RAG 恰好是反过来的思路:知识不往模型参数里塞,而是放在外部的文档库里,使用时临时检索。文档更新,检索结果就更新,成本低、速度快、权限可以在检索层精细控制。理解了这个差异,你就能明白为什么主流 Agent 架构里,RAG 几乎是标配。

2. 离线管道:把纯文本变成可检索的向量索引,核心就三步

RAG 的完整链路可以分为"离线建库"和"在线检索"两段。离线这一段的目标很纯粹:把一堆文档(PDF、Markdown、Word、HTML、数据库里的文本字段)转化为一种适合快速检索的结构化形式。其中最核心的数据结构就是向量索引。很多初学者一上来就被各种框架绕晕,我建议先把手写的三步走通:文档加载与清洗、文本切分、向量化与建索引。

2.1 文档加载:数据清洗比你想的重要

文档加载这一步听起来没技术含量,实际坑最多。我用一个真实案例解释:有一次我把一个团队的运维手册导进来,文本里大量含表格、代码块、加粗标记、折叠块,直接用现成解析器扒出来的 Markdown,上下文全是乱掉的表格分隔符和重复的锚点链接。把这些垃圾片段向量化之后,检索出来的内容自然也是垃圾。

因此,文档加载阶段必须要做三件事:

  • 解析格式:PDF 用 PyMuPDF 或 pdfplumber 抽取文本;Word 文档用 python-docx;HTML 先转纯文本再清洗标签。不要迷信任何单一工具,所有解析器都会在复杂版式上出错,务必抽查样本。
  • 去噪:去掉页眉页脚、重复导航、脚本残留标签、"阅读原文"之类的营销尾巴,这些内容会污染向量空间。
  • 保留元数据:原始文件路径、页码、章节标题、更新时间都要保留,这些内容本身可以被向量化,也可以作为检索后的展示信息,后面做引用溯源全靠它。

如果你想先跑通流程,用现成的框架会快很多。LangChain 里的DirectoryLoader、LlamaIndex 里的SimpleDirectoryReader都能快速读取整个目录,但请记住:框架只解决格式读取,不解决清洗问题。我个人的习惯是写一个十几行的清洗函数,把无意义的空白行、重复字符串、异常编码全部过滤一遍,这一步能明显提升后续检索命中率。

2.2 文本切分:chunk 大小直接决定检索精度

文本切分可能是 RAG 里"性价比最高"的环节——它代码量最少,但对最终效果的影响极大。切分的本质是决定检索的最小单位:用户检索时,我们召回的不是整篇文档,而是其中一个片段,这个片段的大小和边界,直接影响后续生成的上下文质量。

切分策略常见的有三种:

  1. 固定长度切分:按字符数或 token 数硬切,比如每 500 个 token 一段,相邻段之间留 50 个 token 的重叠区间。这是最朴素的做法,实现简单,但会无脑切断语义完整的段落和列表,在代码文档上尤其灾难。
  2. 递归字符切分:按优先级依次尝试按段落标记(\n\n)、换行符、句号、空格逐层切分,尽量保住语义边界。LangChain 的RecursiveCharacterTextSplitter就是这个思路,也是我日常用得最多的方式。
  3. 语义切分:利用 embedding 模型计算相邻句子的语义相似度,在相似度明显下降的地方断开。这种方法切出来的 chunk 质量最高,但计算成本也高。LlamaIndex 的SemanticSplitterNodeParser能实现,前期探索可用,生产环境要评估性能。

关于 chunk 大小的经验值,我实测下来:面向一般技术文档的场景,chunk 大小在 300~500 token 之间,重叠区间 50~100 token,效果比较稳。太小了检索结果太碎,模型难以理解完整上下文;太大了检索结果包含太多无关内容,不仅浪费窗口,还会稀释相关性。这里给出一段我常用的 LangChain 切分配置,你可以直接抄:

from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=400, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""], ) chunks = splitter.split_text(clean_text)

有一点必须强调:切分参数是"超参数",不是"正确答案"。不同文档类型有不同最优值。代码类文档我倾向用更小的 chunk,配合代码语义感知的切分器;法律合同这类长段落文本反而适合更大的 chunk。一定要在自己的数据上做评测,而不是抄别人的参数。

2.3 向量化与索引:Embedding 选型的门道

文本切好之后,下一步就是把每个 chunk 转换成一个向量。这里核心是选 embedding 模型。市面上的选择很多:OpenAI 的text-embedding-3-small/text-embedding-3-large质量稳定但数据要出境;开源的BAAI/bge-large-zh-v1.5、m3e-base在中文场景表现不错,能本地部署;还有一些新的国产商用接口不限并发。

我在中文技术文档场景实测下来,bge 系列中文表现不输甚至超过同尺寸的通用模型,关键的是可以私有化部署,数据不用出境。如果你做的是纯中文的私有知识库,优先考虑本地部署的 embedding 模型,既安全又能省后续接口费用。

向量化完成后,需要一个向量数据库做索引和检索。这一步选型的核心指标有三个:检索质量、写入速度、生态成熟度。我整理了一个参考表格:

方案适合场景优点注意事项
FAISS本地原型、单机 demo部署简单、检索性能高、无需额外服务不支持服务化,数据量大了需额外做持久化方案
Chroma单机小项目、快速验证轻量、API 友好分布式能力弱,不适合大规模生产
Milvus生产级大规模检索分布式、支持多种索引、生态完善部署运维成本稍高
Qdrant生产级中等规模Rust 编写、性能好、过滤能力丰富需要独立部署
pgvector已有 PostgreSQL 的团队直接在原库中做向量字段,不用引入新基础设施检索量极大时性能不如专业向量库

我自己早期直接用了 FAISS 本地跑通整个链路,好处是完全不依赖外部服务,适合学习和调试。后来要给团队做共享知识库,改成了 Milvus,支持并发查询和权限过滤,稳定性和可维护性都上一个台阶。我的建议是:学习阶段别纠结选型,先拿 FAISS 把链路跑通,再评估是否需要迁移到正式向量库。

3. 查询阶段的检索设计:召回率、相关性、排序的三角平衡

离线管道建好之后,RAG 的在线部分——"给一个问题,找回最相关的文档片段"——正式登场。这一段的成败决定生成质量的上限。我一直认为,RAG 的功夫主要花在检索端,而不是生成端。检索出来的东西不对,后面 prompt 写得再花哨也没用。

3.1 最基本的检索流程:向量相似度计算

在线检索的第一步,是用同一个 embedding 模型,把用户的问题也转成一个向量。然后在向量数据库里找到与这个查询向量最相似的若干个文档向量,按相似度排序,返回 Top-K 条结果。

这里涉及的相似度度量通常有三种:

  • 余弦相似度:衡量两个向量在方向上的重合程度,对向量的模长不敏感,是最常用的度量方式。
  • 点积相似度:在向量已归一化的情况下,点积和余弦相似度等价,但点积对模长敏感,更适合某些特殊 embedding 模型的打分。
  • 欧氏距离:衡量多维空间里的绝对距离,数值越小越相似,很多向量库默认转换成相似度打分时会取负。

实际操作里,大部分向量数据库默认用余弦相似度,一般情况下不用额外调整。但要注意:相似度分数本身不能跨模型比较,也不能绝对化理解为"置信度"。不同 embedding 模型产出的分数分布差异很大,有的模型相似度 0.6 就算高,有的模型 0.8 还很普通。你需要在自己的数据上做校准,别被分数绝对值误导。

3.2 多层检索策略:从裸向量到混合召回

只用向量检索的 RAG,在复杂业务场景下会暴露两个问题:

  • 精确词条匹配失效:向量检索对"语义相关但用词完全不同"的文本表现好,但对"商品编号、订单号、型号 ID"这类必须精确匹配的查询反而可能出错。向量空间里,"ABC-123"和"ABC-124"的距离可能非常近,但它们是两个完全不同的商品。
  • 关键词漏召回:有些文档里出现某个专业缩写时,embedding 有可能把包含该缩写但语义侧重点不同的文档排在前面,导致真正关键的精确片段没被召回。

解决方案是引入混合检索(Hybrid Search):同时使用稀疏检索(关键词匹配,如 BM25)和稠密检索(向量相似度),将两者的结果做融合。BM25 擅长精确词匹配,向量检索擅长语义泛化,融合之后能覆盖的场景远多于单一检索。

我用一个实际例子说明:有个知识库里存了一批银行产品的介绍文档,用户拿"利率最低的三年期定存产品"来问,向量检索会把"定存产品利率比较"的段落排在前面,但如果某个段落确实提到了"三年期利率 2.6%"却没出现"定存"也没出现"最低",BM25 可能完全召不回它。反而 BM25 能精准匹配"三年期"和"利率"这两个词。两者融合后,模型既能看到精确匹配的文档,又能看到语义相关的泛化文档,生成质量自然更好。

在实现融合时,最常用的算法是RRF(Reciprocal Rank Fusion,倒数排名融合):对多种检索结果取排名,按公式score = Σ 1/(k + rank)分配得分。k一般取 60。这种方法只依赖排名不依赖原始相似度分数,规避了不同检索器分数尺度不一致的问题。还有个关键技巧:融合前对多种检索结果做去重,同一文档既被语义检索召回又被关键词检索召回时,得分会叠加,这正是我们想要的效果。

3.3 召回之后的精排:Rerank 的价值

Top-K 召回只是粗筛,真正决定最终生成质量的,是粗筛之后的精排(Rerank)。向量检索的目标是召回"可能相关"的文档,但向量相似度高不等于"确实能回答当前问题"。用一个独立的 rerank 模型,把召回结果逐条和原始问题一起打分,可以得到比向量相似度更精准的相关性排序。

Rerank 模型和 embedding 模型的核心区别在于:embedding 模型需要把 query 和 document 分别编码成向量,再用向量距离衡量相似度,这本质上是双塔结构,query 和 document 没有交互,限制了它的细粒度建模能力;rerank 模型通常用交叉编码器结构,把 query 和 document 拼接起来一起过模型,可以更细腻地捕捉交互信息,所以精度更高,但代价是延迟更高、算力消耗更大。

常见的开源 rerank 模型有bge-reranker-v2-m3、mmarco系列,也有一些商业 API。架构上是把重排环节放在检索之后、生成之前:向量 DB 先粗召回 50~100 条,再用 rerank 模型精排到 5~10 条进入生成阶段。这一步对最终答案质量的提升非常明显,可以说是我做 RAG 过程中投入产出比最高的单个环节。

3.4 Top-K 参数:不是越大越好

Top-K 到底取多少,这是每个 RAG 新手都会问的问题。我的答案是:取决于你的生成模型、检索质量和任务类型。如果一个问题只需要一个明确事实支撑,K 取 3~5 就够了;如果问题是需要多角度综合分析,K 可以放到 8~10。但 K 一定不能太大,因为:

  • 生成模型的上下文窗口有限,塞太多片段会显著增加幻觉风险;
  • 排在后面的片段本来就是"矮子里拔高个",相关性差的内容进入上下文会对输出产生负面干扰;
  • 每次生成携带的 token 增多,接口延迟和成本同步上升。

我见过有些同学把 Top-K 设成 20、30 的,检索库又不是特别干净,结果模型被大量半相关文本带偏。请记住:RAG 是"宁缺毋滥"的架构,给模型的上下文宁可少而精,不要多而杂。你要做的是不断测试 K 值,找到检索结果开始明显下降的拐点。

4. 生成阶段:把检索结果组装成上下文,这一步比想象中有讲究

检索做完了,拿到了 Top-K 个文档片段,但"拿到"和"用好"之间还有一道坎。生成阶段的本质,是把检索到的知识片段,和用户的原始问题一起,通过 prompt 组织成一个让模型易于理解和遵守的上下文。这个组装过程有一些反直觉的细节,很多人直接拼接就完事,效果差一半。

4.1 上下文组织顺序与去重

第一步是给检索结果排序和去重。很多初阶代码直接把检索结果按相似度顺序拼接,这是一个典型的错误。原因在于,大模型对上下文不同位置的注意力不完全相同,通常对开头和结尾的注意力更强,中间位置的容易"遗忘"——这是很多研究都证实过的现象,业界形象地称为"lost in the middle"。

因此,把最重要的片段放在 prompt 的开头和结尾,把次要内容放中间,比单纯按相似度排序效果更好。具体实现时,可以先按得分排序,然后把得分最高的片段安排在最前方,得分第二高的片段安排在末尾,其余片段按得分递减填在中间。看起来反直觉,实际测试下来效果有明显提升。

另外,同一个信息可能在多个片段中重复出现,直接拼接会浪费 token。如果你在离线切分时做了重叠窗口,那么重复内容几乎必然存在。生成前用文档指纹(如 MinHash 或简单哈希)做一次模糊去重,能显著精简上下文,这个细节在生产环境里很值得做。

4.2 引用溯源:让 Agent 说话有出处

如果你做的是企业级 Agent,生成阶段必须考虑"告诉我它从哪来"。这一方面是为了用户信任,另一方面也是排查错误答案的必要手段。实现方式并不复杂:给每个 chunk 在切分时分配一个全局唯一的文档 ID,进入生成 prompt 时,以特殊标记的形式把片段来源挂在每个 chunk 后面;模型在生成答案时,只要你在 prompt 里明确指示它"引用部分必须注明来源编号",它就能给出类似 [1][2] 的标记,你在后处理阶段把编号替换成对应的文档路径和页码即可。

我早期偷懒没做引用溯源,结果一条回答里混了三个不同版本的项目资料,用户指出矛盾后我根本没法定位问题出在哪一份文档。后来加上引用标记,再配合全文搜索,定位错误答案的成本从小时级降到了分钟级。所以我的建议是:引用溯源不是锦上添花,是 RAG 生产环境的第一步。

4.3 兜底策略:检索结果不可用的时候该怎么办

RAG 一个经常被忽视的问题是:如果检索结果本就与问题无关,怎么办?很多初阶实现会把检索结果无条件塞进 prompt,即使检索到的片段跟问题是两回事,模型仍然被迫基于无关内容生成答案,幻觉率急剧上升。解决这个问题,需要在 prompt 里加一个关键约束,我常用的一种写法是:

指令:以下是从知识库检索到的相关信息,用 [1][2] 标记了编号。 要求: 1. 优先依据这些信息回答问题; 2. 如果这些信息与问题无关或不足以回答问题,请直接回答"知识库中没有相关信息",不要尝试编造; 3. 如果信息之间存在矛盾,请指出矛盾,并列出所有相关依据,不要自行选择一个"可能的正确答案"。

这一条约束的处理要点在于:不要再让模型"在信息不足时发挥",而是让它明确选择"承认不知道"。很多 Agent 的体验崩坏,不是因为检索不行,而是因为模型在信息不足时选择了硬编。加上兜底约束后,用户体验虽然偶尔会被"不知道"打断,但至少不会被编造的错误信息误导。在知识管理场景里,可靠的"不知道"比看似聪明的"编造"珍贵得多。

5. Agent 形态下的 RAG 演进:从单轮问答到多轮对话再到 Agentic RAG

前面讲的 RAG,本质上还是"单轮检索、单轮生成"的经典链路。但真正做 AI Agent 的同学很快会发现,Agent 的实际交互是多轮对话的,用户会追问、澄清、跳转主题;Agent 也可能在规划过程中多次调用检索工具。如果每一轮都独立做一次 RAG,完全抛弃上文语境,效果会非常糟糕。

5.1 多轮对话中的查询改写

多轮对话中,用户的第二句话通常是「这个方案的成本是多少」这种带省略的指代表达。如果不做任何处理,直接把这句话拿去检索知识库,检索到的自然是对不上号的文档。解法是引入查询改写(Query Rewriting):在进入检索前,先让大模型结合对话历史,把当前问题改写成一句完整、独立、可检索的问句。

比如对话历史是「你们产品的部署架构是怎样的?」,用户追问「容器化支持吗?」,改写后的独立查询就应该是「你们的部署架构是否支持容器化部署?」。用大模型做这一步,最简单的方法是在检索前加一轮"改写调用",虽然增加了一次 LLM 调用延迟,但换来的是检索质量的大幅提升。如果你的 Agent 框架里本来就有"对话管理"模块,把查询改写挂在那里是最自然的做法。

5.2 多轮追问中的上下文继承

第二代多轮 RAG 会遇到的问题是:第一轮检索到 A 文档,第二轮的检索完全不基于第一轮的结果,重新从整个知识库里搜。但很多用户追问的内容恰恰基于第一轮给出的答案。例如用户先问「性能指标有哪些?」Agent 回答了一串指标,用户接着问「第一个指标具体怎么计算?」如果第二轮还做全局检索,很可能找不到相关片段。更合理的做法是:第二轮检索首先在第一轮已召回的文档范围内做局部检索,同时铺开做全局语义检索补充候选。这种"局部优先 + 全局兜底"的策略,在多轮场景下实测效果比单纯全局检索好很多。

实现上,你可以在 Agent 状态里维护一个"当前对话已引用的文档集",每次新检索的时候,把该文档集作为过滤条件之一。向量数据库一般支持类似doc_ids IN [...]的预过滤,配合共享的语义检索,就能实现两路召回。

5.3 Agentic RAG:当检索变成工具调用

再进一步,RAG 不只是 Agent 的一项知识供给机制,它本身也可以被封装成 Agent 可以按需调用的工具。这就是 Agentic RAG 的状态:Agent 根据用户问题的性质,自主决定是否需要调用检索工具、以什么方式调用、对检索结果做何种二次加工。

举个例子,用户问「对比一下我们上季度和本季度的故障率变化」,固定管道式 RAG 只会拿你预设的检索方式去查,而 Agentic RAG 的 Agent 会发现这个问题需要查两份不同时间段的数据,于是它会先调用一次"按时间过滤"的检索,再调用一次"按指标名过滤"的检索,最后把两次结果汇总生成答案。这种自主规划检索步骤的能力,正是从"检索增强的聊天机器人"走向"真正的 Agent"的关键一步。

在工程实现上,你可以在 Agent 的工具列表中加入search_knowledge_base工具,参数包括查询语句、过滤条件、返回条数。Agent 的规划模块会根据意图决定调用时机。这一层变化说起来简单,实际涉及 Agent 的可信度控制、检索结果校验、多工具协同状态管理等复杂问题,我打算在系列后面的篇章详细展开。

5.4 与记忆、图谱的结合:RAG 的无限联结

最后补充一个更进阶的方向:RAG 不是唯一的知识获取手段,它还可以和知识图谱(GraphRAG 与本体的结合)、长期记忆相互配合。知识图谱适合表达实体之间的多跳关系,比如「A 部门的服务器是否支持 B 系统的部署?」这类需要沿关系链推理的问题,纯向量检索很难回答到位。长期记忆则解决"同一个用户在多次会话中反复提到的事实",例如用户的偏好,这些信息没必要每次重新检索,而是应该沉淀在 Agent 的记忆模块里。

我的观点是:RAG 是整个 Agent 知识管道的底座,但它不是全部。真正成熟的系统,应该是 RAG 处理非结构化文档,图谱处理结构化关系,记忆处理个性化信息,三种方案组合使用。

6. 检索质量评测:没有指标的 RAG 优化都是自我感动

很多团队做 RAG,说"效果不行"就埋头调参,却从没定义过"什么是效果"。我在这个项目上最大的教训是:没有评测,就没有优化方向。你可以凭直觉调整 chunk 大小、修改 prompt,但这些调整到底是改良还是劣化,没有量化指标你根本无法判断。

6.1 核心评测指标:召回命中率与排序质量

最关键的指标是召回命中率(Hit Rate):给定 N 个标准问题,每个问题有一个或多个标准答案文档,系统返回 Top-K 结果中有没有包含标准文档。例如构造 50 个评测问题,系统返回 Top-5 中能正确命中的比例达到 80%,说明召回端基本合格。另一个常用指标是MRR(Mean Reciprocal Rank,平均倒数排名),衡量标准答案在排序中出现的位置,越靠前越好。如果标准答案经常出现在第 2、3 位,虽然 Top-5 命中率还行,但生成端拿到的最佳上下文质量仍受影响。

除此之外,还有一个容易被忽视的答案正确性指标:召回命中了就一定能生成正确回答吗?不一定。评测时不能只看检索命中,还要让大模型基于检索结果生成答案,再人工或模型打分判断答案是否正确。这个过程称为"端到端评测"。我自己的习惯是:先建立 30~100 个标准问题的评测集,然后每次改动参数,都跑一遍 Hit Rate 和端到端正确率,再对比前后变化。没有这个过程,你说"好像变好了",大概率是心理作用。

6.2 典型问题定位:Hit Rate 上不去时从哪些环节下手

当你发现 Hit Rate 一直不理想时,我建议按以下优先级排查:

  • 数据清洗问题:文档解析出的文本乱码、重复、缺失大量关键内容,先修数据,不要调模型。
  • chunk 切分问题:关键信息被拦腰切断、跨段语义丢失。尝试增大 chunk、增加重叠,或者改用语义切分。
  • 查询问题:原问题本身过短或指代不明。先做查询改写再检索,这一步对 Hit Rate 的影响往往巨大。
  • embedding 模型不匹配:换一个更强或更适配领域的中文 embedding 模型,对比 Hit Rate 差异。
  • 检索策略单一:从向量检索切换到「向量检索 + BM25 + RRF 融合」。
  • Rerank 缺失:召回效果不错但 Top-5 顺序乱,接入 rerank 后相关片段前置,能显著改进生成质量。

6.3 一组实测参数参考

下面是我在一份约 200 页的中文技术手册上跑过的参数组合,供直接参考(注意这只是一个样例,你的数据需要自己的评测来决定最终参数):

参数取值说明
chunk_size400 token技术文档段落中位数约在 350~450 之间
chunk_overlap80 token保证跨段上下文不完全断裂
embedding 模型bge-large-zh-v1.5中文长尾实体表现稳定
索引类型HNSW(M=16, efConstruction=200)单机百万级向量以内无压力
召回方式向量检索 + BM25 混合,RRF k=60兼顾语义与精确词匹配
粗召回 Top-K50rerank 前的候选数量
精排后 Top-K5进入生成阶段的最终文档数
rerank 模型bge-reranker-v2-m3交叉编码器,精度提升明显
相似度阈值不设硬阈值,依赖 rerank 排序硬阈值在不同模型上不可迁移

这套参数跑出来的结果是:100 个评测问题上 Hit Rate(Top-5)超过 92%,MRR 超过 0.85。作为对比,只使用纯向量检索、不做融合、不做 rerank 时,Hit Rate 大约在 78% 左右。差距完全来自检索链路的逐步叠加优化。

注意:我用的是"Top-5 92%",不是"最终生成正确率 92%"。生成阶段还要考虑 prompt 质量和兜底策略,端到端正确率通常比检索命中率低 5~10 个百分点。如果你发现检索命中率很高但生成答案依然出错,问题大概率出在 prompt 组装和后处理上,别在检索端盲目调参。

6.4 我的建议:把评测集当成项目资产

最后分享一个我自己的操作习惯:我会为每个知识库维护一个独立的评测集,包含问题、标准答案、答案出处文档、检索结果记录四列。每次修改 RAG 任何环节之前,先跑一遍旧版本的评测结果作为基线,改完之后再跑一遍新版本,对比差异。这个方法虽然前期花时间,但长期看,它让我避免了无数次的"我觉得改好了"。

RAG 做的越深我越发现,这个系统的每一个环节——数据清洗、切分、向量化、检索融合、精排、prompt 组装——都像一个独立的水龙头,任何一环漏水,最终出来的水都不干净。先把评测习惯建起来,后面的优化才算有方向。这一篇把 RAG 基础链路完整拆完了,下一篇我打算基于这套管道,往上叠一层 Agent 的规划与工具调用,把"检索增强"升级成"自主获取知识"。