☰
RAG知识获取管道:从原理到实操,搭建Agent知识库问答系统
2026/9/26 5:28:06 网站建设 项目流程

做 AI Agent 开发也有一段时间了,前阵子帮朋友调试一个客服类的 Agent,折腾半天发现模型本身没什么问题,卡住它的恰恰是“知识从哪来”这一步。这个问题其实很典型:Agent 再聪明,没有靠谱的知识获取管道,它只能靠训练时留下的记忆硬答,一旦遇到私有文档、实时内容或者产品库里的具体参数,就立刻露馅。

这就是这一篇要聊的重点——知识获取管道里的基础方案:RAG。简单说,RAG 就是给大模型外挂一份“可检索的资料库”,让它先查资料、再组织回答。这篇文章适合正在搭 Agent 的开发者,也适合刚接触 RAG、想把文档和知识库接入 AI 应用的产品同学。全文不讲虚的,直接把原理拆开,配上可行的实操流程和调优经验,读完你至少能自己搭出一个能跑通的知识问答 Agent。

1. 为什么 Agent 需要一条知识获取管道

1.1 大模型的知识边界:训练快照与“不知道自己不知道”

先说一个容易被忽略的事实:大模型的知识是“训练时”的,不是“使用时”的。无论是 DeepSeek 还是其他主流大模型,它们的知识都来自预训练阶段拿到的语料快照,这个快照有时间截止点,也没有你的私有数据。换句话说,你问它“你们公司新产品的保修政策是什么”,它大概率会给你一个“看起来合理但完全瞎编”的答案。

我习惯打一个比方:你请了一个知识面很广的实习生,但这位实习生入职之前没看过你们公司的任何文件。你问他行业通识,他能说得头头是道;你问他公司内部的 SOP、报价规则、售后流程,他只能靠猜。Agent 也是这样——通用能力很强,领域知识为零。更麻烦的是,模型不会主动告诉你“我不知道”,它会把不确定的内容包装成确定性的答案输出。这就是 AI 圈常说的“幻觉”问题。

所以“知识获取”这件事本质上不是让模型变得更聪明,而是给它装上一套获取“当下、私有、精准信息”的管道。RAG 做的事情恰好就是这个:你准备好资料,程序负责检索,模型负责根据检索到的内容作答。模型的知识边界没有被突破,但它在回答问题时手里有了“参考资料”,准确率和可信度是完全不一样的。

1.2 RAG、微调与长上下文:三条补知识的路怎么选

在接触 RAG 之前,很多人会先想到另外两条路:微调模型,或者直接把长文档塞进上下文窗口。我分别说说它们的真实使用感受。

微调(Fine-tuning):适合改变模型的行为方式和表达风格,不太适合灌入大量事实性知识。微调的本质是调整权重,让模型学会某种“套路”,但每一条具体知识的学习成本很高,而且每次知识更新都要重新训练、重新部署。把公司几十份文档“背”到模型里,成本和收益完全不成正比。我的经验是:想让 Agent 学会某种语气、某种回答格式,微调有效;想让 Agent 知道某个产品参数,请别微调,交给检索。

长上下文(Long Context):模型窗口越做越大,动辄百万 token,初看好像把文档全塞进去就能解决。但这个方案有两个坑。第一,成本高——每次对话都把全部文档送给模型,推理费用和延迟都上去了。第二,检索精度低——模型在超长上下文里找一条关键信息,效果往往不如先用检索把候选内容缩小到几百 token 再让模型读。长上下文适合“偶尔需要全局理解”的场景,不适合高频、多文档的知识问答。

RAG:本质是“先检索,后生成”,它把知识存储和模型推理解耦。文档可以随时更新,检索过程可控,模型本身不用动。这是目前构建 Agent 知识能力时性价比最高的方案。下面这张表可以帮你快速做决策:

