☰
从零搭建 RAG 管道:让 Agent 拥有可靠的外部知识获取能力
2026/9/26 21:00:56 网站建设 项目流程

1. 为什么 Agent 光靠大模型“内置知识”不够:先把 LLM 和 Agent 的关系说清

这段时间后台收到的高频问题里,有一个特别有意思:“DeepSeek 到底算大模型还是算 Agent?我拿它写提示词是不是就等于在搭 Agent?”我的回答通常很短:DeepSeek、Qwen 这些产品本质上都是 LLM,它们负责“理解语言、生成语言”;而 Agent 是构建在 LLM 之上的一个完整闭环,包含目标拆解、工具调用、记忆管理、自我纠错等动作。你拿 LangChain 写了几行链式调用,严格来说还是在“用 LLM API”,离真正的 Agent 还差一个决策循环。RAG 在这套体系里的角色,就是给 Agent 装上“外部知识获取”这根管子——让它在回答问题时,不是只靠模型参数里存的旧知识,而是能主动翻资料。

把这个问题拆开还有个好处:很多同学纠结“我到底应该学 RAG 还是 Agent”,其实这两件事根本不在同一层。RAG 是一个能力组件,Agent 是一个系统架构。Agent 可以用 RAG 来补知识,也可以不用;但每一个需要回答业务问题的 Agent,最终都绕不开“知识从哪来”这个坎。这就是我把这一篇定位成“知识获取管道”的原因——它是 Agent 工程化落地时最实在、最基础的一个模块。

1.1 大模型的三块天生短板

先看 LLM 自己有哪些解决不了的事。

第一,知识是静态的。模型训练好之后,它的参数就冻结了。你问它公司上个月刚调整的产品价格,它大概率会给你编一个合理但错误的数字。预训练数据截止时间决定了它的知识上限,任何在这个时间之后发生的事情,它都不知道。第二,幻觉是无解的。它不是“偶尔犯错”,而是在面对不确定信息时,会用概率最高的词“填满”答案。说得难听一点,你问它一个它没见过的型号参数,它宁可编一个也不愿意承认不会。第三,私有数据它是完全接触不到的。你的个人笔记、公司 ERP 里的库存数据、售后工单、合同条款,这些东西根本不在公开训练集里,模型再大也没用。

这三个短板加起来,正好指向同一个问题的三个层面:知识不是“永远在线”的。而 Agent 区别于普通聊天机器人的核心价值,恰恰是要处理具体的、动态的、私有的任务。所以一个 Agent 光接一个 LLM API 是不够的,它还需要一条能够持续从外部取知识的管道。

1.2 补知识的两种主流思路:微调与 RAG

知识缺失的解决方案,行业内基本就两条路。

一条是微调(Fine-tuning),把知识直接写进模型权重。听起来很优雅,但实操时会遇到一系列问题:标注数据从哪来、一次微调要花多少训练资源、知识更新后要不要重新训、会不会把模型原本的能力“学坏”。我见过不少团队把微调当万能药,最后光维护训练数据就累得够呛。另一条就是 RAG(Retrieval-Augmented Generation,检索增强生成),把知识放在模型外部,回答问题时先检索相关内容,再把检索结果拼进上下文提示词,让模型基于这些材料作答。

用一个不太严谨但很好懂的类比:微调相当于考前熬夜把整本教材背进脑子里,RAG 则是带着参考书进考场,遇到哪题翻哪页。对 Agent 来说,知识更新是常态,业务数据是动态的,RAG 这种“按需取用”的方式明显更划算,成本更低、更新更快、还能给答案标来源。这篇后面讲的整个管道,就是围绕“如何让这套开卷考试机制跑得又快又准”展开的。

1.3 Agent 的知识来源其实是一个组合件

顺便把 Agent 的知识结构梳理一下,否则容易把 RAG 当成唯一知识来源。

