先聊一个经常被问到的问题为什么 Agent 应用里总要单独搭一套知识获取管道而不是直接把资料全部塞给大模型我在做 Agent 项目的时候一开始也试过暴力喂数据——把文档、数据库记录、网页正文一股脑写进 Prompt。结果很快就被现实教育了大模型的上下文窗口再大也经不起上万字的资料堆积而且模型对内部知识的使用是模糊联想不是精确检索。你问它一个具体的合同条款它可能给出一个语义相似却不准确的说法这在企业场景里是致命的。于是把 RAG 作为 Agent 的知识供给管道就成了从零搭建 Agent 无法绕开的一课。这篇是走进 AI Agent系列的第四篇聚焦知识获取管道的核心实现——RAG 基础。我会从原理解析讲到工程落地再补充调试技巧和踩坑记录内容按为什么需要 → 怎么设计 → 怎么实现 → 怎么排查的顺序展开。适合正在搭 Agent、想做知识库问答、或者被检索不精准回答幻觉多困扰的开发者。这篇不堆论文术语全部基于我的实际项目经历尽量把每个选择背后的理由讲清楚。1. 为什么 Agent 必须配一条知识获取管道1.1 模型记忆的边界与知识新鲜度问题先从一个最基本的认知说起大模型的参数里存了海量知识但本质上是一个截断快照。我对接过的几个主流模型训练截止时间都在过去某个时间点之后发生的事情它一概不知。如果 Agent 直接依赖模型自身知识回答遇到三件事会非常被动企业内部私有资料例如产品规格、合同模板、售后记录模型从没看过。时效性强的信息例如当天价格、库存状态、最新公告模型的知识是滞后的。需要精确引用的内容例如法规条款、技术文档原文模型容易凭印象改写。这些场景恰恰是 Agent 最常见的落地需求。没有知识获取管道Agent 就只能靠模型编而编出来的内容在企业里是要担责的。1.2 RAG 的本质把检索变成 Agent 的外挂记忆RAG 的全称是 Retrieval-Augmented Generation检索增强生成。它的本质很简单不强迫模型记住所有知识而是在每次回答问题前先从外部知识库检索出相关的片段拼进 Prompt再让模型基于这些片段生成回答。打个比方这就像开卷考试和闭卷考试的区别。闭卷考试全靠脑子记开卷考试允许你带参考资料答题时先翻到相关章节再组织语言。RAG 就是给 Agent 配了一本可以随时翻阅的参考书而且是自动翻页、自动定位到相关段落的那种。从架构上看一条标准的 RAG 管道分两半一半是入库把文档切块、向量化、写入索引一半是查询把问题向量化、检索相似片段、交给模型生成。两半缺一不可但绝大数人初次做 RAG 的时候注意力全放在向量化和检索模型上忽略了文档切块和查询优化这往往是效果不好的根源。1.3 Agent 与 RAG 的关系工具调用之外的另一种能力很多人会把 RAG 和 Agent 的工具调用Function Calling混在一起其实它们解决的是不同层面的问题。工具调用解决的是Agent 能做什么操作比如查天气、下单、发消息而 RAG 解决的是Agent 用什么知识来支撑决策和回答。但是真的做 Agent 项目时两者经常要配合使用。我做过一个售后客服 Agent用户问这个订单为什么还没发货Agent 先通过工具调用查订单状态发现是物流异常然后从 RAG 知识库里检索物流异常的处理标准再结合订单信息生成回复。没有 RAG这个 Agent 只能告诉用户订单状态却讲不清楚应该怎么处理没有工具调用RAG 也拿不到具体的订单上下文。所以RAG 不是工具调用的替代品而是 Agent 知识侧的底座。2. 知识获取管道的核心组件拆解2.1 文档加载与清洗脏进脏出是最大的坑RAG 管道的第一个环节是文档加载。听起来简单实际上最容易出问题。我接手的第一个 RAG 项目客户给了一批 PDF 格式的合同扫描件直接解析出来的文本全是乱码和错位向量化之后检索出来的内容根本没法看。后来才明白文档清洗这一步没做好的话后面所有环节的效果都会打折扣。文档清洗要处理的问题很多PDF 里的页眉页脚、表格的边框线、换行符连接断句、OCR 识别错的字符、特殊符号残留。我的经验是在进入切块之前至少要完成四件事去除页眉页脚、页码、水印等与正文无关的元素。统一换行符把 PDF 解析产生的硬换行合并成自然段落。修正 OCR 常见的错误例如把0和O混淆、把1和l混淆。移除超大空白、特殊 Unicode 字符和多余空格。这里有一个容易被忽视的细节清洗规则不能一刀切。代码文档里的缩进、表格里的制表符如果被当成普通空白清理掉反而会破坏结构。我一般会把清洗逻辑分成两档纯文本类文档走严格的清洗规则代码、JSON 这类半结构化内容只做最基础的清理。2.2 切块策略固定长度还是语义切分切块是 RAG 管道里争议最大的环节之一也是影响检索质量的核心变量。切得太小一个完整的知识点被截成好几段检索时可能只找到其中一半生成时信息不完整切得太大混入太多无关内容向量相似度被稀释而且浪费上下文窗口。固定长度切块是最容易上手的方案。按字符数切例如每块 500 个字符再叠加 50 到 100 字符的重叠保证跨块的内容不丢失。这个方案实现简单、速度快但缺点也很明显它完全无视语义边界可能把一个句子从中间截断。语义切分就聪明一些它会先识别段落、句子、标题这些结构边界尽量让每个切块落在语义完整的单元上。我用过的语义切分方案里按 Markdown 标题结构切分的效果最好——先按一级标题划出大章节再按二级标题细分最后把过长的段落按句子边界拆开。从我的实践看没有通用的最优切块策略只有针对数据特征调出来的合适策略。如果文档以段落为单位且逻辑完整按标题层级切分效果很好如果是无结构的闲聊文本固定长度加重叠是稳妥的起点。关键是切完以后要抽查看看每个块是不是一个完整的知识点而不是看统计指标。2.3 向量化与混合检索关键词与语义的配合生成切块之后需要把每块转成向量。早期项目我直接用 BGE 系列的中文嵌入模型效果中规中矩但在专业术语多的场景里比如医疗、法律术语语义检索经常出现意思对但关键词对不上的情况。后来我加入了混合检索把 BM25 关键词检索的分数和向量检索的分数做加权融合效果提升非常明显。我使用的融合方式是 RRFReciprocal Rank Fusion就是取两种检索方式各自的排名按1/(60 rank)计算融合分。这个方案的好处是不需要统一两种分数到同一个量纲只要排名正确就能融合。实测下来混合检索比单独用向量检索的命中率能提升 10 到 20 个百分点尤其在问句里有专有名词、型号、编号时关键词检索的补齐作用非常关键。如果你用的检索库是 Elasticsearch 或 OpenSearch它内置了 dense_vector 和 BM25 的混合查询能力如果用向量数据库如 Milvus、Qdrant需要在应用层把两路检索结果做融合。两者的取舍在于向量数据库对海量向量近邻搜索更高效而 Elasticsearch 的优势在于一个系统同时处理关键词和向量运维成本更低。3. 实操搭建一条可用的知识获取管道3.1 环境准备与工具选型这一节给出一套可以直接照搬的搭建方案。我以 Python 环境为例使用 LangChain 作为管道编排框架Chroma 作为向量存储本地部署一个 BGE 嵌入模型来向量化。这套组合是成本最低、最容易复现的起步方案Python 3.10 以上版本。LangChain 库用于文档加载、切块、向量化、检索的流程编排。Chroma 向量库支持本地文件持久化适合单机项目。BGE-large-zh 嵌入模型中文语义理解表现好支持本地部署。一个可用的 LLM 接口用于最终生成。这些工具的选择有个共同理由它们都是社区里资料最丰富、踩坑记录最多的选项。我刚开始做 RAG 时也纠结过要不要上更重的方案后来发现先把管道跑通再逐步替换组件才是正确节奏。你真正需要的不是一个最好的工具而是一条能让你快速迭代验证思路的流水线。3.2 文档处理与向量入库的完整流程假设我们要做一个企业内部的产品知识库文档是 Markdown 格式的产品说明。第一版管道我按四个步骤实现第一步加载文档。用 LangChain 的 Markdown 加载器读取文件得到文档对象。第二步清洗与切块。先按标题层级切块再对每个块内部的超长段落做二次切分。核心参数我设置为块大小 800 字重叠 100 字。为什么是 800因为中文语境下一个完整的技术知识点通常在 300 到 800 字之间切得太大容易混入无关信息切得太小则语义不完整。重叠 100 字是为了防止关键句恰好落在切分点附近而被丢失。第三步向量化入库。调用 BGE 嵌入模型把这些切块转成向量连同原文、来源文档 ID、标题路径一起存入 Chroma。这一步要注意的是向量库里存的只是可检索的内容原文必须保留在元数据里因为生成阶段拼进 Prompt 的是原文不是向量。第四步验证入库效果。写一个简单的检索函数输入测试问题打印出 Top-5 检索结果人工检查召回内容是否与问题相关。这一步非常重要我每次处理完新文档都会先跑一轮抽样验证而不是急于接上 LLM。你可以参考下面这段入库核心逻辑from langchain_community.document_loaders import TextLoader from langchain_text_splitters import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 1. 加载 Markdown 文档 loader TextLoader(product_manual.md, encodingutf-8) documents loader.load() # 2. 先按标题层级切分保留章节结构 headers_to_split_on [ (#, H1), (##, H2), (###, H3), ] md_splitter MarkdownHeaderTextSplitter(headers_to_split_on) chunks md_splitter.split_text(documents[0].page_content) # 3. 对过长的块做二次切分 text_splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap100, separators[\n\n, \n, 。, , , , , ] ) final_chunks text_splitter.split_documents(chunks) # 4. 向量化并入库 embeddings HuggingFaceEmbeddings(model_name./bge-large-zh) vectorstore Chroma.from_documents( documentsfinal_chunks, embeddingembeddings, persist_directory./product_kb, )这里补充一个我在实际项目中反复踩过的细节切块的 separators 顺序不能乱。递归切分器会按顺序尝试分隔符所以要把最优的分隔符放在前面。对中文内容我用的是段落符、换行符、句号、问号、感叹号、分号、空格、空字符串这个顺序。如果把空格放在句号前面英文句子里可能出现奇怪的切分点。3.3 查询阶段的优化从裸检索到多路召回入库完成只是第一步查询阶段才是决定用户体验的关键。第一版查询逻辑很简单用户问题向量化从 Chroma 里取 Top-K 个相似块直接拼进 Prompt。跑通之后我加了两层优化效果提升很明显。第一层混合检索。在向量检索之外用 BM25 对原文做一次关键词检索再把两路结果按 RRF 融合。我在 LangChain 里通过集成 Elasticsearch 的 BM25 实现也可以用一个轻量方案基于 Okapi BM25 算法的开源库对切块文本做一次本地关键词评分。选型的核心不是牌子而是理解 BM25 和 RRF 的原理BM25 是经典的关键词匹配算法它会对在文档中出现次数适中、在整个语料中出现频率较低的词给予更高权重适合精确匹配专有名词。向量检索适合语义匹配比如用户说屏幕闪烁文档里写的是显示器画面跳动两者没有共同的词但语义相近。融合时如果两路都返回了同一个块它的融合分会明显高于只被一路命中的块这代表它大概率是真正相关的内容。第二层查询改写。用户的问题往往不是单一知识点的精确匹配而是复合句或者口语化表达。我实现在查询之前先把问题交给 LLM 做一次改写拆解成 2 到 3 个独立检索子问题分别检索后汇总候选集。例如3000 元以内的 5G 手机推荐一下要电池耐用的改写器会拆成两个子问题5G 手机 3000 元以内和手机电池续航长分别检索再合并结果。提示查询改写不是越复杂越好。我测试过把 LLM 改写用于全部请求结果长尾问题反而引入了噪声。最终采用的策略是短问题、专有名词明确的问题直接用原问问句超过 20 个字或包含多个主题时才走改写流程。3.4 生成阶段的 Prompt 设计让模型学会引用依据最后一步是把检索到的文本块组织成 Prompt交给 LLM 生成答案。这个环节看似简单却是控制幻觉的关键。我的经验是Prompt 里必须包含三项身份与任务、检索到的资料、回答规则。回答规则里至少要写清四件事优先依据资料内容回答资料不足时明确说明根据现有资料无法确认。引用结论时标注资料来源编号。不使用外部知识补充防止模型自由发挥。如果资料之间有矛盾如实指出矛盾而不是自行调和。我也尝试过让模型在回答末尾统一列出参考依据但效果不如在文中直接标注来源编号。前者让用户很难对应到具体内容后者一目了然。在企业内部场景里这种标注方式也方便用户追查原始文档提高对 Agent 的信任度。4. 避坑指南检索质量、幻觉与性能问题4.1 检索不到相关内容时的排查路径做 RAG 项目最常听到的抱怨就是它回答不了我想问的问题。遇到这种问题我第一反应不是去调 Prompt而是先检查检索环节。做法很简单把检索结果单独打出来看看 Top-5 到底召回的是什么。如果召回的文本和问题根本不对应那问题出在找的环节而不是答的环节。检索质量不达标时我按以下顺序排查看切块是否合理。如果是切块过大导致混入噪声缩小块大小如果是切块过小导致信息不完整增大块大小或增加重叠。看嵌入模型是否适配领域。我用过通用型嵌入模型处理法律文书效果很差换成在法律语料上微调过的模型后明显改善。看查询是否需要改写。如果用户问的是复合问题把问题拆开后再检索。看是否遗漏了关键词精确匹配。加入 BM25 混合检索专有名词命中率会明显提升。这套排查顺序是实践总结出来的切块问题最常见也最容易忽略嵌入模型的适配度次之查询改写和混合检索是锦上添花但如果前两项做得差后两项也补不回来。4.2 答案生成的幻觉如何压制RAG 的一个核心目标是减少幻觉但它不能完全消除幻觉。我把幻觉归纳为两类一类是模型不依据资料回答自己编造内容另一类是模型对检索内容做了错误的推断。第二类尤其隐蔽——它看起来好像有依据但实际上依据被曲解了。压制幻觉我主要靠三道防线第一道检索质量。如果检索到的资料本身就不相关生成必然出问题。所以先把检索命中率提到足够高再讨论生成质量。第二道Prompt 约束。明确告诉模型不要使用资料之外的知识并且要求资料不足时如实说无法确认。实测这种显式约束比不约束有效得多模型会倾向于坦白而非编造。第三道结构化输出。要求模型输出时带引用编号方便程序侧校验。更进一步我做了一个简单的校验器如果模型声称某结论来自编号为 3 的资料程序会检查编号 3 的文本是否真的包含该结论的关键词。不匹配则触发重新回答或向用户说明信息不足。这里有一个体会幻觉压制的核心不是更长的 Prompt而是更强的约束回路。让模型的输出可校验、可追踪比反复修改措辞有效得多。4.3 性能瓶颈与工程化落地经验RAG 管道的性能瓶颈通常出现在三个位置文档解析、向量化耗时、大模型生成。文档解析的瓶颈主要来自 PDF 和扫描件。我处理过一个上千页的 PDF 合同库解析用了将近二十分钟而且部分页面结构错乱。后来改用专业的文档解析服务例如将 PDF 转成 HTML 再提取正文后时间降到几分钟准确率也大幅提升。经验是不要死磕直接解析转换中间格式往往更高效。向量化的耗时主要来自嵌入模型的推理。BGE-large 模型在纯 CPU 环境里处理上万条切块非常慢我建议用 GPU 跑嵌入或者使用量化版本模型。如果文档量在百万级还需要考虑分批入库和持久化策略。大模型生成是最耗时的环节。一个复杂的问答可能要 5 到 10 秒才能生成完这在实时对话场景里体验不佳。我常用的缓解方案有两个一是把检索到的资料做一次重排序Rerank只保留前 3-5 个最相关的块减少送入大模型的长度生成速度会明显提升二是对常见问题做缓存命中缓存的请求直接返回历史答案。4.4 效果评估不只是凭感觉调参数很多人调 RAG 都是凭感觉觉得效果不行就改改切块大小、换换提示词改完也不知道是变好了还是变坏了。我后续专门写了一篇关于评估的文章但这里先给出一个最简可行方案构造一个评测集包含 30 到 50 个典型问题每个问题标注期望答案所在的文档或文本块。评测时跑两个指标检索命中率Top-5 结果里是否包含期望文本块这衡量管道找得到的能力。答案准确率生成答案是否与期望答案在关键信息上一致这衡量答得对的能力。每次修改切块参数、换检索方式或调整 Prompt都跑一遍评测集把两个指标记下来。有了这套流程优化就不是靠感觉了而是靠能对比的数据。早期我犯过一个错总觉得检索结果看起来质量不错但一问指标却发现命中率只有 40%后来才知道是切块把大量知识点截断了导致检索永远命中不了完整内容。评测集替我暴露了这个真实问题。5. 进阶方向从标准 RAG 走向 Agentic RAG5.1 标准 RAG 的局限性标准 RAG 适合问-答这种单轮模式它的问题在于检索是一次性完成的检索结果的好坏直接决定了生成的天花板。如果用户的问题很复杂需要多步推理和多源信息汇总一次性检索往往不够。例如用户问对比一下 A 产品和 B 产品在续航、防水、重量三个方面的差异。标准 RAG 的做法是拿整个问题去做一次检索很可能召回到一批只提到 A 或只提到 B 的碎片生成时需要自己脑补缺失的部分。这种问题的正确做法是拆成多个子查询A 产品的续航、防水、重量B 产品的续航、防水、重量然后分别检索最后汇总对比。这种边检索边思考、检索几步再决定下一步检索什么的模式就是 Agentic RAG 的核心思想。它把 RAG 从一次检索 一次生成升级为多轮检索 中间推理 动态决策。5.2 Agentic RAG 的设计思路我理解的 Agentic RAG 是在标准 RAG 管道之上套了一层 Agent 决策循环。典型的设计包括意图路由先判断用户的请求是简单的知识问答还是需要多轮检索的复杂任务。简单任务走快速通道复杂任务走多步检索通道兼顾效果和成本。子问题分解把复杂问题拆解成多个可独立检索的子问题并决定检索顺序。有些问题有前置依赖需要先查 A 再查 B。结果融合与验证每一轮检索的结果先做一轮判断看是否足够回答当前子问题。如果不够再过一轮改写与检索。自我纠错如果最终生成的答案缺少关键信息或者引用与检索结果矛盾Agent 可以重新检索或要求用户补充信息。Agentic RAG 不是另一种框架而是一种思维方式的转变。我刚开始做的时候总觉得要让 Agent 表现得智能一点后来发现核心是把决策流程写清楚什么条件下继续检索、什么条件下停止检索、什么条件下向模型提问。这些规则比堆提示词重要得多。5.3 与 Agent 框架的集成路径如果你已经在用 LangChain 或类似框架搭建 Agent集成 RAG 有两种常见路径。第一种是把 RAG 封装成一个检索工具ToolAgent 需要知识时调用这个工具。这种方式的优点是解耦清晰Agent 的决策逻辑和知识库相互独立但缺点是要在 Agent 提示词里把何时该调用检索工具写清楚否则 Agent 可能无视检索直接回答。第二种是把 RAG 作为 Agent 的记忆组件。这种方式更适合长时间运行的 Agent它会在对话过程中持续利用知识库补充信息更像是伴随式知识供给。我个人的项目经验是大多数场景用第一种就够只有对话轮次多、需要持续引用复杂文档的场景才值得把 RAG 做成记忆组件。从组件化的角度看我后续还遇到过把 RAG 服务化例如 AgentScope 2.0 提出的 RAG-as-a-Service 理念的做法把知识检索能力和 Agent 应用解耦让多个 Agent 共享同一个知识管道。这个方向适合多 Agent 协作的复杂项目但对基础设施的要求更高。把标准 RAG 跑通理解检索质量为什么重要再往这些方向探索会顺手很多。6. 调参经验汇总与常见问题速查6.1 核心参数速查表这里把我在项目中最常调整的参数整理成表方便你对照排查参数常见取值范围我常用的起点调整方向块大小300 - 1500 字符800 字符检索不精准时减小语义不完整时增大块重叠50 - 200 字符100 字符关键内容跨块丢失时增大Top-K 检索数量3 - 208过小时信息不足过大时引入噪声RRF 融合权重1 - 10060向量与关键词结果排名差距大时微调混合检索开关开 / 关开专有名词多的领域必须开嵌入模型通用型 / 领域微调型BGE-large-zh检索效果差时优先更换这里要特别强调一点没有一套参数适用于所有场景。我见过有人在通用领域用 1500 字符的块大小效果很好但在法律合同场景里超过 300 字符的块就已经混入太多无关条款。所以参数表只是起点一定要用评测集去验证效果而不是直接照搬。6.2 常见问题排查一览表问题现象排查方向常见原因处理建议检索结果完全不相关切块、查询、嵌入模型查询与文档语言差异大或切块太大混入噪声检查切块是否合理尝试查询改写加入混合检索答案内容正确但缺少细节切块大小、Top-K切块切断了关键信息或 Top-K 太小增大块重叠增大 Top-K答案出现幻觉Prompt 约束、生成参数约束不足或检索结果本身不相关强化仅依据资料回答的约束降低生成温度回答速度慢检索候选集、模型长度送入大模型的资料过多加入 Rerank压缩候选集到 3-5 个块处理大批量文档很慢向量化、解析方式CPU 跑嵌入模型PDF 直接解析换 GPU或转换中间格式后解析交叉引用内容检索不到切块策略段落在切块时被分隔开改用语义切分或增加重叠比例以上这些坑我几乎都踩过一轮。有些问题在当时看起来像是模型不够聪明排查到最后发现就是切块参数没调对。所以遇到问题时先用最小化的方法定位是找的问题还是答的问题可以省下大量调试时间。6.3 值得养成的工程习惯最后分享几个我在多次迭代后沉淀下来的工程习惯谈不上多高深但确实能避免重复踩坑。第一个习惯是先检后答。任何一次查询请求都会在返回最终答案前把检索到的候选块和应用层指标记录下来。这样出了问题能回溯是检索环节还是生成环节的锅。第二个习惯是小步迭代频繁验证。不要一次性把所有组件调到位而是每次只改一个变量重新跑评测集。我试过一次同时改块大小和嵌入模型结果效果变差却不知道是哪个改动造成的最后只能回滚重来。第三个习惯是语料样本管理。我会定期从真实用户提问里挑出有代表性的问题加入评测集。评测集不是一次建完就结束而是持续更新的里面始终保留那些曾经让系统翻车的问题防止优化后的回退。这三个习惯本质上是一件事让 RAG 的迭代过程变得可观测、可回溯、可对比。做完这些以后一个知识获取管道才算真正能交付而不只是一个能跑通的 Demo。