方案适合解决什么问题不适合解决什么问题维护成本
RAG私有文档问答、实时信息、大规模知识库、频繁更新改变模型回答风格、增强推理能力低,只需维护检索侧
微调输出格式、领域术语表达、行为风格高频更新的具体知识、长尾事实高,每次更新需重新训练
长上下文单次完整阅读较长文档、全局总结高频问答、多文档交叉比对的成本控制中,token 成本会随用量上升

1.3 RAG 在 Agent 架构里的位置:它不是全部,但很关键

做 Agent 的人都清楚,Agent 的能力通常可以拆成几个模块:感知、记忆、规划、行动。RAG 在里面的角色是“外部记忆系统”。有些朋友会把 RAG 和 Agent 混在一起,以为 Agent 就是 RAG 加个聊天框,其实不是。

RAG 是 Agent 的“知识组件”,Agent 则是一个会决定“何时调用知识组件”的决策者。比如你搭一个客服 Agent,它可以先判断用户问题属于哪个业务域,然后决定是否触发检索、检索哪些资料、检索结果如何整合进回答。更进一步,还有人把 RAG 封装成 Agent 可以调用的一个“工具”,这种模式被称为 Agentic RAG——Agent 自主决定查询策略、拆解复杂问题、多次检索。可以说 RAG 本身是管道,Agent 决定怎么用这条管道。

这里顺带提一下和 MCP 的区别,这个问题最近问的人特别多。MCP 解决的是 Agent 与外部工具的协议标准化问题——比如让 Agent 能够调用数据库查询、调 API、操作网页。RAG 解决的是知识检索问题——把非结构化文档变成可查询的索引。技术上 RAG 可以用 MCP 暴露给 Agent,但它们是两个层面的东西:MCP 是“手”,RAG 是“记忆”。理解了这层关系,你搭 Agent 时就会很清楚:工具调用走工具协议,知识获取走检索管道,两条路径互相配合,而不是二选一。

2. 拆解 RAG 三阶段:索引、检索与生成

2.1 索引阶段:文档入库前要处理的几件小事

索引阶段是整个 RAG 管道中最不起眼、但最决定成败的一步。很多项目最后效果不好,回头排查时发现根因不在模型,而在文档入库这一步做得太糙。我总结下来,索引阶段要处理的事情有四件:清洗、切块、向量化、存元数据。

清洗:源文件的格式五花八门,PDF 提取出来的文字可能带着页眉页脚、乱码;Word 转出来的文本可能有大量空行。直接把这些脏文本切块、向量化,检索质量会受影响。常见做法是先用解析库把文本抽出来,再做正则清理——去掉无意义符号、合并多余空行、识别并删除页眉页脚。如果跑的是表格密集的文档,建议先转成 Markdown 再清洗,效果会好很多。

切块:把一本书或者一份几十页的产品手册拆成若干小块,这是为了让检索更快更精准。切块过大,每一块的信息太杂乱,检索命中后上下文不聚焦;切块过小,信息又容易断片,模型拿不到完整上下文。这个度需要根据文档类型微调,后面我会专门讲切块参数的选法。

向量化:将文本块转换成高维向量,这样后续检索才能用“距离”度量语义相似度。这里要考虑两点:一是中文文本要选对 Embedding 模型;二是向量维度会影响存储和检索开销。常见模型里有开源的 bge、m3e,也有闭源接口,选型上要看检索效果和部署成本,本地项目建议从轻量模型试起。

存元数据:每个切块除了文本内容,还应该记录来源文档、章节标题、页码、时间戳等信息。元数据不会参与向量检索,但在后续结果过滤和引用溯源时非常有用。没有元数据的知识库就像一本没有目录的词典,检索能命中的“词”却说不清它来自哪里,这在面向生产场景时几乎不可接受。

2.2 检索阶段:召回质量决定答案上限

检索阶段的目的简单说就是“从海量切块中找出与问题最相关的若干块”。这个阶段的好坏,直接决定了生成阶段的上限——如果检索到的内容本身就不相关,再强的模型也答不对。

常用的检索方式有三类,我分别说一下:

