Skip to content

RAG 全流程:从一堆文档到 Agent 能用的知识库

约 23 分钟

AI 私教专属 1v1 AI 私教,围绕本节内容深度教学

进入私教

假设你让 Agent 帮你写一段代码,用到了公司内部的一个 SDK。这个 SDK 的文档在公司 wiki 上,模型训练数据里当然没有。Agent 不知道这个 SDK 的 API 长什么样,它只能瞎猜——猜出来的代码看起来像那么回事,但跑不起来。

再比如,你问 Agent "我们上个季度的 ARR 是多少"。这个数据在一份内部报告的 PDF 里,模型不可能知道。

这类问题的共同点是:信息存在,但不在模型的训练数据里,也不在 Agent 的记忆里。

RAG(Retrieval-Augmented Generation,检索增强生成)就是解决这个问题的。核心思路很朴素:在模型生成回答之前,先去"查资料",把相关内容塞进上下文,让模型有据可依。

听起来就像考试的时候允许翻书——你不需要记住所有知识点,只要能快速翻到正确的那一页就行。

但"翻书"这件事,做好了是开卷考试,做不好就是翻了半天找不到,或者翻到了错的页面,然后自信满满地写了一个错误答案。

Forrester Research 2026 年的一项研究发现,67% 的 RAG 失败可以追溯到数据质量问题——不是模型不够聪明,是你喂给它的"参考资料"本身就有问题。

所以这篇我们不讲"用什么框架搭 RAG",而是把 RAG 管线里每一步拆开看:哪些地方容易出错,为什么会错,怎么避免。

img

第一步:文档加载

RAG 的第一步是把你的文档变成可检索的文本。听起来简单,但不同格式的处理差异非常大。

Markdown 是最友好的格式。 它本身就是纯文本,有标题层级、有列表、有代码块——结构信息天然就在那里。基本上读进来就能用,不需要太多处理。

PDF 是最头疼的格式。 同样一份 PDF,用纯文本提取可能把表格拆成一行行的乱码,把多栏排版混在一起,页眉页脚跟正文混在一起。结构化提取(比如用 Adobe Extract API 或者开源的 docling)效果好很多,但成本也高。

如果你在 Node.js 生态里做 PDF 解析,几个选择供参考:简单文本提取用 pdf-parse 或者更现代的 unpdf(unjs 团队出品,TypeScript 原生)就够了。需要保留表格和标题结构的,pdfjs-dist(Mozilla 维护,能拿到字形位置信息)或者 IBM 的 Docling(表格准确率 97.9%,中文支持也好)更靠谱。如果不想自己折腾,LlamaParseClaude API 都支持直接处理 PDF,靠模型来理解结构。

代码文件更特殊。 代码不是"文章",它有函数、类、模块这些结构单元。把一个函数从中间劈开分成两个 chunk,两个 chunk 单独看都没有意义。

网页需要去噪。 导航栏、侧边栏、广告、cookie 提示、footer——这些占了页面内容的一大半,但对你的检索来说全是噪音。FirecrawlJina Reader 这类工具可以帮你只提取正文。

总结一句话:数据质量决定了 RAG 的上限。 后面的分块策略再精妙、embedding 模型再强大,也救不了一份解析乱了的 PDF 内容。

第二步:分块——切得好不好,直接决定检索质量

文档加载进来了,下一步要把它切成小块(chunk)。为什么要切?因为上下文窗口有限,你不可能把一整份 50 页的文档塞进去。你需要找到最相关的那几段,只把它们塞进去。

但问题来了:怎么切?

三种主流策略

固定大小分块:按 token 数量硬切,比如每 512 个 token 切一块。简单粗暴,但可能把一句话从中间劈开。

递归分块:先按段落分,段落太长了再按句子分,句子还太长了再按 token 分。LangChain 的 RecursiveCharacterTextSplitter 就是这个思路。它会依次尝试用 \n\n\n. 来分割,尽量保留语义完整性。

语义分块:用 embedding 模型判断相邻段落的语义相似度,在语义变化较大的地方切开。听起来最"智能",但实际效果可能让你失望。