Agent 的知识大致分四类:参数记忆,也就是 LLM 权重里存的语言知识和常识;工作记忆,就是当前对话上下文,几轮内的事;长时记忆,指外部存储,包括向量数据库、普通数据库、知识图谱;工具调用结果,比如调 API 查到的实时天气、库存数量、订单状态。RAG 管的是长时记忆里的“显性知识”,尤其是非结构化文档——PDF、Markdown、手册、邮件、聊天记录。它不适合管那些强结构化、需要精确计算的场景,那种场景交给 SQL 查询或 API 调用更可靠。

所以你看,RAG 不是“包治百病”,而是 Agent 知识体系里的一块重要拼图。理解了这块拼图的边界,下面再讲实现细节,你就能明白为什么要这样设计。

2. RAG 的完整工作链路:一次查询从入库到出答案,中间到底发生了什么

RAG 不是一个单独算法,而是一条管道。很多人一上来就写代码,结果检索不准也不知道该调哪里。先把链条画清楚,后面所有调优都会变得有方向。整条链路可以分成三阶段:索引(Indexing)、检索(Retrieval)、生成(Generation)。

2.1 索引阶段:把杂乱文档变成可查询的向量

索引阶段解决的是“让机器提前知道有哪些知识”。这个过程通常有四道工序:

第一是文档加载与清洗。你的原始文档可能是 PDF、Word、Markdown、HTML,里面掺杂页眉、页脚、表格、图片说明、乱码换行。加载之后不能直接拿去切块,要先做清洗,把和正文无关的内容去掉。这一步看起来不起眼,实际影响非常大,你后面会觉得“这答案怎么一股页码味儿”,多半就是清洗没做到位。

第二是切块(Chunking)。长文档不可能整篇向量化——一方面 embedding 模型有输入长度上限,另一方面检索时粒度过大,相似度会被稀释。切块就是把长文档切成一段一段语义相对完整的片段。切多长、按什么边界切,是 RAG 调优里最关键的活儿,我后面专门用一小节来讲。

第三是向量化(Embedding)。把每个文本块用 embedding 模型转成一个固定维度的向量,比如 1024 维。这个向量的含义是“这段文本在语义空间里的位置”,语义相近的文本,向量距离也应该相近。这个环节的质量直接决定了“系统能不能理解相似”。

第四是入库。向量库(Vector Database)负责存储这些向量,并提供按相似度检索的能力。Chroma、FAISS、Milvus、pgvector、Elasticsearch 都是常见选择,各有各的适用场景。索引阶段做完,你的知识就变成了一份“可查询的语义地图”。

2.2 检索阶段:把用户问题变成一次“语义搜索”

当用户问出“A500 最大功率是多少”时,系统先把这个 query 用同一个 embedding 模型转成向量,然后在向量库里计算它和所有文档块的相似度,按分数从高到低取 top-k 个候选片段。这一步的核心是“召回”,目标不是给答案,而是把最相关的材料捞上来。

很多新手会在这里犯一个认知错误:认为向量检索是全能的。实际上,向量检索擅长“语义相近”,但不擅长“关键词精确命中”。比如产品型号有特殊符号,或者文档里用“峰值功率”而你问“最大出力”,纯向量检索就可能漏。所以线上环境通常会做混合检索:向量检索加上 BM25 这类关键词检索,再把两路结果合并排序。先别急着上一堆复杂配置,等你把基础管道跑通之后,再根据失败案例决定要不要加这一层。

2.3 生成阶段:让模型只看资料说话

检索出来的 top-k 片段会被拼装进提示词,作为参考上下文。同时 System Prompt 里通常要写死一条规则:只能依据提供的资料回答,资料里没有就要明确说“不知道”,而不是自由发挥。然后把用户问题一起交给 LLM,生成最终答案。

这里有个细节值得留意:检索出的片段可能互相矛盾,也可能都不相关,LLM 得有能力“拒绝回答”。所以提示词不能只写“请回答”,要写“请先判断材料是否足以回答,再组织语言”。一个好的生成阶段,还应该在答案后面附上来源片段编号或文档位置,方便用户核对。这个习惯在 Agent 场景里尤其重要——Agent 一旦把查询结果回传给用户,用户无法验证对错,来源就变成了信任基础。