向量检索(语义检索):把用户问题也向量化,然后在向量库里找余弦相似度最高的几个文本块。它的优势是能理解语义关系——用户问“保修期多久”也能匹配到写“质保时间”的文档。短板是它对专有名词、精确 ID 这类“字面精确匹配”不太敏感,所以单独用容易漏掉关键词完全一致但语义表达不同的查询。

关键词检索(BM25):传统搜索引擎的做法,按词频和逆文档频率打分。它对精确匹配能力很强,能找回向量检索遗漏的“型号参数”“订单编号”,但它不懂同义改述,用户换个说法就召回不到。

混合检索 + Rerank:把向量检索和关键词检索的结果合并,再用一个 Rerank 模型重新排序。这是目前生产环境里比较靠谱的组合。Rerank 不是普通的相似度计算,它会拿“问题 + 候选文档”做精细的交叉编码打分,把真正有用的结果提到最前面。代价是多一次模型推理,耗时和成本会上升,但检索效果通常能带来明显改善。

检索阶段还有两个需要关注的参数:top_k 和分数阈值。top_k 决定取多少个候选块进入生成阶段,通常 3~8 个比较合适。取少了可能漏信息,取多了会污染上下文。分数阈值则用来过滤弱相关的块——低于阈值的检索结果宁可不用,也别硬塞给模型。这个阈值没有通用答案,建议在真实数据上采样测试确定,初期可以设低一些,后面逐步收紧。

2.3 生成阶段:组装上下文是有讲究的

检索阶段完成后,你不能简单地把几个切块拼接进 prompt 就完事。生成阶段考验的是“如何把参考材料用得不突兀、不混乱”。

我通常使用一个三部分结构的 prompt 模板:第一部分是系统指令,说明助手身份、回答规则、不许编造、必须引用资料;第二部分是检索到的参考内容,按相关性排序并标注来源;第三部分是用户问题。这样模型在生成时能明确感知“答案要从这部分材料里找”,回答的倾向性会好很多。

多轮对话场景里有一个细节特别容易踩坑:用户在第一轮问“A 产品的电池容量是多少”,第二轮说“那它的充电接口呢”。如果你把两轮对话全文都丢进检索,模型可能不知道该检索“充电接口”还是“电池容量”。我的做法是先对用户问题做“语义改写”——结合历史消息把问题补全成独立查询语句,比如“A 产品的充电接口是什么”,再用改写后的查询去检索。如果历史对话很长,还可以先做摘要,只保留与当前问题相关的上下文。这一环做得好不好,体验差异很大。

另外,如果你希望用户能回看答案来源,那么在生成阶段就要让模型输出对应的引用编号,并确保这些编号对应到元数据里的文档标题或页码。没有引用的 RAG 回答,在正式场景里很难取信于人,这块务必设计进 prompt 规则里。

3. 实操:从零搭一个带 RAG 的 Agent 知识库

3.1 选型思路:框架、向量库与模型怎么定

第一次搭 RAG 项目的人,很容易在选型上卡住。我的建议是先明确你的运行场景——是本地离线要求高,还是允许走云接口?是几百个文档的小项目,还是百万量级的大规模知识库?选择不同,路线完全不同。

框架层面:项目初期用 LangChain 或 LlamaIndex 都行,它们把加载、切块、向量化、检索的流程封装得比较完整,能帮你快速跑通流程。如果不想被框架束缚,也可以手写原生流程,代码不多,反而更容易理解每一步在干什么。个人建议先手写一遍最小流程,再引入框架封装,不然出了问题你会找不到排查入口。

向量库层面:本地小项目可以从 Chroma、FAISS 入手,零配置、内存即可;数据量上来了再考虑 Milvus、Qdrant 这类服务化向量库;如果团队本身在用 PostgreSQL,直接加 pgvector 扩展也是一种低运维成本的选择。向量库本质上解决的是“海量向量怎么存、怎么快速算相似度”的问题,不需要一上来就上一个重组件。

