
现在很多朋友聊 AI Agent开口就是规划、工具调用、多智能体协作但真到自己动手从 0 到 1 搭建一个能“干活”的 Agent 时最先卡住的往往是那个最不性感、却最致命的环节——知识从哪来。模型参数里那点知识是死的企业内部文档、产品手册、最新工单、私有代码库这些才是活的。把活的业务知识喂给 Agent并且让它按需取用靠的就是 RAGRetrieval-Augmented Generation检索增强生成这条知识获取管道。这篇是“走进 AI Agent”系列的第四篇我把话题聚焦在 RAG 最基础的部分它解决什么问题管道里每一截是干什么的以及我实际搭建一个最小可用 RAG 管道的完整过程和踩坑记录。不管你是刚开始接触 RAG 概念的新手还是已经在写 Agent 但发现知识召回总是不准的开发者这篇都能给你一份可以直接照着做的参照系。先说一个我的观点很多人把 RAG 当成“给大模型外挂一个知识库”这个说法没错但容易误导。RAG 的本质不是挂载而是构建一条管道——文档从进来到出去要经过切分、向量化、索引、检索、重排、注入提示词等多个环节任何一环糙一点最后答出来的东西就跑偏。下面我把这条管道从头拆到尾。1. 为什么 Agent 需要知识管道先讲清楚“知识割裂”问题1.1 从“对话玩具”到“干活”Agent 的短板就是知识我在前面几篇里反复强调一个判断纯靠大模型自身参数里的知识做问答、写文案没问题但做 Agent 一定不够。原因很朴素——预训练数据是有截止日期的而且模型不会知道你公司内部那套命名规范、你项目里那个历史包袱、你客户昨天提的那个具体需求。拿我最近帮朋友搞的一个售后客服 Agent 举例。模型本身很聪明能理解用户情绪能组织语言但它不知道这家公司产品最新的固件版本号不知道已知问题清单里哪条对应“开机黑屏”更不知道工单系统里那个“DTS20240815”是什么意思。如果没有外部知识接入Agent 就只能一本正经地胡说八道——术语叫“幻觉”说白了就是知识割裂模型有语言能力但没有业务事实。Agent 解决这个问题的标准姿势就是 RAG。它把知识“外置”到模型之外先根据用户问题去知识库里检索相关内容再把检索到的内容连同问题一起交给大模型让模型基于这些材料组织回答。这样模型不必“记住”所有知识只要会“查”和“读”就行。这就是为什么我说它是一条管道——用户问题进来经过检索管道带着证据出去生成回答。1.2 RAG 在 Agent 里的位置不是插件是管道聊 Agent 架构的时候经常有人把 RAG 归类到“工具调用”里跟搜索网页、调用 API 并列。我的理解不太一样工具调用是 Agent 的“手”RAG 是 Agent 的“记忆检索系统”——它负责把长时记忆文档、知识库转成工作记忆当前上下文相关片段。这个定位很关键直接决定了你怎么设计。Agent 通常分几个核心模块规划Planning、记忆Memory、工具Tools、执行Action。知识获取管道横跨记忆和工具两块从机制看它像工具可被调用从功能看它提供的是记忆内容不是执行动作。所以在我的项目里RAG 管道从来不是独立服务随便挂一下而是和 Agent 的记忆模块、提示词模板结合在一起的。另外要补充一点RAG 不是单次检索就完了。你可能会搜到多个候选片段Agent 需要判断哪个有用、哪个没用甚至要不只检索一轮根据初步回答再补检索一次。这种把检索过程和 Agent 推理循环结合起来的做法就是现在热词里那个 “Agentic RAG” 的方向。不过这篇文章先打底子把基础管道讲透Agentic RAG 后续单独写。2. RAG 基础链路拆解从切分、向量化到检索生成的完整流程2.1 第一关文档加载与切分切不好后面全白搭RAG 管道的输入是文档但大模型不能直接“读”整个文档库所以第一步要把文档拆成合适的块chunk。这个环节看着不起眼却决定了后面检索质量的天花板。我见过太多人兴致勃勃做 RAG结果效果一塌糊涂查来查去发现根因就是切分太粗暴。切分的核心矛盾是块太大向量化后语义太杂检索出来的内容不够精准还可能撑爆上下文窗口块太小语义不完整单个块信息量不足模型看了也拼不出完整结论。我常用的经验标准是通用文本按 300-500 字左右切带标题结构的文档优先按语义段落切代码或表格按逻辑块切。实操中我建议先用结构感知切分而不是纯按字数硬切。比如 Markdown 文档先按标题层级把文档拆成章节再对过长的章节做二次切分PDF 先抽取标题、段落、表格结构尽量保持段落完整。现在主流框架里的 recursive character text splitter 就是先按段落切再按句子切最后按字符切这种递归策略能最大限度保留语义边界。还有个容易被忽略的点切分时的“重叠”overlap。我在相邻块之间保留 50-100 字的重叠区这样即使关键信息正好落在边界上也至少能在两个块里完整出现避免检索时漏掉。这个技巧用好了召回率能有肉眼可见的提升。2.2 第二关Embedding 与向量库选型文档切好之后下一步是把每个块转成向量——一串能够表征语义的浮点数。这一步靠的是 Embedding 模型。选 Embedding 模型我主要看三个指标语义相似度质量、支持的语言、向量维度。语义质量怎么判断不要只看榜单分数拿你自己领域的语料去测。比如我做企业文档问答会准备二三十个“问题-标准答案片段”对跑一遍召回看 Top-5 里能不能命中正确片段。这比任何公开 benchmark 都实在。语言方面如果知识库是中英混合建议选多语言模型比如 BGE 系列、text-embedding-3 之类别选纯英文优化的模型否则中文文档语义全扭曲了。向量维度影响存储和检索速度一般 768 或 1024 维足够维度越高越精细但成本也越高平衡着来。向量库的选型我按项目规模给三条建议。个人练手或小团队原型直接用 Chroma 或 FAISS一个本地文件就能跑起来零运维负担。中等规模、需要并发查询和过滤用 Qdrant 或 Milvus支持元数据过滤这在实际业务里非常重要——比如限定只看某类文档时提前用元数据缩小范围能省大量计算。再大规模、高并发生产系统上 Elasticsearch 或 OpenSearch它本身支持向量检索加全文检索的混合模式后面我会详细说为什么混合检索重要。注意向量检索不是万能的。它擅长语义相近的召回但精确匹配——比如产品型号“DTS-2024”、订单号“SO123456”——往往表现不佳。所以正规 RAG 管道必须做混合检索把关键词匹配和向量召回结合起来我后面实操部分会示范。2.3 第三关检索、重排与生成融合管道后半段就是检索和生成了。检索不只是“找相似度最高的几个块”理想做法是三步走召回Recall、重排Rerank、生成Generate。召回阶段用向量相似度比如余弦相似度从库里捞出候选 Top 20-50 个块。这个阶段标准是“宁滥勿缺”因为后面还有重排兜底召回太严反而容易漏。实战里我最常用的是混合召回向量检索拿语义相似的BM25 拿关键词精确匹配的两边结果合并去重。这样既能抓住“意思相近但用词不同”的文档也能精确命中“型号完全一致”的硬指标。重排阶段用一个专门的 Rerank 模型比如 BGE-Reranker、Cohere Rerank对候选块重新打分排序。为什么要重排因为 Embedding 模型算的是“大概语义接近”而 Rerank 模型是拿用户问题和每个候选块做深度交叉编码精细判断相关性。这一步能把 Top 20 里最相关的 3-5 个块提到最前面从而显著提升回答质量。我的经验值是加了重排后答案准确率通常能提高 10-20 个百分点。生成阶段把最终筛出的块拼进提示词。这里有个关键设计提示词里不仅要放检索到的内容还要明确告诉模型哪些该信、哪些不该信。我会在提示里写“请基于以下资料回答问题如果资料中没有相关信息请直接说不知道不要编造。”这一句话能显著降低幻觉。同时让模型在回答末尾标出引用了哪几个文档片段方便你排查回答依据。3. 实操过程我搭一个最小可用的 RAG 管道3.1 工具选型和环境准备理论说了一堆上点硬的。我这里搭一个最小可用的 RAG 管道目标场景是给一个内部技术手册做问答Agent 能根据手册内容回答“如何重启网关设备”这类问题而且能给出依据。我的选型如下都是经过多次对比后留存的一套组合框架LlamaIndex也可以用 LangChain但 LlamaIndex 对检索链路封装更直接EmbeddingBAAI/bge-small-zh-v1.5中文效果好维度 512资源占用小RerankBAAI/bge-reranker-base加在管道里做精排向量库Chroma本地文件持久化原型阶段足够了大模型DeepSeek 或 Qwen主要看你有哪个 API这一步其实不影响语法安装依赖的时候我建议用一个干净的虚拟环境避免依赖冲突。这步不复杂但很值得做python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install llama-index chromadb sentence-transformers如果你用的是 OpenAI 兼容 API比如 Qwen、DeepSeek 都有兼容模式建议直接配环境变量方便切换export OPENAI_API_KEY你的key export OPENAI_BASE_URL你的API地址3.2 核心实现加载、切割、入库下面这段代码演示文档加载和切分入库。假设我有一份 Markdown 格式的技术手册manual.md同时配套一份产品说明书 PDFdatasheet.pdf。from llama_index.core import SimpleDirectoryReader, Settings from llama_index.core.node_parser import MarkdownNodeParser from llama_index.core.ingestion import IngestionPipeline from llama_index.vector_stores.chroma import ChromaVectorStore from llama_index.embeddings.huggingface import HuggingFaceEmbedding import chromadb # 1. 设置 Embedding 模型 Settings.embed_model HuggingFaceEmbedding( model_nameBAAI/bge-small-zh-v1.5 ) # 2. 加载文档 reader SimpleDirectoryReader( input_files[./data/manual.md] ) docs reader.load_data() # 3. 切分Markdown 结构感知切分 parser MarkdownNodeParser.from_defaults( chunk_size512, chunk_overlap80 ) nodes parser.get_nodes_from_documents(docs) # 4. 初始化向量库 chroma_client chromadb.PersistentClient(path./chroma_db) collection chroma_client.get_or_create_collection(tech_manual) vector_store ChromaVectorStore(chroma_collectioncollection) # 5. 入库自动完成 chunk 向量化 pipeline IngestionPipeline( transformations[Settings.embed_model], vector_storevector_store ) pipeline.run(nodesnodes)这里几个参数说明一下。chunk_size512对中文技术文档是一个比较稳的起点按字符数算512 个中文字大概能承载 2-3 个要点不会太碎也不会太宽。chunk_overlap80保留块间重叠防止关键句在边界被切断。如果你的文档段落本身很长可以调小 chunk_size让切分更细。代码跑完后你可以在本地./chroma_db看到持久化的向量数据。以后每次更新知识库只需要重新加载文档、增量跑一遍 transform 就行。3.3 查询端实战混合检索加重排入库只是基础查询端的表现才是关键。下面是查询处理的完整代码我故意把检索过程拆开方便你观察每一步的作用from llama_index.core import VectorStoreIndex from llama_index.core.postprocessor import SentenceTransformerRerank from llama_index.core.retrievers import VectorIndexRetriever from llama_index.core.query_engine import RetrieverQueryEngine from llama_index.core.indices.query.query_transform import HyDEQueryTransform from llama_index.core.query_engine import TransformQueryEngine # 加载已有索引 index VectorStoreIndex.from_vector_store(vector_store) # 1. 基础向量检索器 retriever VectorIndexRetriever( indexindex, similarity_top_k20 # 召回20个候选 ) # 2. 重排器 reranker SentenceTransformerRerank( modelBAAI/bge-reranker-base, top_n4 # 重排后保留4个 ) # 3. 组装查询引擎 query_engine RetrieverQueryEngine( retrieverretriever, node_postprocessors[reranker] ) # 4. 执行查询 response query_engine.query(如何重启网关设备) print(response) print(--- 参考依据 ---) for node in response.source_nodes: print(node.node.get_content()[:200])这段代码里有几个值得展开的地方。similarity_top_k20是召回数量top_n4是最终进入上下文的片段数。这两个数字的关系是金字塔形召回多一些保证不漏重排精一些保证不杂。在实际项目里我通常把 top_k 控制在 20-50top_n 控制在 3-6具体看你上下文窗口大小。上面的检索器我只用了向量召回。如果你要做混合召回可以用 LlamaIndex 的QueryFusionRetriever把 BM25 和向量检索结果融合起来。具体代码大致是from llama_index.core.retrievers import QueryFusionRetriever from llama_index.core.retrievers import BM25Retriever bm25_retriever BM25Retriever.from_defaults( docstoreindex.docstore, similarity_top_k10 ) fusion_retriever QueryFusionRetriever( [retriever, bm25_retriever], similarity_top_k20, num_queries1, # 可以改成多个查询变体做扩展 modereciprocal_rerank # 用RRF融合排序 )reciprocal_rerank是 RRFReciprocal Rank Fusion模式它把多个检索结果按排名取倒数分值融合不需要调权重效果稳定。这是混合检索的标配强烈建议优先用这个模式而不是简单拼接两个结果集。3.4 检索效果检查先跑三组测试再谈调优管道跑通之后第一件事不是接 Agent而是验证检索质量。我习惯做一个“黄金问答集”手工整理 10-20 个问题和对应的标准答案文档片段然后跑一遍管道看每个问题能不能在 Top-4 里找到正确答案。以“如何重启网关设备”为例我实际测试时看到召回的重排结果前四名分别是网关复位操作章节正确网关设备电源指示灯说明相关但不直接远程升级网关固件步骤语义相近但主题偏离常见故障排除之设备无响应正确这个结果说明管道工作正常前四名里有两条直接命中。如果我看到 Top-4 里全是“不相关”的内容就会往回查切分是否合理是不是大段塞在一起、embedding 模型是否匹配语言、重排是否生效。大部分问题都出在这三个环节而不是模型本身。如果你希望进一步提升召回率还有一个非常推荐的做法——HyDEHypothetical Document Embeddings。思路是先用大模型根据用户问题生成一个假想答案再用假想答案的向量去检索。这样能极大缓解“用户问法跟文档表述不一致”的问题代价是多一次 LLM 调用。实践中我觉得在问题含糊、文档分散的时候HyDE 效果特别明显。4. 常见问题与排查技巧实录4.1 检索不到问题根源基本在切分和召回RAG 上线后最常见的反馈就是“问什么都不对”。我排查这类问题的套路很固定照着来基本能定位第一步看召回结果。把用户的问题直接丢进检索器打印 Top-10 内容看看有没有相关内容被召回。如果没召回问题出在“检索端”要么切分把关键内容切碎了要么 Embedding 语言不匹配要么向量库索引有问题。第二步看过召回是否回答了问题。召回有相关内容但回答不对问题出在“生成端”提示词没把约束说清楚或者重排后留下的上下文不完整。这时候不是调检索而是调提示词和生成参数。一个非常有效的做法是把top_n从 4 提到 6因为有些回答需要多个片段拼起来给少了模型看不全。第三步看数据本身。如果文档是扫描版 PDF文字全是图片你切分入库之前必须先做 OCR。这个坑太常见了电子签章、扫描件、拍照件这些在入库前没转成可检索文本后面全白搭。我一般用 PaddleOCR 或 MinerU 这类工具先做文档解析再进管道。4.2 答非所问多半是知识冲突和上下文污染还有一种情况是内容检索到了但模型答出来驴唇不对马嘴。我遇到过的两个高频原因。第一个原因是上下文污染。如果你的 Agent 同时接了多个知识库检索有些检索结果跟当前问题无关但被一并放进了上下文模型就会被带偏。解决办法是严格做相关性过滤重排后的片段设置一个最低分数阈值低于阈值的直接不进入上下文。宁缺毋滥这是 RAG 上下文管理的核心原则。第二个原因是知识冲突。知识库里同时存在“旧版操作流程 A”和“新版操作流程 B”模型可能各取一半拼出个四不像。这种情况的处理方式是元数据过滤加时间排序。入库时给每个 chunk 打上版本号、日期、来源标记检索时限定“只看最新版本”或者在重排阶段优先选择时间新的片段。我实际维护知识库时还专门做了一版数据清洗把明显过时的内容标记但不删除这样既能避免冲突也保留了历史记录。4.3 系统卡顿和成本失控索引和缓存的优化RAG 管道一旦面向真实业务性能和成本问题必然冒出来。我给出三个最容易见效的优化方向。第一索引分层。不要把所有文档塞进一个集合。我按业务域拆成多个 collection 或分区查询时先用元数据锁定可能相关的子集再做向量检索。这个操作能把单次检索的扫描量降低一个数量级。第二缓存命中。对高频相似问题加一层语义缓存。意思相近的问题直接复用上次的完整响应只缓存准确性和好评率高的问题-回答对。实现方式也很简单检索前先拿用户问题跟缓存库里最近 N 条问题做相似度比对超过阈值就直接返回缓存。这一招能砍掉不少大模型调用成本。第三局部向量化。长文档更新时只需要重新切分和索引变更的块不要每次都全量重建。Chromadb 原生支持增量写入配合你的文件变更检测更新成本可以压得很低。4.4 从 RAG 到 Agentic RAG下一步怎么演进这篇文章标题是基础但现在的趋势是 Agentic RAG——让 Agent 自己决定“要不要检索”、“检索什么”、“要不要再检索一次”。我实践下来的体会是不要一上来就搞复杂先把基础管道跑稳再逐步把控制权交给 Agent。一个轻量演进路径是这样的。第一阶段单轮 RAG用户问题进来检索一次生成回答。第二阶段带路由的 RAGAgent 先判断问题属于哪类知识域再决定调用哪条检索管道。第三阶段多轮交互式 RAGAgent 根据初步检索情况决定是直接作答还是改写问题二次检索这就是 Agentic RAG 的雏形。第四阶段工具融合RAG 检索结果可以和数据库查询、API 调用结果合并共同作为生成依据。我在最近的一个项目里就是把“查询 ERP 产品库存”这类精确数据和“产品介绍语义检索”这类模糊知识结合Agent 先用语义检索找到产品候选再调 ERP 接口拿库存数量最后汇总回答。这种“检索定候选、工具落实锤”的配合比单纯 RAG 或者单纯 API 查询都要好用。写在最后的个人体会这条 RAG 管道我前前后后重写过不下五版最深刻的体会是RAG 的难点不在“跑通”而在“可控”。跑通只需半天但要让召回稳定、答案可信、更新不翻车需要花很多心思在切分策略、检索融合、元数据管理这些细节上。很多人一开始就冲 Agentic RAG结果基础管道不稳定Agent 越是自主决策错误被放得越大。如果你正要从 0 到 1 搭建 AI Agent我建议把 RAG 当成第一优先级的基础工程来做。先把一两条业务线的知识库管道跑稳再做 Agent 的规划与工具调用你会发现后面的路顺畅得多。下一篇我打算写 Agent 的记忆管理——RAG 解决的是“从外部拿知识”记忆要解决的是“怎么记住对话过程中产生的临时信息”两者配合起来才算一个完整的知识体系。