为了让你更直观地感受整条链路,随便举一个例子:假设库里有一份产品手册,文本块是“A500 系列最大输出功率为 1200W,适配电压 220V”。用户 query 是“A500 能带多大功率的电器”,系统检索时发现语义相近,召回这个片段,LLM 基于它生成“A500 系列最大输出功率为 1200W”。你会发现,答案里的信息全部来自外部资料,模型只是负责组织和润色。这就是 RAG 的本质。

3. 从0到1搭一个最小可复现的 RAG 管道:代码与验证

理论基础说完了,下面动手。我不会给你一个非常重的企业级架构,而是先搭一个最小可复现的管道,让你亲手把整条链路跑通。逻辑通了,后面换组件、加功能都是水到渠成的事。

3.1 技术选型:先用最轻的组件跑通

RAG 相关的组件选型非常多,新手特别容易被工具绕晕。我先给出一套适合“本地练手”的组合,再给一张替换对照表。

环节轻量入门方案生产环境可选方案
文档加载TextLoader 读 Markdown/文本pdfplumber、Unstructured、OCR 服务
文本切块RecursiveCharacterTextSplitter按 Markdown 标题结构切、语义切块
向量化HuggingFaceEmbeddings 本地模型公司自建 embedding 服务、领域微调模型
向量库Chroma(纯本地、零部署)Milvus、pgvector、Elasticsearch
生成模型DeepSeek / Qwen 兼容接口按预算、合规和延迟需求选择

这套组合的好处是:除了模型调用之外,其他环节全都落在本地,不需要额外搭建服务。Chroma 一条命令就能持久化到本地目录,特别适合先把原理跑明白。

3.2 核心代码流程

我这里以 Python + LangChain 生态为例。如果你在 Java 技术栈,LangChain4j 或 Spring AI 也有对应的 RAG 模块,核心概念完全一致,只是 API 不同。

from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_huggingface import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate # 1. 加载文档 loader = TextLoader("./product_manual.md") raw_docs = loader.load() # 2. 切块:块大小 500,重叠 80,优先按段落切 splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", " ", ""] ) docs = splitter.split_documents(raw_docs) # 3. 向量化并入库 embedding = HuggingFaceEmbeddings(model_name="BAAI/bge-m3") vectorstore = Chroma.from_documents( docs, embedding=embedding, persist_directory="./chroma_db" ) # 4. 检索:返回最相关的 4 个片段 retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) query = "A500 最大功率是多少?" hits = retriever.invoke(query) # 5. 把检索结果拼成上下文,交给大模型生成 context_text = "\n\n".join( f"[片段{i+1}] {doc.page_content}\n(来源: {doc.metadata.get('source', '未知')})" for i, doc in enumerate(hits) ) prompt = ChatPromptTemplate.from_messages([ ("system", "你只能依据以下资料回答问题,资料里没有的信息要明确说不知道。\n\n{context}"), ("human", "{question}") ]) llm = ChatOpenAI( model="deepseek-chat", api_key="你手头的模型服务密钥", base_url="你的模型服务地址" ) chain = prompt | llm answer = chain.invoke({"context": context_text, "question": query}) print(answer)

这段代码里,前面四步基本上就是索引和检索的全部内容,第五步才是生成。这里有个容易被忽略的点:不同版本 LangChain 的包名经常调整,比如新版把 HuggingFaceEmbeddings 挪到了 langchain_huggingface 里。遇到 ImportError 不要慌,先看你安装的版本,然后按报错信息找对应包名。代码这种东西,能跑通比“写得优雅”重要。

3.3 怎么判断管道真的跑通了

跑通代码不算完,你要用几个测试问题来验证效果。我的习惯是准备三组问题:第一组是“文档里原话能直接命中”的问题,比如“A500 最大功率是多少”;第二组是“文档里换了种说法”的问题,比如“这个型号能带多大负载”;第三组是“文档里根本没有答案”的问题,比如“这个产品的研发团队有谁”。前两组看检索和生成是否正常,第三组看它是否老老实实说不知道。