模型层面:Embedding 模型和生成模型分开选。中文场景下,bge 系列或者 m3e 都是不错的开源选择;如果数据涉及中英混合,可以测试对比几条典型问题再决定。生成模型(也就是最终回答问题的模型)选择余地比较大,本地可以用开源模型,跑在云上也可以用闭源 API。

下面是我在个人项目中常用的一套组合,仅供参考:

组件可选方案适用场景
文档解析PyMuPDF / python-docx / unstructuredPDF、Word、TXT 等常见格式
切块LangChain RecursiveCharacterTextSplitter通用默认方案
Embeddingbge-small-zh / text-embedding-3-small中文 / 多语言
向量库Chroma / FAISS / pgvector小项目 / 中等规模 / 已有 PG 环境
检索策略向量 + BM25 + Rerank生产级效果优先

3.2 最小可跑通的 RAG 流程(附关键代码)

先看一个不依赖重量级框架的最小流程,用不到一百行代码就能跑通。这里我用 Python 伪代码风格写出来,关键环节我会加注释解释每一步在做什么。

from sentence_transformers import SentenceTransformer import numpy as np # 第一步:加载文档并切块 text = open("product_manual.txt", encoding="utf-8").read() chunk_size, overlap = 400, 50 chunks = [] for i in range(0, len(text) - overlap, chunk_size - overlap): chunks.append(text[i:i + chunk_size]) # 第二步:向量化并“存储” embedder = SentenceTransformer("BAAI/bge-small-zh-v1.5") chunk_vectors = embedder.encode(chunks, normalize_embeddings=True) # 第三步:检索——用向量余弦相似度召回 query = "A产品的保修期是多少" query_vec = embedder.encode([query], normalize_embeddings=True)[0] scores = chunk_vectors @ query_vec # 因为向量已归一化,点积等于余弦相似度 top_k = 3 indices = np.argsort(scores)[-top_k:][::-1] retrieved = [chunks[i] for i in indices] # 第四步:生成——组装 prompt 后调 LLM 回答 prompt = f"""基于以下参考资料回答用户问题,不要编造。 参考资料: {chr(10).join(f"[{i+1}] {c}" for i, c in enumerate(retrieved))} 用户问题:{query} """ # 这里的 prompt 交给任意 LLM 接口即可,省略模型调用代码

这套流程的每一步都是可以用真实代码替换的。跑通之后,你可以把它拆开,逐段替换成更健壮的方案:把文本加载换成文档解析库,把固定切块换成递归切块器,把内存里的向量列表换成 Chroma 存储。

这里有一个我在第一版实现时常犯的错:直接把整个文档的向量全部保存在内存列表里,没有做持久化。项目一重启就全部重新向量化,几十个文档还好,几百个文档就非常浪费时间。所以向量化之后一定要落库,无论是 Chroma 的文件模式还是 FAISS 的索引文件,都要保证“建索引一次、查询无数次”。

3.3 表格类资料应该怎么入库

热搜里有一个问题很典型:系列产品表格怎么存进 RAG 知识库。这是我被问过最多的问题之一,因为文档类好处理,表格类很多人容易踩坑。

产品表格通常是几列几十行,比如“产品型号、参数、保修期、价格、备注”。如果你直接把整张表格转成行文本切块,检索时很容易把“A 型号的价格”匹配到 B 型号的行上;而且表格的列名意义重要,单纯逐行切块会让模型失去“列名上下文”。

我的常用方案是:先把表格转成 Markdown 格式,保留表头结构,然后按行或者按“产品族”进行切块。关键技巧是把表头信息拼进每一行的文本中。举个例子,原表格一行是“A100 | 5000mAh | 一年 | 1999 元”,转成切块文本时我会存成“型号:A100;电池容量:5000mAh;保修期:一年;价格:1999 元”。这样每一个检索单元都是自包含的,模型不需要前后拼接也能理解含义。

