Agent 做到第四篇终于要聊知识获取了。前面几篇我们搭过工具调用、聊过记忆机制但很多人跑完 Demo 之后会卡在一个很现实的问题上模型会的知识都是训练时的让它回答一个发生在今天、或者只存在于你们内部系统里的事实它就哑火了。这就是 AI Agent 的知识获取管道问题也是 RAG 基础要解决的核心矛盾——让 Agent 在推理时能实时“查资料”而不是凭记忆硬编答案。这一篇我会按自己做项目的顺序来讲先解释 Agent 为什么必须接外部知识再拆 RAG 的完整链路然后把切分、向量化、检索这三个环节逐个拆开最后聊怎么把它真正接进 Agent 的交互循环里。适合已经跑通 Agent 基础流程、想把知识库能力接进去的开发者也适合刚入门 RAG 但被各种文章绕晕的朋友。1. 先搞清楚一件事Agent 为什么需要外部知识1.1 大模型的“认知天花板”在哪里先说一个很多人没意识到的点预训练模型本身是个“死”的知识体。它的参数权重在训练完成那一刻就固定了之后你不管怎么对话模型内部的知识边界都不会变。你问它公司最新的规章制度、你们产品的当前版本号、某个系统的接口文档它要么编一个像模像样的答案要么用“截止到我训练数据”来搪塞。这不是模型笨而是它的知识获取机制决定了它只能“回忆”不能“查阅”。这里有个很关键的类比把大模型当成一个读书很多但是毕业后就不再翻书的人。你问他教科书上的概念他能答得很好但你要问他上周的会议纪要他只能靠猜。这正是 Agent 场景下的致命伤。因为 Agent 跟普通 ChatBot 最大的区别就是它会“做事”——做事的依据如果不是来自真实世界那这个 Agent 就是空中楼阁。我在前几篇里提过工具调用工具解决的是“行动”问题但行动之前还需要一个“依据”问题。这个依据从哪来就是知识获取管道。1.2 “管道”这个比喻到底在说什么标题里“知识获取管道”这个说法不是随便起的。管道意味着两个特点一是有明确的输入输出二是有固定的路径。RAG 就是给 Agent 装了一条从外部知识源到模型上下文的水管。水流方向是这样的外部文档先被切碎、变成向量、存进向量库用户提问时问题也被变成向量去向量库里捞最相关的片段捞出来的片段和问题一起拼进 Prompt 发给大模型大模型基于这些片段组织答案。整个过程看起来是两段式——索引和检索生成但放在 Agent 里还有一个更上层的问题Agent 要知道什么时候该“开水龙头”。这个“什么时候该查”的开关在传统 RAG 流程里是固定的——每次提问都查。但在 Agent 场景里你会慢慢发现不加判断地查会导致很多问题查回来的内容不相关、打断了 Agent 的推理节奏、甚至让模型被噪声片段带偏。这就是为什么现在大家都在聊 Agentic RAG后面第五章我会专门展开。2. RAG 全链路拆解从一份文档到一次回答2.1 按需拆文档切分质量决定“粮食”颗粒度RAG 管线第一步是文档加载和切分。这一步听起来平平无奇但它决定了后续所有环节的上限。你想想给模型喂进去的知识是碎片化的如果切分出来的片段是语义残缺的再强的检索算法也捞不出完整信息。切分的核心矛盾是颗粒度切得太碎单一片段信息量不够模型看不到上下文切得太粗片段太长向量化之后语义被稀释了而且拼进 Prompt 会浪费大量 token。我见过很多新手直接按固定字符数切——这样省事但中英文混排文档会切得乱七八糟。一个比较稳妥的起步方案是递归字符切分先按段落分段落太长再按句子分句子还太长再按固定长度分。这种递归策略保证了“能完整就完整、不能完整才硬切”。具体实现上 LangChain 里有现成的RecursiveCharacterTextSplitter但我不建议你直接默认参数跑后面第三章我讲怎么调。这里只强调一个观念切分不只是一个预处理步骤它是你知识库的“农田规划”。你打算让模型以什么粒度去理解这份文档完全取决于切分策略。2.2 向量化让计算机理解“意思相近”文档切好之后需要把它们变成计算机能比较的形式。文本本身没法算相似度所以要把每段文本映射成一个高维向量——“意思相近的文本向量之间的距离也近”。这就是 embedding 模型干的事情。做个不严谨但很好懂的类比你把每个段落压缩成一个几百维的“语义坐标”在这个坐标空间里“我今天吃了苹果”和“我啃了一个红富士”应该靠得很近而和“苹果发布了新手机”稍微远一点。向量化就是把语言变成几何问题把“相不相似”变成“距不距离”。实际项目里 embedding 模型的选择非常重要。常见的有 OpenAI 的text-embedding-3-small、text-embedding-3-large开源的 BGE 系列、M3E还有 Cohere 的 embed 系列。选型要考虑语言支持、维度大小、收费模式。中文场景我实测下来 BGE-M3 在开源模型里表现不错兼顾多语言和检索效果如果预算允许OpenAI 的 embedding 接口胜在稳定但要注意它返回的向量维度动辄 1536存储和计算成本要心里有数。向量化之后还有一个容易漏掉的细节embedding 模型要和后续检索部署对齐。训练向量库时候用什么模型线上查询时候就必须用同一个模型否则坐标空间都不一样检索结果会魔幻到怀疑人生。2.3 检索与生成召回不是终点拼进 Prompt 才是索引完成后线上就进入检索生成阶段。用户问题进来先向量化然后去向量库做近似搜索找出 TopK 个最相近的文本片段最后把片段和原问题拼成一个带上下文的 Prompt 丢给大模型。这里有个新手最容易误解的地方检索结果不是直接返回给用户的。RAG 整个流程里最终答案是“生成”出来的而不是“查”出来的。检索只是给生成提供材料生成才是最终的答案来源。有人会问那为什么不把检索到的片段直接展示给用户原因很简单——用户的自然语言问题跟文档片段之间往往存在说法差异直接贴片段给用户体验很差。生成这一步可以让模型用更自然、更贴合问题的口吻来组织答案同时把多个片段的信息融合起来。所以完整的链路是# 伪代码演示 RAG 检索生成主流程 def rag_answer(question: str, top_k: int 5) - str: # 1. 问题向量化 q_embedding embed(question) # 2. 向量库检索 retrieved vector_store.search(q_embedding, top_ktop_k) # 3. 拼装上下文 context \n\n.join([f[片段{i1}]: {doc.page_content} for i, doc in enumerate(retrieved)]) prompt f基于以下资料回答问题\n\n{context}\n\n问题{question}\n回答 # 4. 生成最终答案 return llm(prompt)这个流程看着简单但每个环节都有不少坑。接下来的内容我挑三个影响最大的环节展开讲切分策略、embedding 选型、检索质量控制。3. 把“喂”给 Agent 的知识整理成合格的索引3.1 切分策略固定长度、递归分块与父子分块切分这件事我建议直接从三个层次去选策略从快到慢、从粗到精。固定长度切分是最简单的起步方案。指定一个 chunk_size比如 512 字符和一个 chunk_overlap比如 50从头切到尾。优势是稳定可控劣势是完全无视语义边界可能把一个完整故事拦腰截断。如果文档结构非常规整——比如每行一条日志记录那这种方案反而最合适。递归分块是适用范围最广的方案。思想是维护一组分隔符按优先级从高到低尝试切分段落级分隔符\n\n优先然后是\n、句号、空格、字符。切出来的块尽量在分隔符处断开保证语义相对完整。LangChain 的RecursiveCharacterTextSplitter就是这个思路。中文场景里我习惯把separators设成[\n\n, \n, 。, , , , , , ]这样长句不会从中间被硬切。父子分块是相对进阶的方案适合文档结构嵌套复杂的场景——比如手册里既有大章节又有层级条目。思路是维护两套块父块较大的语义单元用于提供上下文子块较细的片段用于精确匹配。检索时候先命中子块但把其所属的父块一起拼进 Prompt。这样既保证了检索精度又不丢失上下文完整性。代价是实现复杂度高一些一般等简单方案到瓶颈了再上。不讨论模型就用不了生产环境——所有切分参数都应该由实测反馈来调。我给个经验值范围供起点中文通用文档chunk_size500、overlap50起步技术手册类可以放大到1000代码类建议300-400、overlap 在30-50。跑一轮评测看检索命中率再逐步调整。3.2 Embedding 选型决定语义精度的一层Embedding 模型的差距在文档少的时候感觉不明显文档量一上来差距立刻放大。我之前做过一个对比同一批中文技术文档分别用 OpenAI 的 embedding 接口和开源的 BGE-M3 建索引在 20 个测试问题上对比检索的 Top1 命中率。结果 OpenAI 略高但差距其实不大反而是 BGE-M3 在本地部署的成本优势十分明显。适合离线场景或数据敏感型项目。选 embedding 模型时我建议盯五个指标语言覆盖度、向量维度、最大输入长度、推理速度、成本。语言覆盖度解决“文档是不是纯中文/纯英文”的问题中英混排的文档不要选纯中文模型。向量维度影响存储和计算量1536 维的向量在百万级文档量下的索引开销不是开玩笑的。最大输入长度决定了你切块能切多大——embedding 模型吞不下超大 chunk。这里插一个经验教训embedding 模型上线后尽量不要随便更换。换模型 旧库全部重建 线上检索效果重新评测。我见过有人中途把 OpenAI 的 embedding 模型从小换到大结果相似度分布整个变了相关性阈值全部失效花了一周时间重新调参。如果非换不可务必做一轮全量回归测试。3.3 向量库选型从单机到分布式的代价向量库是 RAG 的存储底座。市面选择很多但别一上来就上分布式集群绝大多数项目的真实瓶颈不在向量库而在数据质量和检索策略。个人项目或原型阶段用Chroma或者FAISS就够。Chroma 胜在轻量、Python 直接调、支持持久化几百兆的数据毫无压力。FAISS 是 Meta 开源的纯向量检索引擎不支持文档存储需要自己维护 ID 映射关系但速度快、可控性强。到了需要跟现有业务系统打交道的阶段我会优先考虑pgvector——直接在 PostgreSQL 里加一个向量类型和索引复用现有数据库的权限管理、备份恢复机制减少一个中间件对团队运维是很大的减负。当数据量到千万级以上或者 QPS 要求很高再考虑Milvus、Weaviate、Qdrant这类专用向量数据库。它们提供了分布式能力、混合检索、丰富的过滤条件但也要付出部署运维成本。有个原则供参考当你的向量库成为运维负担之前它不是瓶颈。别过度设计。4. 检索质量控制的几个硬指标4.1 TopK 到底设多少检索召回的数量直接影响答案质量。TopK 设得太少可能漏掉关键信息设得太多噪声片段混进来模型反而无所适从而且 prompt 长度暴涨。TopK 没有标准答案但有一个决策区间。一般文档场景下TopK3~5是起点。如果你的知识库有严格的分类体系、文档切分质量也很高可以缩小到2~3。但如果文档碎片化严重单片段信息密度低就要加到5~8来补足信息量。我实测下来超过 8 个片段之后答案质量的提升就很微弱了而 token 开销却线性上涨。所以与其盲目调大 TopK不如优化切分质量。有个技巧是动态 TopK根据检索分数的分布来决定返回多少。如果前 5 个片段的分数都很高说明知识高度密集返回 3 个足够如果分数普遍偏低说明问题跟资料的相关性本来就不高返回再多也是凑数。这样既节省 token又能提升准确率。4.2 相似度阈值宁缺毋滥还是宁滥毋缺检索接口返回的相似度分数必须设一个阈值来兜底。不设阈值的结果是即使用户问的问题跟知识库完全没关系系统也会硬挤几个“最像”的片段出来。模型拿到这些不相关材料又不想承认自己不知道就会开始一本正经地胡说八道。阈值的高低跟 embedding 模型的分数分布有关没有统一的 0.7 或 0.8。你需要在真实数据上先跑一批查询统计“相关”和“不相关”的分数区间然后在两个分布之间找一个分界点。这个工作看着繁琐但非常值得做它决定了你的 RAG 系统是“知之为知之”还是“不知也硬答”。实际操作里我建议双阈值策略一个高阈值用于直接答一个低阈值用于“不确定但给线索”。高于高阈值模型基于资料自信作答介于两个阈值之间模型在回答中标注信息来源的置信度低于低阈值直接让 Agent 承认知识库不足转交给其他工具或人工。4.3 混合检索让关键字和语义互补纯向量检索有一个天生盲区它擅长语义匹配但不擅长精确匹配。你问“BUG-1024 的状态”向量检索会把“BUG-1024”这个专有名词的语义权重分散到整个句子里匹配效果未必好。这种场景下BM25 这种传统的关键词检索反而更可靠。混合检索就是把两条路都走一遍BM25 负责精确匹配向量负责语义扩展两边召回的结果做融合排序最后统一去重。融合排序最简单的办法是加权求和BM25 的得分和向量相似度分别做归一化然后按权重合并。经验上关键词类问题给 BM25 高一点权重语义类问题给向量高一点权重。很多向量库原生支持混合检索比如 Milvus 支持 BM25 稠密向量混合查询Qdrant 也有类似能力。如果用的是 pgvector可以自己用 PostgreSQL 的全文检索配合向量检索完成融合。混合检索不是必须一开始就上的但它能把 RAG 系统的鲁棒性往前推一大截尤其是处理用户问法跟文档表述差异很大的情况。5. 把 RAG 真正接进 Agent 的交互链路5.1 查询改写Agent 的“嘴”和 RAG 的“耳朵”对齐用户提问是口语化的、指代模糊的而向量检索是字面匹配的。这里有个理解差。比如用户说“就是上次你跟我说的那个配置再帮我确认一下”这句话里没有任何可检索的关键信息直接拿去查知识库必挂。但 Agent 在前面几轮对话里已经提到过“RabbitMQ 的连接超时配置”完全有能力把这句话改写成可检索的查询。这就是查询改写。在 Agent 里RAG 不应该直接消费用户的原始输入而应该消费 Agent 理解后的改写结果。可以把改写看成一个小的LLM调用def rewrite_query(history: list[str], raw_input: str) - str: # 把历史上下文和当前问题一起送给 LLM让它输出用于检索的查询语句 prompt f根据对话历史和用户最新提问生成一个适合检索知识库的独立查询。 只输出查询语句本身不要解释。 历史{ .join(history)} 用户最新问题{raw_input} 改写后的查询 return llm(prompt)改写后的查询可以干很多事情补全缺失信息、把口语变成书面语、把模糊指代换成明确的实体名称、甚至拆成多个子查询分别检索再合并结果。很多 Agent 项目里 RAG 效果不好不是因为向量库不行而是因为“问题”本身没有整理干净就送进去检索了。5.2 知识库路由先想清楚去哪找再去找当 Agent 接入了多个知识库时——比如一个接产品文档库、一个接内部运维手册、一个接入工单系统——你不会希望每次提问都把三个库全查一遍。原因不只是浪费更关键的是混合结果会互相干扰。产品相关的问题里混进运维手册的片段模型就会被带偏。知识库路由就是先决定“该查哪个库”再执行检索。路由本质上是一个小分类任务可以简单到用关键词规则也可以用一个轻量 LLM 来做决策。实际项目中我更推荐用 LLM 做路由因为规则维护起来太痛苦了——知识库数量一多关键词规则之间就开始打架。打个比方Agent 是接待员知识库是各个办公室。没有路由的时候接待员逮住哪个办公室就问哪个效率极低还经常问错人。有路由之后接待员先判断“这事归谁管”然后直奔目标办公室效率天差地别。路由的决策结果还可以带上结构化参数比如“检测到用户咨询的是内存溢出问题优先查故障排查手册检索 TopK3”。这样知识库路由和检索参数联动整个 RAG 调用就更加精细了。5.3 Agentic RAG 与 ReAct 模式的融合最后聊一下 Agentic RAG——这是当前社区里热度极高的方向也是“知识获取管道”从单向管道进化为智能管道的下一步。传统 RAG 是一次性的检索一次生成一次结束。它的局限在于如果第一次检索结果不好没有人会去补救。Agentic RAG 打破了这种单次模式Agent 把检索当成一个可交互的工具先查一次看结果够不够好不够就改查询再查或者查另一个库或者把结果反馈给推理链继续思考。这就跟 ReAct 模式天然契合了。ReAct 是“推理 行动 观察”的循环——Agent 先想想现在缺什么信息然后决定调用检索工具观察检索回来的结果再决定下一步是继续检索还是生成答案。在这个模式下RAG 不再是流水线上固定的一道工序而是 Agent 手里的一个可自主支配的“情报员”。举个例子。用户问“我们的支付服务最近有没有异常的监控告警”Agent 的思考链路可能是先查监控知识库了解告警规则再查告警记录库拉最近告警发现数据不足再改写查询扩大时间范围最后结合两边的资料生成结论。整个过程涉及两次以上的检索、多个知识库的切换这是传统 RAG 完全做不到的。不过要泼一盆冷水Agentic RAG 不是银弹。多轮检索不可避免地带来更高的 token 消耗和更长的响应时间而且 Agent 的推理链路越复杂出错概率也越高。我的建议是先从传统的单次 RAG 跑通业务闭环确认知识库和切分质量没问题再逐步加 Agentic 能力。如果基础 RAG 都做得稀烂上 Agentic RAG 只会让错误更复杂。我自己的实践体会是RAG 的调优过程很像“炖汤”——材料文档、刀工切分、火候检索参数都影响最后的口味缺一环都不行。很多人一上来就追最新的向量库、最贵的模型结果忽略了最基础的文档清洗和切分。等你把基础链路跑稳、把检索质量调到位再回头看那些花哨的概念会发现其实每一步都只是把这一篇讲的内容做深做细而已。