如果第三组问题它开始编答案,说明你的 System Prompt 约束还不够硬。如果第一组能命中但第二组召不回,说明 embedding 或切块策略还没到位。我建议你把每次检索返回的片段先打印出来看一遍,而不是只看最终答案。这一步能帮你快速判断问题出在检索环节还是生成环节,调优就有一半的把握了。

4. 检索质量的四个命门:切块、Embedding、召回策略和重排序

同样一份文档,有人搭出来的 RAG 准确率很高,有人搭出来像人工智障,差别往往不在代码,而在四个关键抉择。这四件事决定了“检索到底能不能捞到正确答案”,也决定了生成质量的上限。

4.1 切块:块的大小和边界决定召回上限

切块是整个管道里最“微妙”的环节。块太小,一个语义单元被截断,比如“A500 最大功率为 1200W,适配电压 220V”被切成两半,检索时各自语义都不完整;块太大,一个片段里混了很多主题,向量表达被稀释,相似度也不准。

我的经验参数是:一般文档从 300 到 800 字符起步,重叠区设 10% 到 20%。切分边界优先用自然段落,“\n\n” 或句号、问号这些语义断点。对于有层级结构的 Markdown 或 HTML,按标题切块往往比纯字符数切更合理,因为标题天然定义了一个语义区块。结构化表格就更特殊,普通切块会把一行表格切开导致语义错乱,最好单独做表格解析。

切块没有一个“最正确”的参数,只有“最适合你文档”的参数。你把文档类型、块大小、重叠度、检索结果质量放到一起对比,调几轮就有手感了。

4.2 Embedding 模型:决定相似度是否可信

Embedding 模型决定了“语义相似”这件事靠不靠谱。通用模型在普通文本上表现不错,但遇到专业术语、产品型号、中英文混杂时,相似度计算往往会失灵。比如“R200”和“R2OO”,人眼一眼看出是同一批货,embedding 可能完全分不出来。

选型时重点看三个方面:中文效果是否达标、支持的最大输入长度、向量维度带来的存储和计算成本。开源方案里像 BAAI/bge-m3 这类模型在中文场景表现比较稳定,而且支持本地运行,不依赖外部接口。如果你的语料有很强的领域属性,后续可以考虑用领域数据微调一个专用 embedding 模型。不过我建议:不要一上来就折腾 embedding 替换,它属于“底层换血”,影响面最大、成本最高。先把切块、召回、重排这几种更便宜的手段用尽,还不够再考虑换 embedding。

4.3 召回策略与重排序:让正确答案浮出水面

单纯用向量检索召回 top-4,遇到复杂问题很容易把相关片段漏掉。改进方向有两个:一是混合召回,二是重排序。

混合召回就是向量检索和关键词检索并行,然后把两路结果做合并去重。这套方案特别适合“产品型号、部门缩写、专业代码”这类场景,因为这些词往往是精确匹配比语义匹配更可靠。重排序则是把召回范围先放大,比如先取 top-50,再用一个重排模型(Reranker)对这些候选做精细打分,挑出真正相关的 top-5 送给 LLM。重排模型和 embedding 模型不同,它是“给定问题和片段,直接预测相关性”,准确性通常更高,代价是多一次模型推理。

从工程成本看,重排序是性价比极高的优化项。很多RAG项目在加了重排之后,答案可用率明显提升。但要记住,重排也不能拯救极度糟糕的召回——如果前 50 个候选里根本没有正确答案,重排再准也没用。

4.4 查询改写:把用户口语变成可检索的 query

最后一个命门最简单也最容易被忽略:用户问的问题往往不适合直接拿去检索。

举个例子,用户问“那这个型号能带多大功率呢?”这个 query 语义不完整,“这个型号”指代不明。直接向量化检索,效果自然差。Agent 场景里更常见:多轮对话之后,用户说“它呢”,系统若无其事地拿“它”去做检索。正确处理办法是在检索之前加一个 Query Rewrite 步骤,让 LLM 先把用户口语结合对话历史改写成独立的、完整的检索 query,比如“A500 系列在 220V 电压下的最大输出功率是多少”,然后再进入检索环节。这一步不复杂,但对检索质量的影响是立竿见影的。