还有一类表格是范围型配置表,比如“按发货地区分运费”。这种情况先在文本里说明表格的用途,再把行转成描述性句子:“华东地区首重 8 元,续重 4 元;华北地区首重 10 元,续重 5 元”。这比保留原表格式更容易被生成模型理解。核心原则只有一条:切块后的每条文本,都应该在不依赖其他块的情况下被独立理解。

3.4 多轮对话场景的 RAG 设计要点

RAG 接进 Agent 之后,多轮对话是个绕不开的场景。这里我要展开说几个设计要点。

问题改写:如前文所说,多轮对话中用户经常省略主宾语。你需要在交给检索器之前把对话历史压缩成一个独立查询。这部分可以单独调 LLM 实现,也可以本地用简单规则处理。我个人的实现是维护最近两轮对话,拼接后让 LLM 输出“当前用户问题对应的独立检索词”,再拿这个词去检索。这样成本不高,效果也基本够用。

历史记忆与知识检索分开:Agent 的记忆应该分两类,一类是对话历史里的用户偏好和已确认信息,一类是知识库里的客观事实。不要把对话历史直接作为 RAG 检索的内容——历史记录是给模型生成时参考的,不是用来检索的。我在项目中会维护一个独立的会话摘要缓冲区,问答时的上下文同时包含“会话摘要”和“检索到的知识块”,但它们分别来自两条管道,互不污染。

有条件地触发检索:不是每一轮都需要走 RAG。如果用户问“刚才说的价格是多少”,这个问题在历史摘要里就有答案,不需要检索知识库。如果用户问“帮我改一下地址”,这需要调用工具而非检索文档。所以合理的设计是让 Agent 先做意图判断,再决定是走 RAG、走历史、还是走外部工具。在简单实现里,我会用一个关键词或分类模型做意图路由;复杂场景下,交给 Agent 本身的规划能力判断,也就是前面提到的 Agentic RAG 方向。

引用溯源:多轮场景下的引用比单轮更难做。我的方案是:每轮回答末尾统一输出一个“相关文档”区块,列出本轮引用的文档标题和页码。这样即使用户连续问多个问题,也能清楚看到每个答案的来源,排查时也更方便定位是哪份文档导致的问题。

4. 常见问题与调优实录

4.1 检索不到、检索太杂、答非所问,三步定位

做 RAG 最怕的就是输出有问题却不知道问题出在哪个环节。我建了一个很简单的排查顺序:先看检索结果,再看生成策略,最后才怀疑模型。

第一步,把检索到的内容直接打印出来看。如果候选块本身不相关,那是索引或检索的问题,与模型无关;如果候选块相关但最终答案不对,那问题出在 prompt 组装或模型理解上。第二步,反向检查提示词——检索内容被完整传进上下文了吗?被截断了吗?系统指令是否明确要求“只根据资料回答”?第三步,换一个更强的模型做对照实验。很多时候弱模型生成能力不够,换更强的模型后同样链路的输出质量立刻上来了。

这个排查顺序我称之为“先怀疑管道,再怀疑引擎”。因为 RAG 里管道环节多、可调性大,模型反而是最稳定、最不容易出问题的一环。绝大多数项目翻车都翻在检索结果不干净或者 prompt 设计不合理,真正因为模型笨而答错的场景其实不多。

4.2 切块导致的“信息撕裂”怎么缓解

切块太碎会导致一个典型问题:某条信息被拆到两个块里,两个块单独看都不完整,检索时只能命中其中一半,模型看到的就是残缺信息。

我有三个缓解手段。第一,设置合理的重叠(overlap)。重叠部分能让切块边界处的信息在两个块中都有出现,提升命中概率。第二,采用“父文档检索”策略——先检索小片段获取精确位置,再返回它所属的更大块作为模型上下文。比如你检索到一个 200 token 的小片段,但把它放回 1000 token 的父块里,父块包含完整的产品介绍,模型理解整段内容的难度会低很多。第三,对每个文档块生成一段摘要,专门做一个“摘要索引”,检索时先匹配摘要再返回正文。这相当于给知识库加了一级目录,尤其适合长文档和教材类资料。

