
这是《走进 AI Agent》系列的第四篇。前三篇我们聊了 Agent 的基础循环、提示词和工具调用按理说一个 Agent 能说话、能调用 API已经可以跑起来了。但你会很快发现一个尴尬的事它回答不了关于你自己业务的问题。模型只了解训练数据截止前的大众知识不知道你们公司的产品参数、售后话术、内部制度。这个时候就需要给 Agent 补上一条知识获取管道也就是 RAGRetrieval-Augmented Generation检索增强生成。RAG 的原理不复杂先把私有文档切成小块并转成向量用户提问时先检索相关片段再把这些片段作为参考资料交给大模型生成答案。听起来就像给一个聪明的新员工配了一本工作手册而不是逼他背下整本手册。这篇文章适合正在从 0 到 1 搭 AI Agent准备把 FAQ、产品手册、企业内部文档接入 Agent 的同学。即使你不是算法工程师工程向开发也能照着动手。我会给出一条能落地的管道包含切分策略、Embedding 与向量库选型、检索重排、Agent 接入方式、效果指标以及一堆只有真正做过知识库项目才会注意到的坑。1. 先搞清楚Agent 的知识获取管道到底解决什么问题1.1 LLM 的“记忆力”靠不住别把模型当知识库很多人刚接触 Agent 时会有一个直觉既然模型见过那么多资料为什么还要单独搭知识库直接问模型不就行了我做过一个企业售后知识库项目第一次 Demo 时问模型“XX 设备的保修期是多久”模型振振有词地答了一个通用说法完全没对上我们产品手册里的真实条款。问题就在于 LLM 的“记忆”本质上是参数化的概率分布不是一张可以随时查的 Excel 表。模型训练好之后知识就冻结在权重里。你当然可以做微调但微调的本质是让模型“背”更多资料代价是昂贵、更新慢而且模型仍然会混淆不同来源的信息。更根本的问题是你根本不需要让它记住。企业内部知识每天都在变手册会改版产品会迭代制度会更新靠模型训练去追赶这种变化就像为了回答几个问题把整本书重新印刷一遍完全不合理。还有一个常被忽视的点知识割裂。大型企业里产品资料散落在 ERP、CRM、Wiki、工单系统、在线文档里。用户问了一个问题真正的答案可能要横跨三个系统产品参数在 ERP故障代码在售后库沟通口径在 FAQ。如果只靠 LLM 的“大脑”这种割裂永远解决不了因为模型连这些系统的存在都不知道。1.2 RAG 是什么检索 生成一条可回滚的“资料管道”RAG 的思想说起来很朴素回答问题之前先查到相关资料再把资料和问题一起给模型。它把知识获取拆成三个阶段索引、检索、生成。索引阶段做的是把文档切块、做 Embedding、写入向量库检索阶段根据用户问题找出最相关的知识片段生成阶段模型基于这些片段组织答案。我习惯把它理解成一场“开卷考试”。模型不再是那个必须靠记忆回答的学生而是可以随时翻资料、但必须引用资料的学生。这个转变带来三个好处第一答案可更新文档改了索引重跑一遍就行第二答案可溯源可以给用户展示“我根据哪份文档、哪个段落回答的”这对企业场景很重要第三答案可控权限可以做到文档级别不该让用户看到的内容干脆检索不到。在 Agent 架构里这条管道往往不是一个单独的函数而是被封装成工具或者服务让 Agent 在对话过程中按需调用。这也是“知识获取管道”这个名字的由来它负责把外部知识转换成 Agent 能消费的上下文而不是把它揉进模型参数里。1.3 哪些场景该用哪些场景别硬上我在决定“要不要上 RAG”时有一个非常偷懒的判断标准如果这个问题能用一段话回答且这段话来自某一份具体文档那就适合 RAG。比如产品参数、操作步骤、制度条款、售后 FAQ。反过来如果这个问题需要跨几十份文档做全局统计或者需要精确计算和事务处理RAG 就不该是第一选择。适合用 RAG 的场景不适合硬上 RAG 的场景企业 FAQ、产品手册、私有文档问答多文档全局统计、汇总报表答案要求有出处、可审计精确数值计算、数据库事务资料频繁更新需要快速同步模型已很擅长的通用常识权限隔离的知识检索需要实时一致性的业务查询检索多个知识库再交叉印证需要完整阅读整本长文档并推理举个例子生产环境里出现过“库存还有多少台”这种问题有同学上来就做 RAG把 ERP 导出成文档塞进向量库。结果模型凭记忆编了个数字差点被业务部门投诉。这种问题应该走 SQL、API或者至少走参数化工具调用而不是靠语义检索猜答案。RAG 的价值是“找到对的段落”不是“做计算”。2. 从零搭一条最小可用的 RAG 管道2.1 文档加载与切分先处理“原始料”再谈向量很多人第一次做 RAG上来就调用一个加载器把 PDF 读进来然后直接 Embedding。结果模型回答质量很差十有八九问题出在源头文档里有页眉页脚、导航目录、表格被切碎、一个完整的功能说明被拆成两半。我的经验是数据准备阶段至少要占整个 RAG 项目一半的精力。切分策略是第一个关键点。固定大小切分最偷懒比如按 token 数 512 切一段重叠 64 个 token。这种方式的缺点是容易切断语义特别是把表格、代码块、列表项切断。我现在的默认方案是“结构感知切分”先识别 Markdown 标题、PDF 章节、表格边界再按层级把内容分组。如果一段内容太长再按固定大小继续切同时保留结构信息到 metadata 里。还有一个很容易被忽略的步骤清洗。PDF 转文字经常有乱码Word 导出的文本有各种额外换行网页有导航噪声。我会先看 20 个切分后的 chunk 样本再开始 Embedding这一步省不了。如果文档里有大量表格最好先把表格转成结构化的文本描述比如“参数名-值”列表否则向量化效果会很差。2.2 Embedding 与向量库选型别一上来就追新模型Embedding 模型决定了“语义相似度”怎么算。中文场景下我常用的几个模型特点适用情况BGE-M3 / bge-large-zh开源、中文好、支持多语言和长文本大多数中文企业知识库m3e-base轻量、便宜原型验证、资源受限环境text-embedding-3-small/large通过云 API 调用、质量稳定不想本地维护模型Cohere embed-v3多语言、有 rerank 配套多语言混合场景我的建议是先选一个开源中文模型跑通全流程不要一上来就纠结“哪个分数最高”。Embedding 模型的效果差距确实存在但很多时候项目效果差是切分和检索的问题不是 Embedding 的问题。换模型之前先把上一节的数据准备做到位。向量库的选型也很容易让人纠结。我的选择逻辑如下存储方案适合场景需要注意的点Chroma / FAISS本地原型、小数据量缺权限和持久化能力生产慎用pgvector中小规模、已有 PostgreSQL事务、备份、SQL 都能复用Milvus / Qdrant千万级向量、高并发要额外运维一套分布式服务Elasticsearch / OpenSearch已有 ES 基础设施需要混合检索向量与倒排索引可以共存我个人对中小项目的默认路径是原型用 Chroma 或 FAISS进入生产后迁到 pgvector。原因很务实PostgreSQL 一套环境就能搞定结构化数据和向量数据不用再引入一个“看起来专门做向量但实际上要养一个集群”的系统。等真的到了需要横向扩展或高并发的时候再考虑 Milvus 或 Qdrant 也不迟。2.3 召回、重排与上下文装配检索质量的决定性一环管道走到检索这步第一个坑是“只用向量检索”。向量检索擅长语义相近但不擅长精确匹配你搜“RTX 4090 价格”如果文档里写的是“GeForce RTX 4090 报价”向量也许能命中但“保修 3 年”这种硬条件必须靠关键词。我的做法是混合检索同时跑向量检索和 BM25 关键词检索再把两边的结果合并去重。合并完的结果不能直接进 Prompt。我的常规操作是第一轮先用非重排方式取回 Top 50然后用 Reranker 做二次排序最后只取 Top 3 到 Top 5 的片段作为上下文。为什么不能直接用 Top 5因为向量检索是“初筛”它只做个大概Reranker 通常用交叉编码器把 query 和每个候选文档逐字计算相关度比双编码器的向量相似度准得多相当于“复试”。上下文装配也有讲究。我会在代码里把每个片段的来源标记出来让模型在回答时能引用还要控制总 token 数避免一个 Prompt 塞进几十个片段。模型注意力有限塞得太多它不仅不记得重点还会被无关信息带偏。经验值是单个片段 512 token 左右Top 3 到 Top 5 片段总上下文控制在 2000 到 3000 token。下面是基于 LangChain 的最小实现思路代码不算复杂但每一步都要理解from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.vectorstores import Chroma from langchain_community.embeddings import HuggingFaceEmbeddings # 1. 清洗后的文本 text load_cleaned_document(product_manual.md) # 2. 结构感知切分重叠是为了防止信息被截断 splitter RecursiveCharacterTextSplitter( chunk_size512, chunk_overlap64, separators[\n## , \n### , \n\n, \n, 。, , ], ) chunks splitter.split_text(text) # 3. Embedding 并写入本地向量库 embeddings HuggingFaceEmbeddings(model_nameBAAI/bge-m3) vectorstore Chroma.from_texts(chunks, embeddings, metadatas[{source: idx} for idx in range(len(chunks))]) retriever vectorstore.as_retriever(search_kwargs{k: 20}) # 4. 混合检索 Reranker 伪代码 # docs bm25_retrieve(query) vectorstore.similarity_search(query, k20) # docs deduplicate(docs) # reranked reranker.rerank(query, docs)[:3] # 5. 构建带引用的提示词 prompt f 基于以下资料回答问题。如果资料中没有相关内容请明确说不知道。 资料 {doc_text_with_sources} 问题{question} 请引用资料来源。 这个代码跑通后你已经有一条最基础的 RAG 管道了。但离“Agent 能用”还有一段距离。3. 把 RAG 接入 Agent从“一次问答”到“Agentic RAG”3.1 把 RAG 封装成 Agent 的一个工具最小集成方式很多教程把一个 RAG QA Chain 直接暴露给用户对话框这在 Agent 场景里不够用。Agent 需要的是“工具”而不是“服务”。我之前看到一个反面案例把所有知识库都拼进系统提示词里结果几个会话之后模型开始把不同知识源的答案混在一起输出非常不稳定。正确做法是让 Agent 自己决定“要不要查”。你可以把 RAG 管道封装成一个 function calling 工具然后在 Agent 的工作流里注册。工具名称建议用knowledge_query描述写清楚“用于查询企业内部文档、产品手册、FAQ 等静态知识”参数包括查询文本、过滤条件如source_type、department。这样 Agent 拿到用户的问题后会先判断该不该调用这个工具再决定怎么调用。一个简单的工具注册块长这样{ name: knowledge_query, description: 检索企业知识库中与问题最相关的文档片段返回带来源的内容。, parameters: { type: object, properties: { query: {type: string, description: 用户问题的改写或原句}, knowledge_base: {type: string, enum: [product_manual, faq, internal_policy]} }, required: [query, knowledge_base] } }把 RAG 做成工具之后你其实已经进入了 Agentic RAG 的第一层Agent 来决定“何时检索”。别小看这一步它避免了每次都把所有知识塞给模型同时让 Agent 可以按需调用多个知识库而不是只挂着一条默认管道。3.2 Spring AI / LangChain4j / Semantic Kernel 里的落地形态如果你是 Java 技术栈LangChain4j 的思路和 Python 版很像但更贴合 Spring 生态。它内置了 ContentRetriever 和 ContentInjector 接口前者负责从向量库或混合检索器里取内容后者负责把内容注入提示词。Spring AI 也提供了QuestionAnswerAdvisor和RetrievalAugmentation这类组件配置好 VectorStore 后一个 ChatClient 就能带知识库问答。C# 生态里Semantic Kernel 是常见选择。它有 Memory 能力也支持把 RAG 包装成 Kernel Plugin。我见过一个很典型的落地案例本地 ERP 的产品数据先同步到语义记忆再定义一个product_search插件Agent 对话时通过语义内核自动调用回答时附带产品编号和规格来源。关键是“本地”两个字很多制造业企业要求数据不出内网Semantic Kernel 配合本地 Embedding 模型就能做到。另外一个我最近看到的趋势是“RAG as a Service”。比如 AgentScope 2.0 就把 RAG 做成服务而不是算子Agent 不需要知道知识存在 pgvector 还是 Elasticsearch只要按统一接口声明数据源后台管道负责同步、切片、索引。这种形态对多 Agent 协作很友好一个团队维护一套知识服务多个业务 Agent 复用知识割裂问题会少很多。3.3 Agentic RAG多跳检索、查询改写、路由与自反思基础版 RAG 有个很明显的天花板只能做一次“问题→检索→回答”如果第一轮检索结果不好它不会自救。举个例子用户问“上周报修的门禁今天又坏了”答案要同时来自工单系统和产品手册。基础版 RAG 如果只搜“门禁”可能拿到一堆不匹配的资料。Agentic RAG 的思路是让 Agent 自己做规划循环最多 3 次 rewritten_query 查询改写引擎(用户问题 上下文) kb 路由引擎(rewritten_query) # 选择知识库或混合源 chunks 混合检索(rewritten_query, kb, top_k20) chunks rerank(query, chunks, top_k3) answer, references 生成(rewritten_query, chunks, 用户问题) if 校验函数(answer, chunks) 通过: 返回 answer else: 根据校验反馈调整查询继续循环这个循环里有四个可以落地的组件查询改写、路由、校验、迭代重检。查询改写解决“问题表述模糊”的问题比如把“那台设备”改成具体型号路由解决“多个知识库怎么选择”的问题校验解决“模型有没有忠实于资料”的问题。我在生产里最常用的是校验这一步让模型自己回答“这个答案有没有引用依据”如果没有就再查一次或者直接告诉用户没有找到。但要提醒一下不是所有项目都要上 Agentic RAG。如果 90% 的问题一次检索就能答对硬上多跳循环只会增加延迟和成本。我通常先把基础 RAG 上线用数据评估确认有一部分问题确实需要多跳检索之后才逐步加 Agentic 组件。4. 效果怎么衡量别让知识库成为“看起来有用”4.1 基础指标Hit Rate、MRR、NDCG 怎么算怎么读判断 RAG 好坏不能只看“用户觉得回答挺顺”因为模型的表达能力强它可以把错误答案说得非常自信。我建议在进入 Agent 之前先建立一套离线评测集挑 100 个代表性问题每题标注“正确答案出自哪一段原文”。然后用下面的核心指标来量测。指标计算方式说明Hit RateK检索结果 Top K 中是否存在正确片段数值越高说明“答案没被找丢”MRRK正确片段在结果中排名倒数的平均值越靠前越好对排序质量敏感NDCGK按相关性等级加权的折损收益适合一个 query 对应多个相关片段的情况Faithfulness / 忠实度生成答案中有多少比例能对应到检索片段防止模型自己编造举个例子共有 3 个测试问题。第一个问题正确答案在第 2 位第二个在第 1 位第三个没找到。那么 Hit Rate5 2/3MRR (1/2 1/1 0)/3 0.5。这个例子说明Hit Rate 告诉你“能不能找到”MRR 告诉你“找得快不快”两个都重要。实际操作中我发现单独看 Hit Rate 会掩盖排序问题。检索系统把正确答案放在第 20 位只取 Top 5 进 Prompt 时照样答不出来。所以我更推荐同时关注 MRR并且把“Top K 进 Prompt 的答案正确率”作为业务指标和用户问卷反馈一起看。4.2 上线后最容易翻车的 4 个问题症状、原因与动作我整理了知识库 RAG 项目上线后最常遇到的四类问题。对照排查比瞎调参数有效得多。症状常见原因排查动作答非所问明显跑偏切分太碎或者检索时没有混合关键词检查人工标注的正确片段是否在 Top 20 里答案空洞缺乏细节只做向量检索忽略了精确匹配加 BM25 混合检索提升关键信息召回多个知识库互相混淆没有按 metadata 过滤权限和业务域检索时强制加source_type过滤条件模型明明没找到答案却编造阈值过低把不相关片段也塞进上下文提高重排后阈值或让模型对未命中来源拒绝回答第一个问题的排查方法很简单写个小脚本把测试 Question 的 Top 20 检索结果打印出来看。如果正确答案确实不在里面说明是语义检索的问题优先调 chunk 策略和 Embedding 模型如果正确答案在里面但生成答案不对说明是重排或上下文装配的问题。这两个问题的修法完全不同千万别混在一起调。还要留意一个隐蔽问题片段是正确的但过短、缺上下文。比如 FAQ 里“保修期 12 个月”单独成一个 chunk模型看到这句能答但要解释“保修期从何时起算”片段就答不上。这类问题要靠“父子切分”或“段落扩写”解决命中子片段时把父段落一并作为上下文。4.3 从数据割裂到知识图谱GraphRAG / Ontology RAG 什么时候上当你的知识库规模变大问题从“搜不到”变成“多个实体之间的关系理不清”时基础 RAG 会显得吃力。GraphRAG 是一种解决方向把实体和关系抽取成图结构先对每个社区做摘要回答全局性问题时可以沿图路径检索。它特别适合回答“哪些供应商和我们同时合作过”“这个故障影响了哪些产品线”这类问题。但我对 GraphRAG 的态度比较谨慎。它构建成本高抽取实体关系要跑大模型而且图结构维护比向量库重得多。对大多数企业知识库来说先用 Ontology本体解决“术语统一”更划算。所谓 Ontology RAG本质上是先建立一套企业知识的共同语言。比如产品域里有“SKU”“规格”“故障码”售后域里有“工单”“报修时间”两边叫法不同检索时系统需要知道“故障码”和“错误代码”是同一个东西。我曾经在 ERP 产品检索项目里做过一件事把产品资料字段统一映射到一个本体模型再在检索时用所有同义词和上级概念一起召回效果立竿见影而且不需要跑复杂的图算法。所以我的顺序是遇到知识割裂先做术语和元数据的统一也就是轻量 Ontology还不够再考虑引入 Knowledge Graph只有急需回答全局性关系问题且团队有人专门维护时再上 GraphRAG。热搜里看到的“ONTOLOGY RAG”“GRAPH RAG”都值得关注但不要把它当成银弹。5. 工程化避坑与我的实操经验5.1 版本对齐Embedding 换库、模型换代的连锁反应这条坑我至少踩过三次。项目早期用了一个轻量 Embedding 模型向量库里已经存了几万条向量。后来觉得效果不够好换成了更强的 BGE-M3结果发现检索效果不升反降。原因很简单向量库里的旧向量还是旧模型生成的新旧向量没有对齐相似度计算自然没有意义。换 Embedding 模型的正确做法是全量重新索引。任何存储起来的向量都只能和同版本模型产生的向量做对比。所以生产环境里我通常把文档原文和 metadata 也存下来方便随时重建索引。同一条文档记录尽量带版本号换模型后只需要触发一次异步重建任务不需要手工导数据。还有个小技巧不要在服务运行过程中“热切换”Reranker。Reranker 和 Embedding 一样版本变了整个检索流程的分数分布就变了。至少要准备一版离线回归测试跑完测评集再切线上。5.2 权限、数据更新与缓存容易忽略的三种“边界”RAG 接入 Agent 后权限隔离是最容易被忽略的问题。我在项目里见过把“内部薪酬制度”docs 放进知识库结果前端用户问几句就套出了敏感内容。基础教训是检索时必须在 metadata 上做文档级权限过滤。每个 chunk 都要带上“可见部门、岗位、角色”字段查询时根据用户身份拼一个过滤条件。这一步不能放在生成后再过滤而是在检索阶段直接用向量库的 filter 能力把不可见内容排除掉。数据更新的频率也要想清楚。有的知识库一天更新一次就够了有的需要实时同步。我的建议是不要对单个文档做“即改即查”因为增量索引的实现会引入很多复杂度。先用定时全量重建比如每天凌晨跑一次等业务方反馈实时性不够再增量。缓存这条更偏工程化。同一个问题短时间内被不同用户反复问是知识库最常见的流量模式。可以在 Agent 调用 RAG 工具之前加一级查询缓存缓存 Key 用“归一化后的问题 用户权限域”这样既省向量库负载也降低模型 API 成本。但要注意数据更新后需要主动清理涉及该知识的缓存。5.3 如果你现在要开始我的建议路线如果你新接手一个 RAG 项目我建议按这个顺序推进第一步先不要追求框架把 20 份典型资料用 Python 脚本清洗、切分肉眼检查 chunk 质量。第二步用开源 Embedding 本地向量库跑通最小管道不接 Agent直接在脚本里看检索结果。第三步构造 100 条测试问答记录 Hit Rate 和 MRR调整切分策略和混合检索。第四步再把 RAG 包装成 Agent 工具让模型学会调用knowledge_query。第五步上线后持续收集用户反馈把“没答对”的 case 补回测试集定期回归。这个顺序的好处是每一层都有可验证的产物不会出现“整个系统跑起来但不知道哪儿不对”的失控状态。很多项目死在“一开始就搭了一个豪华框架结果数据一塌糊涂”的路上。最后分享一段个人体会我做知识库项目最深的感触是RAG 90% 的问题出在知识侧的整理而不是模型侧。你可以换最好的 Embedding、上最贵的模型但文档切片不对、检索阈值不调、权限没过滤一切白搭。项目最开始的那几天多花点时间在地板上翻资料、清数据比研究各种花哨框架值钱得多。这也正是知识获取管道这个叫法的分量所在它首先是一条管道其次才是 AI。