
做 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, encodingutf-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_embeddingsTrue) # 第三步检索——用向量余弦相似度召回 query A产品的保修期是多少 query_vec embedder.encode([query], normalize_embeddingsTrue)[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[{i1}] {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 知识管道的你。