这三种方案各有成本,建议先从重叠和父文档策略开始,它们改造成本低,见效也快。摘要索引准确率更高,但需要额外调用 LLM 生成摘要,离线批处理时一次性的成本,数据量大时要评估一下耗时。

4.3 本地化部署 RAG,有哪些忠告

很多人问我本地 RAG 怎么搭,我理解他们真实的需求多半是数据不出内网。本地化部署要注意四个点。

Embedding 模型不能太弱。本地部署时很多人为了省资源选一个很小的 Embedding 模型,检索质量会明显下降。中文场景建议至少用 bge-small 级别的开源模型,如果你有比较大的搜索相关性压力,直接上 bge-large 或更强的商用模型。这个侧的资源投入对整体效果影响很大,不值得抠。

向量库要选能支撑当前数据量的。本地小项目用 FAISS 或 Chroma 完全足够。不过如果你有持续增量更新的需求,建议早点切到服务化的向量库。本地文件型向量库在并发写入和持久化方面有一些额外的工作要做,服务化方案反而更省心。

生成模型和 Embedding 模型要分开看。有些朋友以为本地部署必须全部开源,其实生成模型可以在本地,也可以走企业内部 API。Embedding 一定要和检索链路放在同一个环境里(文本标准化和向量格式才一致)。如果没有企业 API 环境,Embedding 和生成模型都放本地,对显存的要求会更高。

数据更新要有“重建索引”的机制。文档是活的——今天改了一版,明天加了新产品。如果知识库只增不改,慢慢就会出现“检索到旧版”“新文档被忽略”的问题。最简单的机制是记录每个文档的版本号或更新时间,更新时重新解析、重新切块、重新向量化,并清理旧向量。这一步做不到,本地 RAG 短期能用,长期一定出问题。

4.4 一套调优优先级清单,按顺序做不会错

最后我整理一个调优优先级清单,这些都是我真实项目里按踩坑多少排出来的顺序。

优先级调整项说明
1文档源质量清洗不干净、格式混乱,后续所有环节都跟着乱
2切块策略块大小、重叠、分隔符,直接影响检索命中质量
3Embedding 模型换一个更适配领域语料的模型,效果提升最明显
4检索策略从向量到混合检索再到 Rerank,逐步叠加
5提示词模板系统指令、引用规则、上下文格式
6生成模型最后考虑换更大的模型或调整采样参数

这套顺序的核心逻辑是:先把信息管道做干净,再谈生成效果。如果管道本身有漏洞,换再强的模型也只是把错误内容说得更流利而已。

拿我自己最近的一个项目举例:最开始文档解析乱、切块大小不合适,检索结果上下文不聚焦,反复调提示词都没有效果;后来把切块从固定 1000 改成递归切块 400,加上 parent-child 返回父块,检索质量立刻改善,最后的回答准确率比之前提升了一大截,而模型一直没换过。这个体验让我对“先管道后模型”的调优路线更加确定了。

写在最后的一些经验

我把这套东西反复做了几轮之后,最大的体感是:RAG 本身不难,难在理解每个参数为什么这么设、每份文档为什么这么切。很多人一上来就堆框架、堆模型,反而被复杂的抽象遮住了视线。我个人建议所有想掌握 RAG 的人,先手写一遍最简流程,用十来个文档跑通,再慢慢替换成框架方案。这个过程会让你对索引、检索、生成三个环节有实打实的手感。

最后再分享一个小技巧:做任何 RAG 项目,先准备 20~30 条覆盖各种提问方式的真实测试问题集。每次改动切块参数、检索策略、prompt 模板时都把这组问题跑一遍,对比效果。有了一组稳定的评估样本,你就能清楚地知道每次改动是变好了还是变坏了,而不是每次都在“感觉好像好了”的模糊状态里打转。这也是我从踩坑里总结出来最重要的一条经验,送给正在折腾 Agent 知识管道的你。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询