除了这三种,还有按句子切的句子级分块、按页面/标题层级切的结构化分块、让 LLM 来决定怎么切的 LLM-Based 分块(质量最高但成本极高)、基于 embedding 语义分块等等,五花八门的,但我们接下来来看看应该选择哪一种比较好。

一个反直觉的发现

2026 年 2 月,PremAI 团队做了一个跨 50 篇学术论文的分块策略基准测试,结果挺出人意料的:

策略端到端准确率
递归分块 512 token69%
固定大小 512 token67%
按页分块64.8%
语义分块54%

语义分块排最后到了最后。

因为语义分块切出来的 chunk 平均只有 43 个 token——太碎了。每个 chunk 包含的上下文信息太少,模型拿到之后没法做出准确的回答。

这些结果告诉我们一个反直觉的结论:不要被"看起来更高级"的方案迷惑。 递归分块这种"土方法",在大多数场景下就是最好的选择。Chunk Size 设为 256-512 token 这个区间,OpenClaw 的默认配置是 400 token/chunk,可以在实际场景里面直接采用这个配置。

代码文件怎么切?

代码不能按 token 硬切,原因很直觉:一个函数被从中间切开,两半各自都没有意义。

更好的做法是 AST-based chunking——用 AST(抽象语法树)解析代码,按函数、类、模块这些语义单元来切分。

不过说实话,在 coding agent 的场景里,现在越来越多的产品根本不用 RAG 来检索代码。Claude Code 就是一个典型例子——它直接用 grep、glob、选择性读文件来找代码,没有任何 RAG 基础设施。为什么?因为代码有明确的结构(import、类型、调用链),用确定性的工具去导航比用向量搜索更准确。

Anthropic 的工程师 Boris 在 Latent Space 播客上说过:"我们早期试过本地向量数据库做代码 RAG,后来放弃了。Agentic search 的效果碾压向量搜索。"

RAG 更适合的是文档类内容——技术文档、产品文档、知识库、报告。 代码检索,让模型用工具就好。

img

第三步:Embedding——把文字变成向量

分好块之后,需要把每个 chunk 变成一个向量(一串数字),这样才能做相似度搜索。这个过程叫 embedding。

Embedding 到底在做什么?

一个简单的类比:想象一个巨大的多维空间,每一篇文档在这个空间里有一个位置。语义相近的文档,位置就靠近;语义不相关的,位置就远。

举个例子,"怎么部署应用"和"部署流程指南"在这个空间里应该靠得很近。而"怎么部署应用"和"今天天气不错"就应该离得很远。

Embedding 模型做的就是帮你算出每个 chunk 在这个空间里的坐标。查询的时候,把用户的问题也算一个坐标,然后找跟它最近的那些 chunk——这就是向量搜索。

模型怎么选?

2026 年的一项基准测试对比了主流 embedding 模型(PremAI 也有一份更侧重选型建议的报告),基于这些前沿的研究和测试,我说几个关键发现:

Gemini Embedding 2 是目前的全能选手。 跨语言、长文本、压缩能力都是顶尖的。如果你不确定选什么,选它大概率不会错。

OpenAI 的 text-embedding-3-small 适合快速起步。 便宜(每百万 token 0.02 美元),精度够用,API 简单。但在生产环境里可能不够用——跨语言能力和长文本处理不如 Gemini。

本地模型要谨慎。 nomic-embed-text 和 mxbai-embed-large 在英文场景下表现还行,但跨语言能力基本为零(nomic 的跨语言得分只有 0.154,满分 1.0),超过 4K token 的长文本质量也会明显下降。如果你的文档有中文,或者单个 chunk 比较长,本地小模型可能会坑你。

第四步:存储——向量数据库选型

embedding 算出来了,需要存到一个支持向量搜索的数据库里。

这里有一个先说清楚的前提:向量数据库的选择对 RAG 系统质量的影响很小。真正决定质量的是前面的数据处理、分块策略和 embedding 模型。所以不要在数据库选型上花太多纠结的时间。