5. 我实际踩过的五个坑:检索不到、上下文拼接失控、多轮对话失效

理论看着都清楚,一进实战还是会翻车。下面这几个坑是我在不同项目里实打实遇到过的,每个都附上排查思路,你以后遇到可以直接照着走。

5.1 明明文档里有答案,却检索不到

第一次跑 RAG 时最容易崩溃的场景:我把答案写在文档里了,问它它就是答不上来。排查链路基本是这样:先把检索返回的 top-k 片段全部打印出来,肉眼确认相关片段在不在里面。如果片段里没有,说明召回阶段就没捞到;如果片段里有但答案不对,问题在生成阶段。

召回阶段捞不到的常见原因:切块把完整语义切断了;文档里用的是“峰值功率”,问题里用的是“最大功率”,embedding 没把它们关联起来;特殊符号“R200-A”被切块切成两截;或者是纯向量检索对关键词不敏感。解决办法就是前面讲的:调切块边界、上混合检索、加查询改写。我的经验是,先加混合检索往往能解决一大半问题,因为它能在语义检索失效时兜底。

5.2 材料检索对了,生成结果还是答非所问

有一种更隐蔽的情况:检索返回的片段确实是相关的,但 LLM 生成时还是乱编。我在一个项目里遇到过,System Prompt 写的是“请基于以下资料回答”,没写负面约束,模型就自由发挥补齐了很多资料里没有的内容。

解决这件事分两手。第一手,System Prompt 要写得很硬:只允许使用资料中的信息;资料不足以回答时,明确回复“资料中未找到相关信息”;不得推测、不得编造。第二手,把检索片段编号并在生成时要求模型引用,例如“根据片段3的信息,A500 最大功率为 1200W”。这样即使答案出错了,你也知道它是从哪个片段推理出来的,定位问题会快很多。

5.3 多轮对话里的指代问题

Agent 和普通问答的最大区别就是多轮对话。用户说“那个呢?”、“价格呢?”、“和上一款比呢”,这些“那个”“上一款”如果不处理,检索出来的结果全是噪声。

我踩过的坑是把对话历史原封不动也塞进上下文,让 LLM 自己“感受”指代关系。结果模型确实能感受,但在检索阶段,query 还是“那个呢”,向量检索完全找不到北。正确做法还是在进入检索之前先做查询改写:把整段对话历史喂给 LLM,让它生成一个不依赖上下文的独立 query。这一步可以在 Agent 里做成一个小 skill,每次调用 RAG 工具前自动执行。

5.4 增量更新与旧版本数据污染

当一个 RAG 系统上线后,文档会持续更新。最粗鲁的做法是每次全量重建索引——数据量小的时候问题不大,但积累到几百万片段时,全量重建的成本和耗时都不可接受。

更现实的做法是增量更新:新增文档直接加进去,修改后的文档用新的 chunk 覆盖旧的,删除的文档要主动清理。向量数据库基本都支持按 ID 删除和更新,但前提是你在入库时就给每个片段规划好稳定的文档 ID 和版本号。我在实际项目里吃过一次亏:旧版产品手册入库后没有及时清理,结果同一个问题同时检索到新旧两版参数,模型直接混乱。后来我加了一张“文档版本表”,每次更新都记录版本号和生效时间,这个问题才彻底解决。

5.5 把相似度阈值当成万能参数

很多教程会告诉你“设置相似度阈值 0.7,低于这个分就判定为无关”。但我要泼一盆冷水:embedding 相似度的分布和模型、文档类型、切块方式都有关系,0.7 在一个系统里是好阈值,在另一个系统里可能把所有正确答案全部过滤掉。

我的建议是:不要把相似度阈值当成固定的安全网,而是把它当成最后一道防线。先用 top-k 召回,再用人工抽查返回片段来感受分数的分布,最后根据你业务能接受的“漏报率”来定阈值。上线后还要持续监控,因为文档更新后分数分布可能会漂移。

