在《走进 AI Agent》系列的前几篇里我把 Agent 拆成了规划、记忆和工具调用三个模块来聊。到了第四篇我决定把知识获取管道单独拎出来因为它才是真正决定 Agent 输出质量下限的东西。我自己在团队里搭过几个 AI Agent一开始总觉得是选型模型不行反复排查以后才发现很多一本正经的胡说八道根本原因就是 Agent 没有一条稳定的 RAG 管道模型在生成回答时压根没拿到足够可靠的最新外部知识。RAGRetrieval-Augmented Generation检索增强生成说白了就是在让大模型开口之前先从外部知识库里捞出和问题相关的片段把这些片段拼进 Prompt让它参考材料作答。这技术现在已经不算新鲜但放在 AI Agent 的语境下它是一切知识型功能的基础管道。企业内部问答、客服、研发辅助、文档助理几乎都绕不开它。这篇我打算从原理、基础链路、落地代码、Agent 集成再到调优评估完整过一遍哪怕你是第一次接触 RAG也能照着搭出一个能用的知识获取管道。1. 为什么AI Agent离不开知识获取管道——先聊清楚“知识从哪来”1.1 大模型的两种知识形态先问一个问题一个训练好的大模型它的知识到底存在哪如果你用过对话产品第一反应可能是“它什么都懂”。但严格来说模型拥有的只是参数化知识——经过训练把海量文本压缩进了几十亿甚至几千亿个参数里。这种知识有三个天生的短板训练数据有截止时间、不会自动更新、低频或私有信息几乎学不进去。所以当你问某个业务文档里出现过但训练集完全没覆盖的具体问题时它只能靠上下文去猜猜不出来就编。另一种知识形态是上下文知识。模型在生成时只能看到当前 Prompt 和对话历史里提供的信息。我们想让它“学会”某份新文档最快的方式不是重新训练而是把这些内容塞进上下文。RAG 做的事情就是帮我们从海量文档中自动挑选最相关的一部分塞进上下文而不是把整份文档都丢进去。把这个过程理解成“知识获取管道”会更准确数据从文档进入经过清洗、分段、向量化然后被检索出来最终进入模型生成空间。打个比方这就像请一个经验丰富但不了解你们公司的咨询顾问做项目他能力再强也必须先拿到足够的项目资料否则只能泛泛而谈。大模型本身什么都知道一点可真到具体业务场景里没资料就胡说八道才是常态。1.2 RAG在Agent里解决的核心问题在 AI Agent 场景里RAG 通常解决三个问题事实性、时效性和领域性。事实性主要指防止幻觉。模型拿不到准确材料就会在细节上出错比如把设备型号说错、把流程顺序讲反、把负责人张冠李戴。时效性问题更常见产品最近的版本状态、新上线的服务地址、本周的运维公告训练数据里根本不可能有。领域性则是指企业私有知识——合同条款、内部流程、历史决策、技术架构勘误这些信息模型永远无法通过预训练学到。还有一个容易被忽视的问题可审计性。客户或者老板会问你 Agent 给出的结论到底是从哪来的如果没有 RAG这个很难回答但如果有了管道你可以把命中的文档、章节和 chunk 一并返回至少做到证据链可查。这一点在企业场景里特别重要甚至比答案本身更重要。1.3 RAG vs 微调为什么Agent阶段优先用RAG有人可能会问那我直接拿文档去微调模型不就行了。先说结论在做 AI Agent 的现阶段绝大多数情况下应该优先考虑 RAG而不是微调。微调是对模型权重做有监督训练它适合改变模型的语气、输出格式、领域术语偏好但用它来灌入事实知识会有几个硬伤数据清洗成本高、训练周期长、知识一更新就得重新微调一遍而且微调后的模型仍然可能记不住新事实也依然会幻觉。RAG 则把知识放在外部系统里更新文档就等于更新知识随时随地生效。从架构上看它和工具调用一样是 Agent 可插拔能力的一部分。你可以给知识库加一个索引、删一个目录而不需要动模型本身。当然了RAG 和微调也不是非此即彼。我的经验是先把 RAG 跑起来解决事实问题再判断是否需要微调来适配表达风格。如果团队资源有限RAG 的性价比通常高得多。2. RAG基础链条拆解——索引、检索、增强、生成四步是怎么走的2.1 索引阶段文档清洗与切片策略RAG 基础链条可以拆成四步索引、检索、增强、生成。索引是把文档变成可检索形式它最枯燥但往往也最影响最终效果。索引包含三件事加载、清洗、切片。加载没什么好说的PDF、Word、Markdown、网页该写的解析代码写清楚就行。真正花时间的是清洗。我遇到过一份两百页的架构文档PDF 里带页眉、页脚和页码直接切进去之后每个 chunk 都混着“内部资料”和页码噪声检索时全是干扰。清洗至少要处理页眉页脚、重复段落、乱码、表格结构调整必要时做 OCR 纠错。这不是锦上添花而是避免噪声进入向量索引的关键步骤。切片是重中之重。最简单的是按固定字符数切比如每个 chunk 512 个字符相邻 chunk 重叠 50 个字符。这样做的好处是简单缺点是很经常把句子的逻辑砍断——一段讲背景后半段讲方案检索到后半段时模型完全不知道前提是什么。更稳妥的做法是按文档结构切先识别 Markdown 标题或 PDF 里的章节层级把同一个二级标题下的内容作为一个大块如果还是太长再按段落或固定长度二次切分overlap 设置为 5% 到 10%。chunk 大小没有标准答案。太大每次塞入的信息多但 Embedding 向量会被平均化检索精度下降太小召回可能缺上下文。如果文档句子普遍较长512 到 800 个字符是我常用的区间API 文档和代码块我倾向按代码块或函数块切同时保留文件名和路径作为元数据。这样到后面调试时才能知道每次检索出来的内容到底来自哪一章哪一页。2.2 嵌入与向量化怎么把文本变成数学空间里的坐标切片之后要做嵌入也叫 Embedding。它的作用是用模型把每段文本映射到一个向量空间。可以粗糙理解成它在数学空间里给每段文本标了一个坐标语义相近的文本坐标距离会更近。这样检索时就不是简单的关键词匹配而是“语义匹配”。比如用户搜“如何部署”可能能匹配到文档里的“发布上线流程”。选 Embedding 模型有几个考量点语言支持、向量维度、更新频率、部署成本。中文场景我常用 BGE 系列或者智源的文本向量模型预算充足也可以直接用商业 API。需要注意入库时用的模型必须和查询时用的模型是同一个版本否则坐标空间都不一样检索效果会莫名崩掉。向量维度越高保存的语义信息通常越丰富但检索速度和存储成本也会上升。一些规模很大的知识库还会做降维但那是后话。还有一点经常被忽视长文本和短文本的检索策略要区别对待。Embedding 模型吃长文本容易稀释关键语义所以入库时我不会把整个大章节一次嵌成一个向量而是切成小段但我会把上一级标题、摘要等元数据挂在 chunk 上检索时就能做层级过滤。比如先定位到“第三章 存储架构”再从中取相关段落比直接全局比对要准得多。2.3 检索阶段向量检索关键词检索重排检索阶段是整个管道里效果提升空间最大的部分。最基础的方案是纯向量检索拿到用户问题的向量在向量库里按相似度取前 K 个。它擅长语义匹配但对精确实体和唯一标识符不敏感。你问“哪个版本的 API 支持 XX 功能”如果文档里用全称、问题里用简称纯向量不一定能把对应编号准确捞出来。这时候关键词检索就有意义了。生产环境里我强烈建议做混合检索向量召回 BM25 关键词召回再用 RRF 或加权分数合并。BM25 负责精确词汇匹配向量负责语义扩展两者互补。举个例子文档里写“OSS 存储桶”问题里写“对象存储”向量能建立联系但如果问题里有具体的订单号、服务名称BM25 能保证这些精确串不被漏掉。拿到候选内容之后还需要重排。向量库用的通常是双塔模型速度快但精度一般重排器用交叉编码器把 query 与每个候选句子同时输入模型算出一个更准的相似度分数但是速度慢。所以一般先用向量加 BM25 粗排取 Top 50再用 Rerank 精排取 Top 3-5。这个 Top K 不是越大越好——塞进 Prompt 的上下文如果都是不相关内容模型反而会被带偏。如果 context window 足够大我会给 Top 3 的正式上下文另外放一个可选参考区让模型只在需要时使用。2.4 增强与生成让LLM“带着资料说话”检索到内容后进入增强和生成。增强就是组装 Prompt系统指令、材料区、用户问题三个部分要清晰分开。系统指令要明确“你是一名根据材料回答问题的助手”材料区要标注每个片段的出处用户问题里可以要求“如果材料没有覆盖请明确说不知道”。这个设计看似简单但很多 RAG 效果差恰恰是在这一步没用对。我有两个实际建议。第一上下文里要保留 chunk 的来源信息比如文件名、章节、页码。模型在组织回答时如果能看到来源会更倾向忠于原文。第二不要把所有命中的 chunk 一股脑塞进去。我给每个 chunk 加序号Prompt 里写“只能基于上面的材料 1-3 回答不要引用材料之外的信息”这样能显著降低模型自行扩展的概率。生成参数也要控制。越底层的知识型问答温度越低越好我一般设在 0.1 以下需要创造性头脑风暴时再考虑调高。长回答容易引入不确定我会让模型先给结论再给依据把来源标识成 [1][2] 这样的引用格式。这样用户既能快速得到答案又能追踪到依据才是知识获取管道该有的样子。3. 落地一个最小RAG管道——从空项目到能回答公司文档问题3.1 技术选型我用了什么组合前面讲了原理下面进入工程落地。一个 RAG 管道至少需要四样东西文档加载与切分工具、Embedding 模型、向量数据库、LLM。我的示例会采用 Python 加 LangChain 生态因为它封装了加载器和向量库对接十几行代码就能跑通。如果你不想用框架用原生 Python 也能做无非是多写一些调用逻辑。向量库的选择我按场景做了分类刚学习或者内部 Demo 用 Chroma 或 FAISS不需要运维团队级知识库用 pgvector 或 Milvus如果已经有 ES 集群也可以直接用它自带的向量能力。个人项目最省事的是 Chroma按 pip 安装就能用。Embedding 模型我会用 HuggingFace 上的 BGE 中文模型或者用本地 Ollama 加载开源嵌入模型。LLM 部分可以接任意大模型 API也可以完全本地方案。示例里的重点不是选哪家大模型而是把整条流程拉通。3.2 数据准备与切分参数实例我们以一份《公司技术架构白皮书.pdf》为例。第一件事是解析用 pypdf 或 unstructured 库读取把每一页拼接成文本。接着做清洗去掉页眉页脚、页码、重复章节名。然后按结构切分先识别标题层级把二级标题作为边界再按每段长度二次切分。如果最终文本超过 800 字符就切成两个 chunkoverlap 设 64 字符这样既保留了章节语义又控制了向量化长度。实际操作里我会先按段落拆并记录每个段落在原始文档中的位置再把临近段落合并到 512 字符左右。不要小看“记录位置”这一步后面所有调试都要靠它回溯到原始 PDF。一旦检索结果有问题你能立刻定位是哪一页哪一个标题下的内容而不是面对一堆无法追溯的字符串。3.3 构建向量索引的代码示意下面是一段最小可用的索引构建代码用 LangChain 加 FAISS 实现from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceBgeEmbeddings from langchain_community.vectorstores import FAISS # 1. 读取并清洗文本 raw_text extract_text_from_pdf(company_architecture.pdf) raw_text clean_header_footer(raw_text) # 2. 切分按结构 长度 splitter RecursiveCharacterTextSplitter( chunk_size512, chunk_overlap64, separators[\n# , \n## , \n### , \n\n, \n, 。, .], ) chunks splitter.split_text(raw_text) # 3. 向量化 embedding_model HuggingFaceBgeEmbeddings( model_nameBAAI/bge-m3, encode_kwargs{normalize_embeddings: True}, ) # 4. 写入向量库 vectorstore FAISS.from_texts(chunks, embedding_model) vectorstore.save_local(kb_index)这段代码不到二十行但已经能跑通。注意两个点separators 我把 Markdown 标题放在最前面是为了让切分先按标题层级断而不是硬按长度断normalize_embeddings 打开之后后续相似度计算更稳定。实际项目里你可能还要在 chunk 上关联 metadata比如章节路径、文档 ID、更新时间这在后面做过滤和引用时会方便很多。3.4 检索问答的完整流程编排索引建好之后查询端流程就是用户问题进来生成问题向量做向量检索加关键词检索重排拼 Prompt交给 LLM最后返回答案和引用。代码示意如下question 我们的对象存储支持哪些访问协议 # 向量检索 docs vectorstore.similarity_search_with_score(question, k20) # 混合检索向量 BM25 bm25_retriever create_bm25_retriever(chunks) ensemble_retriever EnsembleRetriever( retrievers[vectorstore.as_retriever(), bm25_retriever], weights[0.6, 0.4], ) candidates ensemble_retriever.invoke(question) # 重排示意 reranker CustomReranker() top_docs reranker.rerank(question, candidates[:50])[:3] # 组装 Prompt context \n\n.join( f[{i1}] {doc.page_content}来源{doc.metadata[chapter]} for i, doc in enumerate(top_docs) ) prompt f请基于下面的材料回答问题并在答案后标注命中材料的编号。 材料 {context} 问题{question} response llm.invoke(prompt)这里混用了几种示意实现真正的工程肯定更完整但我想强调的关键点是top_docs 的 metadata 要跟着走因为后面做引用溯源全靠它。我自己在初期会把每次查询的命中路径记下来包括召回分数、重排结果、最终 Prompt方便离线分析效果。4. 把RAG接进AI Agent——从“查资料”到“用工具”4.1 Agent如何以工具方式调用RAG前面讲的是把 RAG 作为一条单向管道。但放进 AI Agent 后RAG 很少直接暴露给用户而是被封装成一个“工具”。Agent 的核心循环是接收任务、规划、调用工具、观察结果、继续行动。RAG 工具只是这些工具中的一种与计算器、搜索、代码执行并列。这样设计的好处是Agent 可以自主决定什么时候该查知识库。比如用户问“今天有什么待办事项”Agent 看完日历和邮件就够了不需要查知识库但如果用户问“合同审批流程是什么”Agent 就会把 RAG 工具加入计划。这种路由逻辑不是硬编码而是交给 LLM 根据工具描述判断。在 LangChain 系里我会用 tool 把检索函数包起来给一个清晰的名字和 description。description 写得好不好对 Agent 影响非常大。不要只说“检索文档”要说“当用户询问公司内部流程、产品文档、技术规范时使用这个工具查找相关知识”。描述越具体Agent 越知道什么时候用、怎么用。4.2 Agentic RAG是什么让Agent自己决定查几次、怎么查如果只做一次检索然后生成答案这是最基础的 RAG。但实际业务问题经常没那么乖。比如用户问“A 项目的部署时间和 B 项目的负责人有关系吗”单一查询可能什么都捞不到。这就需要 Agentic RAG让 Agent 在推理循环里反复检索必要时改写问题、拆分子查询、回溯结果直到能组合出答案。本质上Agentic RAG 不是新算法而是把 RAG 从“一次性召回”升级成“Agent 自主使用检索工具”。它可以做三件事查询改写把口语化问题改成更符合文档表述的关键词多轮检索第一次没找到就换角度再查多跳推理拆成两个子问题分别查再综合。这些能力在 LangChain 的 create_retrieval_agent 或自定义 ReAct 循环里都有现成组件。我建议按问题复杂度决定是否上 Agentic RAG。如果 80% 的问题是单跳问答普通 RAG 加路由就够了硬上一个会思考的检索循环反而增加延迟和成本。团队可以把 Agentic RAG 做成可选的增强模式根据置信度触发而不是默认全开。4.3 与其他知识管道的边界GraphRAG、Ontology RAG何时用随着 RAG 被讲得越来越复杂市场上出现了 GraphRAG、Ontology RAG、Semantic Kernel 这些概念。但我需要给一个清醒的判断它们都不是替代基础 RAG 的银弹而是在不同知识结构下对基础 RAG 的补充。GraphRAG 适合关系密集型分析比如多实体之间的关联、社区发现、全局性问题总结Ontology RAG 适合有强领域建模的场景比如医疗、金融这些概念关系稳定的行业。基础 RAG 的适用面仍然是最广的。做架构选型时我建议从“最小可用”开始先用标准 RAG 加元数据过滤。如果发现频繁需要跨多个文档找深层关系再考虑图谱或递归检索而不是一开始就上 GraphRAG。工具越复杂维护成本越高而 Agent 本身已经够复杂了。很多时候把基础 RAG 的切分和混合检索调好效果提升比换一个高级方案更明显。5. 说点踩坑后的实在话——RAG效果调优与评估清单5.1 用Hit Rate和上下文相关性评估检索质量很多团队做完 RAG 就开始内测靠感觉说“效果不错”。这样在规模小的时候还行文档一多问题就会变隐蔽。我建议至少要建两个评估指标Hit Rate命中率和上下文相关性。Hit Rate 是指在评测集里系统检索出的 Top K 内容中是否包含真正能支撑答案的文档或 chunk。做法是给每个测试问题标注标准答案对应的 chunk然后让检索器跑一遍统计命中比例。如果 Hit Rate 低问题大概率出在切分、Embedding 或者混合检索上。上下文相关性是强一些的评估把检索结果直接丢给 LLM让它评分判断这些上下文是否足够回答用户问题。这个指标偏低可能是 Top K 太小、重排器质量差或者问题本身需要多跳检索。测试集不需要多20 个真实问题就能暴露问题。关键是要覆盖不同类型名词解释、流程说明、具体参数配置、跨文档对比每种来几条才不会偏科。5.2 常见效果差的根因切片、Embedding、元数据我把踩过的坑整理成一张排查方向表遇到效果不好可以直接对着找。表现常见根因排查动作回答内容发散、不贴材料Prompt 里没有强制约束引用来源增加系统指令限制只能基于已有材料降低 temperature明明有答案但检索不到切分把关键上下文切断Embedding 粒度太大减小 chunk_size增加 overlap改用结构切分专有名词、编号查不到纯向量检索丢精确词加 BM25 关键词召回确认切分没有把编号和正文分离不同业务的问题互相串知识库没有做分区或过滤引入文档类型、租户 ID 等元数据检索时强制过滤每次答案不稳定模型生成温度过高或随机性太大温度拉到 0.1 以下固定随机种子增加引用约束这些根因都不涉及什么高深模型反而是基础链条里的细节。很多问题用元数据过滤和 Prompt 约束就能解决先别急着换大模型。5.3 我的几组实测调优经验最后分享几个亲测有效的小技巧。第一chunk overlap 加到 5% 到 10% 通常能明显提升召回但别超过 15%否则重复内容会污染向量索引。我还试过给章节标题单独建一个“标题索引”查询时先命中标题再拉内容对于长文档特别有用。这招本质上就是在检索前先缩小范围很朴素但效果很实在。第二混合检索比纯向量稳定得多。我在内部测试里纯向量在口语化问题上的命中率可能有 80%但遇到精确编号直接降到 30% 以下加上 BM25 之后才恢复正常。RRF 融合分数的公式不需要调太复杂权重微调的影响却很大所以不要迷信参数越复杂越好。第三重排器是投入产出比很高的环节。向量召回 Top 50 里可能只有 5 个真正相关重排能把这些选出来。如果暂时没有重排模型我会把 Top K 提高到 8 到 10 段一起塞给模型让模型有更大机会看到正确信息有了重排器之后 Top 3 就够。这算是一种在没有精排条件下的妥协方案。最后一招对 Agent 场景把 RAG 工具的返回值设计成两个字段answer 和 source_chunks。LLM 生成的答案归 answersource_chunks 给前端展示引用。这样产品做可信展示时会更方便也方便人工审计。用户问服务器配置时前端可以直接展示“回答基于/docs/技术架构白皮书.pdf 第 3 章”信任度会明显提升。我在实际项目里体会到知识获取管道不能只被理解成一个检索接口它是一整套从数据到答案的流程。任何复杂系统都从这套基础开始。如果你正在搭 AI Agent先别急着把各种 Agent 框架堆上去先回到这条管道把每条路口梳理清楚。数据进去知识出来Agent 才能站得住脚。