下面有几个主流选择,你选择一个即可:

sqlite-vec——如果你在做本地应用或者边缘部署,它就是最合适的。零依赖,单文件,不需要额外的服务进程。OpenClaw 就是用的这个。缺点是单线程写锁,并发高了会有瓶颈。

ChromaDB——大部分项目的务实选择。轻量,几分钟就能跑起来原型。2025 年用 Rust 重写之后性能提升了 4 倍。底层其实也是 SQLite。

pgvector——如果你的项目已经在用 PostgreSQL,直接加个扩展就行了,不需要引入新的基础设施。50-100M 向量以下都能扛得住。比如 Supabase 这种基于 PostgreSQL 的数据库服务,可以直接用这个方案,很方便。

Qdrant / Pinecone——大规模生产环境用。Qdrant 是 Rust 写的,过滤能力强;Pinecone 是全托管的,不用操心运维。但长期使用的经济成本也相对高。

Milvus——如果你的向量数据量在百万到亿级别,Milvus 是目前最成熟的分布式向量数据库之一。它支持多种索引类型(IVF、HNSW、DiskANN 等),可以根据数据规模和延迟要求灵活选择。背后的 Zilliz 也推了全托管的云服务。在国内企业级 RAG 场景用得非常多,生态和中文社区也比较活跃。轻量级场景可以用它的嵌入式版本 Milvus Lite,一行代码就能跑起来。

一个简单的决策路径:本地/边缘用 sqlite-vec,原型验证用 ChromaDB,已有 Postgres 就用 pgvector,大规模生产环境直接上 Qdrant、Pinecone 或 Milvus 三者任选其一。

第五步:检索——这里的坑最多

存储做好了,现在用户来提问了。你需要把问题转成向量,去数据库里找最相关的 chunk。

这一步听起来简单,但实际上是 RAG 管线里最容易翻车的地方。

有一个最核心的问题:语义相似不等于任务相关。

用户搜"怎么部署",向量搜索可能返回"部署架构图"(语义近)而不是"部署命令清单"(用户真正需要的)。因为 embedding 模型对操作性和描述性内容的区分能力有限。

纯向量搜索在生产环境里是不够用的。混合检索(向量 + 关键词)、Query 改写、Reranker 精排——这些优化手段能把检索质量提升一个台阶。但每一种都有自己的原理、适用场景和成本代价。

下一篇我们会专门拆解这些检索优化技术——从"语义相似 ≠ 任务相关"这个问题出发,讲清楚混合检索为什么有效、Query 改写的几种策略、Reranker 的两阶段架构,以及怎么评估你的 RAG 到底好不好。

这里先建立一个直觉:检索不是"搜到就行",而是"搜到对的"。 这个区别决定了你的 RAG 系统是"勉强能用"还是"真正可靠"。

检索到了相关的 chunk,最后一步是把它们塞进模型的上下文窗口,让模型能够看到这些检索结果。

索引更新:文档变了怎么办?

最后还有一个很实际但经常被忽略的问题:你的文档不是一成不变的。API 文档更新了,知识库加了新文章,旧的条目被删了——索引需要跟着更新。

两种策略:

全量重建:把所有文档重新分块、重新 embedding、重新入库。简单暴力,但成本高。适合文档量不大或者变更量超过 10-15% 的场景。

增量更新:只处理变更的部分。用 hash 比对文件内容是否变化(OpenClaw 就是这么做的),没变的跳过,变了的重新处理。效率高,但实现复杂一些。

OpenClaw 的做法比较稳:增量更新用 hash 检测变化,但全量重建的时候用临时数据库 + 原子交换——先在一个临时 SQLite 文件里建好新索引,确认没问题后一次性替换旧的。

还有一个需要注意的场景:embedding 模型升级了。 新模型生成的向量和旧模型的不在同一个空间里,不能混用。这时候必须做全量重建。

一个值得思考的问题:RAG 还有必要吗?

2026 年了,模型的上下文窗口已经到了 200K 甚至 1M token。一个几十万字的文档库,直接全塞进去不就完了?为什么还要搞分块、embedding、向量搜索这一套?