6. 从基础 RAG 到 Agentic RAG:知识获取管道还会怎么演进

把基础 RAG 跑通之后,你会发现它本质上是一条“固定管道”——用户一提问,就检索一次,生成一次。但 Agent 场景里,知识获取往往需要更灵活的姿态。这一节聊几个和热词密切相关的话题:RAG 和 MCP 的区别、Agentic RAG 是什么,以及还能往哪个方向练手。

6.1 RAG 与 MCP:知识管道和工具协议的分工

最近问“RAG 和 MCP 有什么区别”的人特别多,因为这俩都跟“让模型获取外部信息”有关。我的理解是:RAG 解决的是“知识如何进入上下文”,核心是检索;MCP(Model Context Protocol)解决的是“模型如何标准化地调用外部工具和数据源”,核心是协议。

RAG 关心的对象通常是文档,非结构化的知识,比如 PDF、手册、笔记;MCP 关心的对象通常是服务和数据源,比如数据库、ERP 系统、内部 API、外部平台。两者不是替代关系,而是互补关系。你完全可以把一个 RAG 服务封装成 MCP 工具,让 Agent 通过 MCP 协议按需调用这个知识管道。在 Agent 系统里,RAG 是“知识存储与检索层”,MCP 是“工具接入层”,它们各司其职。

6.2 Agentic RAG:把“要不要查、怎么查”交给 Agent 判断

传统 RAG 的问题是“被动”:不管问题需不需要外部知识,它都会检索一轮,检索结果不好也不会重试。Agentic RAG 则把知识获取变成一个决策循环,Agent 自己决定要不要检索、去哪个知识库检索、当前检索结果够不够、要不要换个 query 再试一次,甚至多轮检索后把不同来源的信息做交叉验证。

举个例子。用户问“我们上个月的退货率为什么升高”,传统 RAG 可能直接检索文档给了个泛泛答案。Agentic RAG 的做法是:先生成一个问题推理,判断“退货率”这个数字在文档里没有,可能需要查数据系统;于是 Agent 决定调用一个数据查询工具,拿到数字后再结合知识库里的售后政策文档,综合分析后才给出结论。这种“检索只是其中一个步骤”的形态,才是 Agent 场景下 RAG 的完整形态。

但是要提醒一句:不要一上来就追新概念。Agentic RAG 的前提是基础 RAG 已经稳定可靠,否则 Agent 只是在更花哨地犯错。

6.3 几个可以直接上手的练手方向

如果你想把这套知识继续深化,我列几个我亲测有学习价值的方向:

  • 个人笔记知识库问答:把你日常写的 Markdown 笔记全部入库,做一个能按主题检索、按笔记生成回答的小助手。这是成本最低、反馈最直接的练手项目。
  • 本地 ERP + RAG + LLM 产品检索:很多企业内部都有 ERP 系统,里面有产品主数据、库存、价格。你可以先把产品说明文档做成 RAG,再把结构化数据通过 MCP 工具暴露给 Agent,做一个“既能回答参数、又能查实时库存”的助手。
  • 结合 Agent Skill:把“检索知识库”封装成一个可复用的 Skill,让 Agent 在多步任务中按需调用。这个方向能帮你理解 RAG 从“管道”变“组件”的过程。
  • 结构化知识与 Ontology RAG:普通 RAG 把文档看成一段段独立的文本,但很多知识是有关系网络的。引入知识图谱或本体(Ontology)后,检索可以沿着实体关系扩展,这属于 RAG 的高阶进化方向,适合想往深处钻研的同学。

我自己的体会是,RAG 这套东西,真正难的不是代码,而是“用户问法、文档形态、检索策略”这三者的匹配。工具会不断变——今天是 LangChain,明天可能是新的框架——但只要你能把“索引、检索、生成”这条链路里的每个环节都亲手调过一遍,后面任何新框架对你来说都只是换一层皮。先把最小管道跑通,再拿你自己的真实文档去折磨它,踩坑踩得够多,你自然就有手感了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询