如果说前三篇是在给 Agent 搭骨架、装器官那这一篇开始真正让 Agent“变聪明”的部分才正式登场。作为系列第四篇主题是知识获取管道RAG。RAG 全称 Retrieval-Augmented Generation中文一般叫检索增强生成。它不是什么神秘的单一模型而是一整套让大模型从外部资料中取知识、再组织答案的工程方案。我第一次接触 RAG 时以为就是把 PDF 切一切、向量库存一下、查出来拼进 prompt 就完事。真到了几十个业务文档、几百个常见问题摆在我面前的时候才发现光是“怎么切文本”就能折腾我一整周。这篇内容主要服务两类人一是正在做 AI Agent 开发、想给 Agent 接企业知识库的开发者二是刚入坑 RAG、希望从原理到落地建立起完整知识框架的算法工程师。花二十分钟读完你应该能回答三件事为什么 Agent 一定需要一条可靠的知识获取管道RAG 管道里每个核心环节到底在做什么以及遇到召回不准、答案跑偏的时候该用什么思路去定位问题。文中所有参数、策略和排查经验都来自我实际项目里反复试错后的结论不一定是最优解但一定是一条稳妥的起步路径。1. 为什么 AI Agent 需要一条“知识获取管道”1.1 模型再强也缺“私有知识”先拆一个底层问题大模型本身有没有知识有但它的知识是训练时固化进参数的属于“封闭世界”。它知道你训练数据截止日期之前的大部分公开内容却完全不知道你们公司的内部制度、某个ERP系统里的产品库存、上个月刚发布的新品参数。更麻烦的是模型面对不知道的问题时不会老实说“不知道”而是会一本正经地开始编这种表现叫“幻觉”。Agent 的记忆体系通常分三层会话窗口里的短期记忆、长期数据库里的语义记忆、以及调用外部工具时获取的信息。RAG 本质上是把语义记忆层和外部世界打通的那条高速路。没有这条路Agent 就是一个只有常识、没有“档案柜”的工作人员你问它内部文件上的事它只能现场发明一个答案给你。有了这条路Agent 可以先查档案再动嘴回答。1.2 RAG 是什么开卷考试的工程化理解 RAG 最简单的类比是开卷考试。闭卷考试是纯靠脑子里的知识答题对应只靠模型参数作答开卷考试允许你翻教材、查笔记答题前先找证据。RAG 就是把这套“翻教材”的动作工程化用户提问后系统先去知识库里检索出最相关的几段内容再把“问题 检索到的内容”一起交给大模型要求它仅凭资料回答并允许它引用出处。这个方案和微调相比优势非常明显。微调要准备大量标注数据、重训模型、重新部署而且知识一更新又得再来一轮RAG 只需要替换知识库里的文档、重新生成索引模型本身完全不用动。企业场景下文档三天两头更新RAG 这种“改资料不改模型”的特性让它成了知识密集型应用的默认选择。1.3 从经典 RAG 到 Agentic RAG经典 RAG 是一条静态流水线拿到 query向量检索 top-k 结果拼进 prompt生成答案。简单问题这么干足够但复杂问题就不行了。比如“A 项目的负责人上季度负责的产品有哪些”你至少需要先查到项目负责人再查这个人负责的产品列表中间还涉及“上季度”这个时间过滤条件。这种多跳查询静态流水线根本处理不了。所以现在的趋势是把 RAG 嵌入到 Agent 的推理循环里这就是大家常说的 Agentic RAG。它不再只是“检索一次、回答一次”而是把检索封装成一个 Agent 可以反复调用的工具由 Agent 自主决定要不要查、拆成几个子查询、是否要结合数据库和其他工具。两者的差别我整理成了下面这张对照表维度经典 RAGAgentic RAG流程单次检索、单次生成多步推理、循环检索查询处理直接使用原始 query可改写、拆解、补充条件知识来源单个固定向量库多个知识库、数据库、API 工具关系推理较弱难以多跳关联可配合图谱、结构化查询可解释性有出处可查有完整推理链路可追踪理解这个演进路径很重要因为后面第五部分我会专门展开 Agentic RAG 的扩展方向。但不管 Agent 怎么编排地基仍然是那条稳定的 RAG 管道所以接下来四节我们先把这条管道拆到螺丝钉级别。2. RAG 管道的核心环节拆解2.1 数据接入与清洗管道的第一个坑RAG 管道的第一段不是向量检索而是“原料处理”。你手里的资料可能是 Word、PDF、Markdown、HTML、Excel甚至是从旧系统导出的纯文本。每种格式都有各自的毛病。我用 PDF 举例很多 PDF 页脚带着公司名和页码检索时这些内容会被当成正文表格跨页就会被硬生生切成两半语义全乱扫描件更是直接给你一堆图片不 OCR 连字都提不出来。我的习惯是先“抽文本、再清洗、后切分”三步分开做。抽取文本时统一转成 Markdown把表格转成 Markdown 表格标题层级保留图片里的文字能 OCR 就 OCR不能 OCR 至少留个图注。清洗阶段主要做几件事去掉页眉页脚和重复水印修正错误的换行把无序列表规整删除明显无关的封面页。很多人忽略另一个细节给每个文档块保留元数据。source 文件名、页号、章节标题、更新日期这些字段在检索后太有用了。调试时你要能追到“这句话到底出自哪一页”线上运行时你要能按时间过滤旧文档。我见过太多项目向量库里只有一堆裸文本出了问题根本没法排查。记住清洗环节省的时间最后都会在检索质量上加倍还回来。2.2 切分策略决定召回上限的因素切分是 RAG 里最容易被低估的环节。为什么必须切分一是向量模型的上下文窗口有限二是检索的基本单元必须语义独立。如果一整章几千字塞进一个向量检索时很难精准定位到你问的那一小段如果一句话切得太碎又丢失了上下文召回之后模型也看不懂。这就是切分的矛盾单元太大精度下降单元太小语境缺失。常见的切分方式有三种。第一种是固定长度切分按字符或 token 数硬切最省事但经常把一句话拦腰截断。第二种是递归字符切分先按段落切再按句子切尽量靠近自然边界这是我现在的主力方案。第三种是按语义切分用 embedding 判断哪些句子该聚在一起效果好但计算成本高一般放在数据量不大、对质量要求高的场景。参数上我的经验值是这样的中文场景下 chunk_size 设成 500 到 1000 个字符比较稳注意我叫它“字符”而不是“token”因为嵌入模型切的是 token中文一个字大约对应 0.6 到 0.8 个 token你要真按 token 设800 token 大约等于一千多字。chunk_overlap 设 50 到 100 个字符作用是把前一段尾巴上的关键内容留给下一段防止语义在切口处被割断。如果文档本身是 Markdown优先用按标题结构切分的解析器比如把每个小标题下的内容作为一个候选块这比盲目按长度切合理得多。2.3 嵌入模型与向量检索切分完的文档块要变成计算机能算的东西。嵌入模型干的事就是把这些文本映射成一串向量让语义相近的文本在向量空间里距离也相近。这个“距离近”就是向量召回的基础把你的问题也向量化然后在向量数据库里找最接近的文档块。嵌入模型怎么选关注三个指标语义能力、中文支持度、向量维度。语义能力决定它能不能理解“苹果”和“水果公司”这类同义表达中文支持度决定它在中文分词、专有名词上的表现向量维度影响存储成本和检索速度。本地部署我常用 BGE-M3 或者 bge-large-zh-v1.5这类开源模型中文效果稳定数据也不用出内网。预算充足且数据允许上云时也可以直接调用商业 API 的 embedding 服务好处是维护成本低坏处是数据链路要合规。向量数据库的选择又是一层。个人项目我用 Qdrant轻量、支持过滤、开发体验好企业大规模用 Milvus分布式能力强如果团队已有 PostgreSQL 且在管理数据量不大用 pgvector 也能省掉一个组件。选型没有绝对标准关键是别过度设计你一个十万级文档的项目非得上分布式集群纯属给自己找麻烦。还有一个高频坑纯向量检索对专有名词极不友好。你们公司内部管某个系统叫“结算二部”员工习惯简称“二部”向量模型很可能匹配不上。这时候光靠向量不够要上混合检索BM25 关键词检索负责精准命中术语向量检索负责语义泛化两者结果融合后再进下一步。这是我在实战中最有收益的一个改动。2.4 重排最后一道精度闸门向量召回阶段为了保证召回率一般会把 top_k 放大到 20 到 50但这么多候选块不能全部塞给模型否则上下文又长又乱。所以中间需要一个“精排”环节也就是重排Rerank。重排的原理值得展开讲一下。向量召回用的是双编码器结构query 单独过一次模型文档单独过一次模型最后算两个向量的相似度。这样做的好处是文档向量可以提前算好存库检索时只要算 query 一个向量速度极快缺点是两个模型没有交互对细粒度语义关系把握不够。重排器用的是交叉编码器把 query 和文档拼接成一句话一起送进模型打分。这样模型能看到 query 和文档之间每个 token 的交互精度自然高不少但每个候选都要单独过一遍模型速度慢。所以常规操作是分两层召回阶段用双编码器快速拿到 30 个候选重排阶段用交叉编码器精算成 3 到 5 个。本地重排模型我用 BGE reranker 系列效果靠谱。这一步加完之后整个管道的最终精度会有一个非常明显的提升我在项目里测下来 hit rate 能提高十个百分点以上性价比极高。3. 搭建一条可用的 RAG 管道3.1 工具链选型双编码器、重排、向量库怎么组纸上谈兵到这里进入正题一条能用、好调、能上线的 RAG 管道怎么搭。先说我的工具链组合。编程语言用 Python编排层用 LangChain 或 LlamaIndex这两个框架文档多、社区大真要自定义逻辑也容易改。向量库用 Qdrant 本地版支持嵌入式模式开发阶段不用起服务。嵌入模型用 BAAI/bge-m3重排器用 bge-reranker-v2-m3全套离线部署数据不出内网。有人会问我为什么不用那种“全套托管的知识库服务”原因是黑盒不好定位问题。RAG 调试最关键的能力是看中间结果每一步都能打印出来检查。托管服务通常只给你最终检索结果中间切分、嵌入、重排的逻辑改动空间也小。自己做一遍虽然前期多点代码但对原理的掌握程度完全不一样。如果团队是 Java 技术栈也有成熟路线Spring AI 和 LangChain4j 都对 RAG 做了封装企业内部 Java AI 平台通常会把 RAG 做成服务对上层 Agent 暴露一个“知识查询 API”。本质上还是管道这套逻辑只是换了一层编程语言和部署形态。3.2 最小可运行实现文档加载到检索问答下面我给你一条最小可运行的实现路径。假设你已经有一个“knowledge_base.txt”文本文件内容是一份企业产品知识手册。我们要搭建的是读文档、切分、向量化、存库、检索、重排、送进模型回答。首先安装依赖pip install langchain langchain-community langchain-text-splitters qdrant-client sentence-transformers读取并切分文档from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter loader TextLoader(knowledge_base.txt, encodingutf-8) documents loader.load() splitter RecursiveCharacterTextSplitter( chunk_size600, chunk_overlap80, separators[\n\n, \n, 。, , , , , , ], keep_separatorend ) chunks splitter.split_documents(documents) print(f共切分 {len(chunks)} 个文档块)这里 separators 里我放了一堆中文标点目的就是让切分尽量落在语义完整的句子上而不是硬生生卡在某个字中间。keep_separatorend 让句末标点跟着前文走这是中文切分的小技巧。接下来向量化和入库from langchain_community.embeddings import HuggingFaceBgeEmbeddings from langchain_community.vectorstores import Qdrant embedding_model HuggingFaceBgeEmbeddings(model_nameBAAI/bge-m3) vectorstore Qdrant.from_documents( documentschunks, embeddingembedding_model, path./qdrant_local, collection_nameknowledge_base )这样本地向量库就建好了。后续要加载已有库不需要重新向量化改成Qdrant.load_local就好。检索并重排retriever vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 30} ) # 先召回 30 个候选 candidates retriever.invoke(保修期是几年) from langchain_community.cross_encoders import HuggingFaceCrossEncoder from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain.retrievers import ContextualCompressionRetriever reranker_model HuggingFaceCrossEncoder(model_nameBAAI/bge-reranker-v2-m3) compressor CrossEncoderReranker(modelreranker_model, top_n3) compression_retriever ContextualCompressionRetriever( base_compressorcompressor, base_retrieverretriever ) top_docs compression_retriever.invoke(保修期是几年)最后组织 prompt 和生成。这里以调用本地 Ollama 上的模型为例from langchain_ollama import ChatOllama from langchain_core.prompts import ChatPromptTemplate llm ChatOllama(modelqwen3:14b, temperature0) prompt ChatPromptTemplate.from_template( 请严格依据以下检索资料回答问题。如果资料中没有提及请直接回答“资料中未找到相关信息”。 不要编造内容也不要使用你自己的知识。 检索资料 {context} 用户问题{question} ) context_text \n\n.join([doc.page_content for doc in top_docs]) chain prompt | llm answer chain.invoke({context: context_text, question: 保修期是几年}) print(answer.content)这个最小示例覆盖了 RAG 全流程加载、切分、向量化、召回、重排、生成。实际项目里你还要接上文档更新机制、日志、监控和权限控制但核心管道就是这些。3.3 关键参数速查与效果评估我刚接触 RAG 时最大的困惑是“参数到底设多少”。下面这张表是我基于多个项目总结出的常见配置和调整思路你可以把它当起点但千万别当终点因为不同数据集的最优值差异很大参数推荐区间作用与调整思路chunk_size500~1000 字符内容复杂、多表格时调小系统性说明文可调大chunk_overlap50~100 字符保留前后文衔接防止切口语义断裂top_k20~50召回阶段的候选数量太大拖慢重排rerank_top_n3~5最终进生成器的文档数量根据模型上下文调score_threshold0.3~0.5过滤低分片段但要靠实际测试校准temperature0~0.2知识问答场景要低避免模型自由发挥没有评估就调参等于盲人摸象。我的习惯是每做一个知识库先人工整理 50 个左右典型的问答对每个问题标注来源文档。然后跑管道统计两个指标一是 hit rate也就是检索结果里有没有包含正确答案所在的那个文档块二是 answer correctness也就是最终答案对不对。RAG 调优的顺序应该永远是先保住 hit rate再抠答案质量。检索准了答案不一定对但检索都不准后面必错。先看检索再改 prompt这个顺序能帮你少走很多弯路。4. 常见问题与排查技巧实录4.1 召回不准先看召回再调参“我查什么它都不给对的内容”这个问题几乎每个 RAG 项目都会遇到。我的排查思路很固定先把召回结果打印出来看。调一次 retriever 把 top 文档打印出来看看模型返回来的到底是什么问题立刻清晰一半。有一次我在处理一份 FAQ 文档全是“Q... A...”结构。按普通文本切分后很多 chunk 把问题和答案从中间切开了结果检索时只匹配到了半个问题没有答案。解决方案也简单换成按段落切分、把 chunk_size 稍微调大保证 Q 和 A 不被拆散。这个案例也说明切分策略先于模型选择切分错了换再强的检索模型也救不回来。4.2 检索结果对但生成答案还是跑偏另一种情况更让人头大你打印召回内容发现检索到的段落确实包含正确答案但最终模型答错了。这大概率不是检索的问题而是生成环节的问题。常见原因有三个一是 prompt 里“仅依据资料回答”的约束不够硬模型还是不自觉动用了自己的知识二是召回内容里混入了无关片段干扰了判断三是资料本身存在前后矛盾模型选了错误的那段。针对这几个原因通常做法是修改 system prompt强调“宁可不回答也不能编造”在 prompt 里要求模型标注答案来自哪一段资料如果资料有矛盾提前在逻辑中说明“冲突时回答未知”。prompt 工程在这里不是玄学而是必要的输出约束手段。4.3 上下文溢出、速度变慢、成本升高进入生产环境后你大概率会碰到的不是质量问题而是资源问题。假设你的 chunk_size 是 1000 个字符top_k 拉到 10rerank_top_n 用了 5那么每次请求送给模型的检索内容就有大约 3000~5000 个字符如果用户的上下文再叠加上一轮历史大模型窗口很容易顶到一两万 token。一旦模型窗口溢出服务直接报错。我处理的办法是分流对纯知识问答场景不把完整历史对话全部塞给模型只保留最近两轮摘要检索内容进入模型前做压缩无关部分裁掉rerank_top_n 降到 3。同时引入缓存相同问题直接返回历史答案避免重复查库和重复调用大模型这在知识检索场景里尤其有效因为用户问的问题往往高度集中。4.4 问题排查速查表下面这张速查表是我每次项目上线前必过的检查清单现象可能原因排查方向检索结果里没有正确答案切分把答案拆碎先打印召回内容再调切分策略检索结果相关但顺序很差缺少重排环节加交叉编码器重排回答里写“找不到”但资料里有score_threshold 太高下调阈值或检查 recall答案和资料矛盾prompt 约束不足强制只依据资料未提及则未知响应太慢、请求超时候选集太大、模型窗口超限减小 top_k、缩短历史、加缓存更新文档后仍返回旧内容索引没更新建立文档版本号检索时加时间过滤中文专有名词匹配不上向量语义泛化不足加 BM25 混合检索或同义词扩展5. 从 RAG 走向 Agentic RAG5.1 把检索权交给 Agent基础 RAG 管道稳定之后下一步就是把它升级成 Agent 可调用的工具。做法不复杂把上面的 retriever 和重排封装成一个函数例如search_knowledge_base(query, filters{})然后把它注册为 Agent 的工具。Agent 面对问题时会自己判断这个问题需不需要查资料是查一次还是拆成多个子问题各查一次如果用户的提问是“对比 A 产品和 B 产品的保修政策”Agent 可以先查 A 的保修文档再查 B 的保修文档最后把两个结果放到一起对比生成答案。这种“由 Agent 决策检索”的模式就是 Agentic RAG。实测下来它最大的提升是解决了复杂问题的多跳查询不再依赖你事前把所有内容揉成一个个大 chunk。同时它对检索器的 prompt 编写也提出了更高要求工具描述要写清楚“这个知识库覆盖什么范围”不然 Agent 会拿它去回答不该它回答的问题。5.2 不止向量图谱、本体与结构化知识向量检索在“语义相似”上很强但在“关系推理”上很弱。比如“这个项目组里谁对接过数据库团队”这类问题不是靠文本相似能解决的它需要实体之间的关系链条。解决思路有两条一是 GraphRAG 方向先抽取文档中的实体和关系构建知识图谱检索时把问题映射到子图再把子图作为上下文给模型二是 Ontology RAG 方向在行业本体的约束下做结构化检索适合企业内部权限敏感、字段要严格的场景比如把产品、部门、人员、项目这些实体及其关系按领域模型严格定义好。这两种方案都算 RAG 管道的高级变体。我的建议是如果你的知识内容里有大量“实体关系”比如订单、产品、组织架构之间的关联尽早考虑图谱化如果只是“某个产品怎么用”的说明书纯向量基础管线就够用了别为了炫技把复杂度提高。5.3 从个人项目到生产级部署最后聊一下上线前的封装。个人项目里管道写在一个脚本里没问题但生产环境上 RAG 就是一个必须独立治理的服务。我的常规做法是把它拆成三个可独立扩展的部分文档摄取服务负责定时拉取新文档、增量更新索引检索服务对外暴露 query 接口内部封装向量召回、混合检索、重排生成编排层也就是 Agent 本身只负责决策和生成不再直接碰向量库。索引更新的问题尤其要提前设计。不要每次都全量重建向量库而是给每篇文档计算一个内容哈希只有内容变化了才重新切分和向量化。检索时支持按文档版本和时间范围过滤这样“昨天更新的政策今天生效”这种需求才能落地。另外企业知识有些涉及权限边界的话建议在检索层做文档级别的 ACL 过滤只允许 Agent 检索当前用户有权访问的文档。这个点虽然基础但真到上线时最容易被遗漏。6. 实操体会把精力花在评估上写到最后分享一点我个人的实战体会。做 RAG 最忌讳一上来就堆模型、堆参数。我见过太多团队花几个星期找“最好的 embedding 模型”最后浪费在调参上效果却不理想。真正拉开差距的是你有没有一套属于自己的高质量评估集。哪怕只有五十个问题只要每个问题都标注了标准答案和来源文档你每一次改动就能量化出效果变化。没有这个评估集你根本不知道一个参数改动是好是坏最后只能陷入“看起来都变好了其实全在赌博”的状态。另一个很重要的习惯是每次修改一个变量而不是同时改三个。换模型、改切分、加重排一起上效果好了都不知道是谁的功劳。我在实际项目中反复吃了这个亏之后现在一律按顺序来先固定评估集和检索参数再调切分切分满意了再验证嵌入模型然后再考虑是否加重排最后才动生成端的 prompt。这条顺序看起来慢实际是最快到达“可上线质量”的路径。RAG 这条知识获取管道说到底不是某个模型的能力而是工程统筹能力。理解了管道里的每一个环节知道每个环节什么时候该调、为什么该调才能在 Agent 接到各种奇怪问题时稳稳兜住。下一篇我会把这条管道接到 Agent 的 tool 层聊聊 Agent 如何自主决定什么时候翻书、什么时候直接回答。先聊到这儿希望这篇能帮你少踩几个坑。
阅读完成 · 觉得有帮助?