这个问题其实我们在讲 Context Rot 的时候已经回答过了:上下文不是越长越好。 塞进去的内容越多,模型对每一条信息的注意力就越分散,准确率会持续下降。

RAG 的核心价值不在于"能检索",而在于**"只把最相关的内容放进上下文"**。它是一种上下文管理策略——跟我们前面讲的入口管理、压缩、Deferred Loading 是同一个思路的不同实现。

当然,对于小规模的文档库(比如就几篇文档,总量不超过 10 万 token),直接塞进去确实比搞一套 RAG 管线更划算。RAG 的价值在规模上来之后才真正体现——当你的知识库有几百篇文档、几千个页面的时候,全量塞入既不经济也不有效。

从 RAG 到 Agentic RAG:当检索变成循环

前面讲的整套管线——分块、Embedding、检索、注入——本质上是一条直线:用户问一个问题,系统检索一次,把结果塞进 prompt,模型生成回答。一次检索定生死。

这在简单问答场景下没问题。但你试过问更复杂的问题吗?"我们的用户增长放缓是不是跟上个月改的定价策略有关?"——这个问题需要先查用户增长数据,再查定价策略变更记录,然后对比时间线,可能还要看看竞品同期的数据。一次检索根本不够。

Agentic RAG 就是把检索从一条直线变成一个循环。 你可能在社区里听过这个词,它其实就是我们课程里两个概念的组合——Agent Loop + RAG 管线

传统 RAG 的流程是:

用户提问 → 检索 top-k → 注入上下文 → 生成回答

Agentic RAG 的流程是:

用户提问 → Agent 决定要不要搜 → 搜了看看够不够
→ 不够就换个 query 再搜 → 或者调个工具补充数据
→ 够了再生成回答

实际上你如果用了我们前面讲的 Agent Loop + 把向量检索包装成一个工具,你已经在做 Agentic RAG 了——Agent 在循环里自主调用搜索工具,搜到不满意就换个 query 再搜,搜到够了才开始回答。Claude Code 的 Grep + Glob + Read 组合本质上就是一种 Agentic RAG:模型自己决定搜什么文件、读哪些内容、读完够不够,只是没有用到语义向量化的方式。

所以 Agentic RAG 不是什么新范式,它就是 Agent 能力和 RAG 管线的自然融合。你只需要:把这篇讲的向量检索包装成一个工具,交给 Agent Loop 去调用,剩下的事情模型自己会做。

串一下

RAG 管线看起来步骤不少,但每一步的核心逻辑其实都很清晰:

  1. 加载文档——把各种格式的文件变成干净的文本。数据质量决定了 RAG 的上限,符合"G"的规律。
  2. 分块——把长文档切成小块。递归分块是最稳的起点,512 token 左右,语义分块看起来高级但实测效果经常不如递归分块。
  3. Embedding——把文本变成向量。Gemini Embedding 2 是目前的全能选手,OpenAI text-embedding-3-small 适合快速起步。
  4. 存储——向量数据库的选择对质量影响很小,别在这里过度纠结。本地用 sqlite-vec,原型用 ChromaDB,已有 Postgres 就用 pgvector。
  5. 检索——纯向量搜索不够用,语义相似不等于任务相关。混合检索、Query 改写、Reranker 这些优化手段下篇详细拆解。
  6. 注入上下文——检索结果放到模型的上下文中。
  7. 索引更新——增量更新用 hash 检测变化,全量重建用临时数据库加原子交换。embedding 模型换了必须全量重建。

另外,我们还讲了 Agentic Search,本质上就是 Agent Loop + RAG 管线,没有新东西。

下一篇我们专门拆解检索优化——混合检索为什么有效、Query 改写的几种策略、Bi-encoder 和 Cross-encoder 的两阶段架构、qmd 的三种搜索模式,我们下一节精彩继续。

参考资料

上一页深入 Just-In-Time Context:上下文不是越早塞越好下一页检索优化:语义相似 ≠ 任务相关,怎么让 Agent 找到真正需要的信息?