1. 为什么Agent需要知识获取管道先认清LLM的“知识天花板”前几篇我们聊了Agent的骨架、规划和工具调用这套机制让模型能拆解任务、调用外部API、执行动作。但如果你真正上手跑过几个Agent大概率会遇到同一个尴尬场面Agent逻辑很顺、工具调用也很溜可一问到具体的业务细节、内部文档、某个产品的参数规格它就开始一本正经地胡说八道。这不是模型笨而是它脑子里的知识有边界。大模型在训练时把互联网上的公开知识压缩进了参数里但训练语料有截止日期而且不可能覆盖你团队内部的私有资料。你不可能让一个通用模型天然知道你们公司的报销制度、某个设备的维修手册、上个月的运营数据。这时候就需要一条从外部知识源把信息“喂”给模型的管道这就是RAGRetrieval-Augmented Generation检索增强生成要做的事。RAG不是一个独立的模型而是一个架构模式先根据用户的问题去外部知识库里检索相关内容再把检索结果和问题一起塞进提示词让模型基于这些材料回答。所以我把这个系列里RAG这篇定位成“知识获取管道”。它是Agent的眼睛和耳朵负责在模型开口之前先把有用的背景材料捞过来。这篇文章面向两类人一类是刚开始搭Agent、不知道知识库这块怎么下手的小白另一类是已经跑了裸模型接口、但发现回答总是不够准、不够新的开发者。看完你应该能理解RAG的完整链路并且能照着后面的代码搭出一个能用的最小管道。1.1 Agent的“记忆缺陷”与RAG的补位先别急着写代码我们把问题掰开揉碎。LLM本身有没有记忆有但那是“参数记忆”知识被以概率分布的形式存在几亿几百亿个权重里。这种记忆有三个硬伤静态、模糊、通用。静态是说它不会因为你昨天更新的文档而变化除非重新训练或微调但那成本高得吓人模糊是说它记不住精确的编号、日期、价格因为它学的是“规律”不是“数据库”通用是说它只擅长大众化的知识你问它某个冷门框架的底层实现它只能靠“猜”。Agent在执行任务时最怕的就是这种模糊性。比如一个客服Agent用户问“我上个月订单为什么还没发货”模型只能瞎编一个原因。但如果给它检索到该用户的订单状态、物流轨迹、仓库备注它就能基于事实回答。RAG就是在模型推理之前先做一次精准的“开卷考试”不是让模型背全书而是把相关章节撕下来递给它。这里要区分RAG和微调Fine-tuning。很多人一开始都会纠结要不要微调一个垂直模型我的建议是如果你的知识是事实型、更新频繁、需要精确引用的内容优先上RAG。微调更适合改变模型的“行为风格”比如让它按固定格式输出、学会某种语气不适合塞大量事实细节。RAG还有一个好处是每次回答都能引用来源出了问题你可以去查检索到了什么这在新规合规场景下非常重要。1.2 RAG在整个Agent架构中的位置在Agent架构里RAG通常不是独立模块而是作为“工具”或“上下文构建器”嵌入到工作流里。最朴素的做法是Agent接收到用户问题后先调用一个“知识检索工具”把检索到的文本片段作为上下文再交给主模型生成最终回复。稍微进阶一点Agent可以判断这个问题是否需要检索、需要检索几次、检索后用哪个工具的结果这就是后面要讲的Agentic RAG。很多人会混淆RAG和“长上下文窗口”。现在模型动辄支持100万token上下文那是不是把整个文档库都塞进去就行从理论上说可行但成本高、速度慢而且实验发现过长的无关上下文反而会稀释模型的注意力导致回答质量下降。RAG的本质是做“精挑细选”从海量文档里挑出最相关的几千token作为上下文让模型把注意力集中在关键信息上。就好比你给员工开卷考试直接把整本辞海放他桌上他未必能找到答案但如果事先帮他折好页码效率立刻上来了。所以你在搭建Agent时不要把知识库设计成“一次性把所有资料导入上下文”而要把知识获取设计成“按需检索的管道”。管道这个词很关键它意味着数据从源头到模型之间要经过清洗、切分、索引、召回等关卡每一关都会影响最终效果。下面我按管道顺序拆开讲。2. 构建RAG管道的四个核心环节从文档到召回一次标准的RAG流程可以分成四步加载与清洗、切分、向量化、存储与召回。这四步环环相扣前面一步做不好后面再怎么调模型也没用。很多人在第二步切分上踩坑最多但第一步的清洗反而最容易被忽略。2.1 文档加载与清洗别让垃圾数据进管道文档加载听起来最简单不就是读文件嘛。实际上坑很多。PDF有扫描版、表格版、三栏排版Word里有页眉页脚Markdown里有HTML标签PPT和Excel导出成文本后往往留下一堆无意义的编号。如果你的知识库是面向业务的通常还有各种格式的导出文件。我们要做的清洗包括去除页眉页脚、统一换行符、去掉图片OCR产生的乱码、合并断行、处理表格转换为结构化文本。我的经验是在加载阶段多花半小时比在后续调参节省十小时。比如PDF里的文字经常因为排版被拆成半个单词直接切分后会出现大量“chun”和“ked”这样的碎片。清洗阶段可以先把所有文档统一转成纯文本或Markdown格式再用正则清理掉连续空行、特殊符号。如果你用的是现成加载器比如LangChain的PyPDFLoader也要记得用text_splitter之前先自己预览几次输出内容。还有一个常被忽视的点数据粒度。知识库里的文档长短差异巨大有的是一整本书有的是一句话FAQ。不要混在一起直接处理。建议先按文档类型或业务板块分目录管理再对不同类型配置不同的清洗和切分策略。管道设计上要让“清洗”这一步独立出来方便后续单独调整。2.2 文本切分决定检索质量的“隐藏杠杆”切分是RAG里最容易被低估的一环。选什么模型、用什么向量库固然重要但文本怎么切直接决定了检索命中的质量。如果切得太碎一个完整的概念被拆到两个片段里模型看到的信息是残缺的如果切得太粗一个片段里塞了太多无关内容向量相似度会被稀释召回结果飘忽不定。常见的切分方式有两种固定长度切分和语义切分。固定长度切分是按字符或者token数量无脑切比如每300个字符一段段与段之间重叠50个字符。这种方式简单、稳定适合大多数情况尤其是中英文混合、代码、日志这类文本。语义切分则是按段落、标题、句子边界来切比如Markdown格式的文档按标题层级切分效果更符合人类的阅读逻辑但实现复杂度高一些。关于切分大小我建议你以“检索后拼给模型之后模型能否独立理解这一段”为标准来调整。如果你们公司准备上一套RAG拿业务文档先试一把多用几个chunk size跑一遍看哪个尺寸命中率最高。经验值说说中文场景下chunk size在300到500字之间通常比较稳妥overlap设置在50到100字。太长会导致多个观点挤在一起向量方向被“平均化”太短则上下文不足。表格数据尽量保持完整行组合成块不要拆断字段。2.3 向量化嵌入把文字变成坐标文本切完之后要转成计算机能计算的向量。这一步的核心是嵌入模型Embedding Model它把一段文字映射成一个高维向量语义相近的文本在向量空间里的距离更近。你对嵌入模型的选择会影响整个管道的召回上限。目前常用的开源嵌入模型有BGE、M3E、GTE、text-embedding-adapter等闭源的如OpenAI的text-embedding-3-small、阿里云的文本向量模型。选型时要考虑中英文双语能力、向量维度、支持的最大token长度、是否本地部署、成本。如果你处理的是纯中文业务文档直接用支持中文的模型比用通用英文模型效果好得多。最怕的是中英文混排的专业术语比如“RAG pipeline”很多模型会把它们分开处理导致向量偏移。这里要补充一个细节嵌入模型不是越大越好。大模型生成的向量质量更高但推理成本也高。小模型在垂直领域经过微调后效果可以超过通用大模型。所以不要盲目迷信热门建议抽200条有代表性的测试问题分别用两三个模型跑一下检索命中率再做决定。向量维度也需要关注。高维度比如1024维或1536维表达能力强但占用存储多检索计算量大低维度如256维或384维更轻量适合快速实验。有些向量库支持量化压缩把浮点向量转成二值或8位整型大幅减少存储代价是少量精度损失。不是极端场景我建议先用原始精度跑通再考虑量化。2.4 存储与召回向量数据库怎么挑怎么用向量化之后要存起来这就轮到向量数据库登场。市面上的选择很多FAISS、Milvus、Qdrant、Chroma、Weaviate、Elasticsearch向量插件、pgvector等。我的看法是阶段不同选型不同。如果你在做原型验证、个人项目、不超过几万条文档片段直接用FAISS或者Chroma就够了。FAISS是Meta开源的内存索引库没有服务器轻巧灵活Chroma更偏向RAG一站式体验自带数据持久化。如果数据量到了百万级以上、需要分布式部署、要支持过滤和复杂查询就上Milvus或Qdrant或者直接用PostgreSQL加pgvector扩展让业务数据和向量数据在同一个库里减少维护成本。日常使用中我把向量数据库更多看成“召回服务”而不只是存储。关键参数有这几个top_k返回多少个候选片段。太小容易漏太大会把不相关的东西塞进上下文增加token消耗。我一般初始设置为5然后根据问题复杂度调整。score阈值相似度分数低于某个值的结果应该被丢弃。很多RAG管道不加阈值导致明明没检索到相关内容硬把低相似度的片段塞给模型模型就只能瞎编。建议在召回后加一道过滤分数过低就返回“知识库中未找到相关内容”。元数据过滤向量数据库不只存向量还能存来源、章节、时间、类型等标签。检索时可以先用元数据缩小范围再做向量相似度计算这样既提升速度也提升准确率。比如用户问的是“2024年售后政策”你可以先过滤时间为2024年的文档再在剩余文档里做语义检索。存储和召回这块我强烈建议你把“召回日志”记录下来每个问题召回了哪些片段、分数是多少、最终答案引用了哪些来源。没有日志出问题时你只能抓瞎。日志是后面调优和排查的重要依据。3. 实操手把手搭一个最小可用RAG管道理论说了一堆下面来做点能跑的东西。我用Python选型尽量简单langchain负责文档加载和文本切分OpenAI或textembedding模型做嵌入FAISS做向量索引。这一步的目标不是做一个生产系统而是让你看到一个问题从输入到输出经过了哪些环节以及每一环节的参数到底影响什么。3.1 技术选型先跑通要紧技术选型的原则是能少依赖就少依赖能看清内部逻辑就看清。我见过很多新手一上来就部署Milvus、上K8s、接Elasticsearch结果调了一周环境问题连RAG本身的原理都没摸到。正确路径是先用单机内存版向量库跑通一条数据再考虑扩展。依赖安装很简单pip install langchain langchain-community langchain-openai faiss-cpu openai如果你不想用LangChain也可以自己写加载逻辑。但在没有明显痛点之前用LangChain能省很多时间。需要注意版本兼容问题LangChain更新很快我下面的代码以2025年的稳定版为基准实际运行时如果遇到API变动优先查对应版的文档。3.2 代码实现从文档导入到问答一回假设我们有一个docs/目录里面放了几篇Markdown格式的公司产品说明。我们把它加载、切分、向量化、存入FAISS然后写一个最简单的查询函数。from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import FAISS from langchain_openai import ChatOpenAI from langchain.schema import HumanMessage, SystemMessage # 1. 加载文档 loader DirectoryLoader( ./docs, glob**/*.md, loader_clsTextLoader, loader_kwargs{encoding: utf-8} ) docs loader.load() # 2. 切分 splitter RecursiveCharacterTextSplitter( chunk_size400, chunk_overlap80, separators[\n\n, \n, 。, ., , ], ) chunks splitter.split_documents(docs) # 3. 向量化 存储 embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vectorstore FAISS.from_documents(chunks, embeddings) vectorstore.save_local(faiss_index) # 4. 检索 生成 def rag_answer(question: str, top_k: int 5): db FAISS.load_local(faiss_index, embeddings, allow_dangerous_deserializationTrue) hits db.similarity_search_with_score(question, ktop_k) context \n\n.join([doc.page_content for doc, score in hits]) llm ChatOpenAI(modelgpt-4o-mini, temperature0.1) messages [ SystemMessage(content你是一个知识问答助手只能根据给定的上下文回答问题。 如果上下文中没有答案请明确说‘知识库中没有相关信息’不要编造。), HumanMessage(contentf问题{question}\n\n上下文\n{context}) ] return llm.invoke(messages).content, hits这段代码里有两个容易被忽略的点。第一temperature0.1生成任务里不要把温度调太高否则模型容易在不确定时发挥想象力。第二SystemMessage里明确限制模型“只能根据上下文回答没有就直说”这是抑制幻觉的第一道防线。跑一次查询answer, hits rag_answer(我们公司支持哪些支付方式) print(answer) print(召回片段数, len(hits)) for doc, score in hits: print(f分数{score:.4f}, 来源{doc.metadata.get(source)}, 内容前50字{doc.page_content[:50]})你会看到输出包含答案和召回的原文片段。检验RAG是否跑通不是只看答案漂不漂亮而是先看召回片段是否相关。如果召回片段本身牛头不对马嘴那再好的生成模型也救不回来。3.3 评估召回效果别只凭感觉“看起来还行”“好像回答得挺准”这种主观感受不能作为上线依据。我建议你用一个最简单的评估方式准备10到20个业务问题每个问题注明它对应哪份文档的哪个片段然后写脚本批量跑检索统计召回片段中是否包含对应原文。评估指标用两个就行hit rate被测试问题中正确答案片段是否出现在召回前k条里。比如20个问题有16个的正确片段被召回到前5那hit5就是80%。MRR (Mean Reciprocal Rank)正确答案片段在召回列表中的排名倒数取平均。如果排名越靠前MRR越高。手动构造测试集虽然要花一点时间但它能让你快速发现切分粒度和检索参数的问题。我在实操中遇到过这样一个案例我原本以为问题是embedding模型不够好反复换模型都提升不大。后来一查日志发现大量查询命中的都是同一份文件里的低质量片段而正确答案排在十几位。问题出在切分时把表格拆散了字段和数值都被分到不同chunk。修正切分逻辑后hit5从52%直接提到86%。所以第2章里强调的“切分是隐藏杠杆”就在这里。光调向量库参数不解决内容结构问题。评估工具也不用复杂一个脚本就够了。生产环境可以接LangSmith或自建记录系统但现在我们先把度量能力养起来。4. 从基础RAG到Agentic RAG知识管道的升级路径基础RAG能解决60%的“查资料”问题但它不够聪明。它的问题是无论用户问什么都做一次相似度检索然后无脑用料生成。真实场景里用户的问题可能是多跳的、模糊的、需要推理的。这时候就需要让Agent参与知识获取的过程也就是所谓的Agentic RAG。4.1 朴素RAG的痛点我用三个真实场景说明朴素RAG的局限。第一问题表述与文档风格差异大。用户问“最近能不能申请居家办公”但文档里写的是“员工弹性办公管理办法”关键词完全不重叠向量相似度得分很低召回结果自然差。如果Agent先把问题改写成更规范的检索查询比如“弹性办公申请条件”效果立刻不同。第二需要多跳检索。用户问“谁负责审批我们的外读申请他平时在哪个办公室”这个问题要拆成两步先找到制度文档里关于外读审批的章节再从组织架构文档里找到审批人的办公室信息。朴素RAG一次检索不可能同时命中两个文档片段。Agent可以把这个问题拆成两个子查询分别检索再汇总。第三需要判断什么时候不检索。用户跟Agent闲聊“今天天气怎么样”这时候去知识库检索是浪费。又比如用户问的是数学模型题应该让模型直接推理而不是检索。Agent可以先用意图识别判断需要调用知识库还是直接生成。4.2 何时引入Agent判断我不建议一上来就做复杂的Agent编排而是先给检索增加一层“路由控制”。用一个较弱的分类模型或规则判断用户问题是否需要检索如果需要是检索整个知识库还是指定某一类文档。这一步能过滤掉大量无效召回。接下来增加“查询改写”。对原始问题做扩充或改写让它更适合向量检索。比如原问题是“上次说的那个报销规则”改写为“差旅报销规则及报销时限规定”能明显提升命中率。这时候可以用一次LLM调用完成改写成本不高收益很大。再往后就是“多轮检索”。对复杂问题用Agent的工作流循环执行检索评估召回片段是否足够回答如果不够再看缺什么条件重新生成查询再检索。这个“评估-再检索”的循环就是Agentic RAG和普通RAG的分水岭。比较轻量的方案是LangGraph里构建一个带条件分支的图节点包括“检索”“评判”“生成”评判节点决定是否回到检索节点。我建议的落地节奏是先把基础RAG跑稳评估指标达标了再加查询改写再上路由最后做循环评判。中间每一步都加日志和评估不要一口气堆出个黑盒系统。4.3 可落地的小项目练手如果你看完这篇想动手我推荐三个练手项目由易到难。第一个是“文档问答机器人”用公司或自己的笔记库搭建一个本地RAG要求答案必须引用来源做一个简单的命令行或网页交互。重点练习文档清洗和切分。第二个是“多知识库路由Agent”把文档分成技术、人事、财务三个库先用小型分类器识别问题的归属域再路由到对应库检索。重点体会元数据过滤和路由对准确率的提升。第三个是“多跳问答工作流”用LangGraph构建一个三步循环第一步检索、第二步判断信息是否足够、第三步决定再检索或生成。数据集可以用一些公开的开放域QA数据或者干脆用你自己的行业文档构造多跳问题。重点体会Agent编排对复杂问题的增益。这三个项目做完你对RAG的理解会比单纯读十篇文章深刻得多。而且每一步都能积累可复用的工具函数后面做生产系统心里会有底。5. 常见问题与排查技巧实录最后写点实操中踩过的坑。这里整理成一张速查表然后挑几个重点展开。问题现象可能原因解决思路回答里出现知识库没有的内容检索到了低相关片段或System Prompt约束不足提高分数阈值增加来源引用强制模型“不知道就说不知道”答案正确但引用的片段不对切分时上下文断裂语义不完整增加chunk_size或overlap保留段落边界检索结果与问题无关但模型还硬答查询用词与文档用词差异大加一层查询改写或使用混合检索特定文档永远召回不到切分时破坏了表格或结构或文档未清洗针对该文档单独设计清洗和切分规则召回速度慢向量库未加索引或元数据过滤缺失建立HNSW/IVF索引先过滤再检索成本过高embedding和LLM调用频繁增加缓存相似查询复用结果5.1 查不到或查到乱切分与检索参数排查“查不到”通常分两种情况一是完全没召回相关内容二是召回了但排名太低。先确认切分后的片段本身是否包含完整答案。一个简单的方法是直接读几个切分片段人工判断内容是否可读。如果一段话被从中间腰斩比如把一个产品参数表拆成了上下两半那基本就能确定问题在切分。接下来检查检索参数。top_k是否太小试试10。相似度计算方式是否合适有的向量库默认用余弦相似度有的用点积如果你之前用OpenAI嵌入注意OpenAI官方文档明确建议用余弦相似度。FAISS的IndexFlatIP是内积需要把向量归一化后才等价于余弦。别小看这个细节我见过有人纠结半天最后发现相似度都算反了。还有一个很隐蔽的坑中文标点与英文标点统一问题。文本切分时如果原文档里用的是英文逗号而查询输入用的是中文逗号某些embedding模型对这类细节比较敏感。清洗时尽量把标点统一成中文或英文一种形式。5.2 幻觉依旧存在召回增强与答案生成的边界有人以为RAG可以彻底消除幻觉这是误解。RAG只是降低幻觉概率模型依然可能因为上下文里的矛盾信息而产生错误推理。比如知识库有新旧两版政策召回时同时命中模型可能各取一半拼出一个不存在的规则。解决办法分两边。召回侧在知识入库时给文档打上版本号、生效日期等元数据并把这些元数据拼进片段文本中比如在每段前加一行“【来源2025年新版员工手册】”。这样模型更容易识别冲突。生成侧让Prompt要求模型“如果你发现上下文中存在相互矛盾的信息请明确指出并优先采用最新版本”这能在一定程度上减少误导。另外要给答案加上引用标记。让模型在回答里标注“来源1”“来源2”对应召回的片段编号。这样用户和开发者都能快速核验。我一般在SystemMessage里加一句“每个关键信息后面用方括号标注来源编号”然后在返回结果时把编号映射到元数据。如果还是频繁幻觉我的排查顺序是先看召回片段是不是真的包含正确答案再看Prompt是否约束到位最后才考虑换更强的模型。很多团队一遇到幻觉就换大模型纯属浪费钱。5.3 性能与成本平衡RAG管道的成本大头通常不在向量化而在生成阶段的LLM调用。每次检索生成都要消耗几千token如果Agent还做多轮改写成本翻倍。优化思路有几种。第一做检索缓存。把问题和对应答案存进缓存同一个问题只计算一次。第二对生成模型做分级简单问题用迷你模型复杂问题才用强力模型。可以通过召回到的片段数量、长度、置信度来触发模型升级。第三减少无效上下文召回的top_k不等于必须全部塞给模型。如果前3个片段已经很相关后面2个明显弱相关就只送前3个既省token又减少干扰。向量化成本也别忽略。如果每天有大量新文档入库每次都全量重算嵌入会非常慢。建议做成增量流程记录每个文档的哈希值内容没变就直接跳过只处理新增或修改的文件。知识库做到后面维护成本和构建成本一样重要管道要把这两件事都考虑进去。我个人在实际操作中的一点体会是RAG调试最忌“头痛医头”。经常有人问为什么召回不准第一反应是换embedding模型问了半天发现是切分有问题切分改好了又发现是查询表达有问题。整个管道是一条链路要带着测试集和日志去定位而不是靠感觉调参。我建议你从现在开始每做一个RAG项目先写一个简单的评估脚本把hitk和MRR跑出来再动手优化。有了数字很多争论会自然消失。最后分享一个小技巧在做完最小管道之后把召回结果打印出来去读一读那几段被召回的文本。很多时候你一眼就能看出哪里不对劲这比任何指标都直观。RAG的门槛不高但它是一门需要耐心打磨的实践手艺。希望这篇能把你的第一根知识管道顺利铺出去让它真正成为Agent的稳定“外接大脑”。
阅读完成 · 觉得有帮助?