☰
RAG知识获取管道全解析:从原理到Agent实战,构建大模型的开卷考试能力
2026/11/4 5:09:47 网站建设 项目流程

如果说前三篇是在给 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", encoding="utf-8") documents = loader.load() splitter = RecursiveCharacterTextSplitter( chunk_size=600, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""], keep_separator="end" ) chunks = splitter.split_documents(documents) print(f"共切分 {len(chunks)} 个文档块")

这里 separators 里我放了一堆中文标点,目的就是让切分尽量落在语义完整的句子上,而不是硬生生卡在某个字中间。keep_separator="end" 让句末标点跟着前文走,这是中文切分的小技巧。

接下来向量化和入库:

from langchain_community.embeddings import HuggingFaceBgeEmbeddings from langchain_community.vectorstores import Qdrant embedding_model = HuggingFaceBgeEmbeddings(model_name="BAAI/bge-m3") vectorstore = Qdrant.from_documents( documents=chunks, embedding=embedding_model, path="./qdrant_local", collection_name="knowledge_base" )

这样本地向量库就建好了。后续要加载已有库,不需要重新向量化,改成Qdrant.load_local就好。

检索并重排:

retriever = vectorstore.as_retriever( search_type="similarity", 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_name="BAAI/bge-reranker-v2-m3") compressor = CrossEncoderReranker(model=reranker_model, top_n=3) compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=retriever ) top_docs = compression_retriever.invoke("保修期是几年?")

最后组织 prompt 和生成。这里以调用本地 Ollama 上的模型为例:

from langchain_ollama import ChatOllama from langchain_core.prompts import ChatPromptTemplate llm = ChatOllama(model="qwen3:14b", temperature=0) 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 拉到 10,rerank_top_n 用了 5,那么每次请求送给模型的检索内容就有大约 3000~5000 个字符,如果用户的上下文再叠加上一轮历史,大模型窗口很容易顶到一两万 token。一旦模型窗口溢出,服务直接报错。

我处理的办法是分流:对纯知识问答场景,不把完整历史对话全部塞给模型,只保留最近两轮摘要;检索内容进入模型前做压缩,无关部分裁掉;rerank_top_n 降到 3。同时引入缓存,相同问题直接返回历史答案,避免重复查库和重复调用大模型,这在知识检索场景里尤其有效,因为用户问的问题往往高度集中。

4.4 问题排查速查表

下面这张速查表是我每次项目上线前必过的检查清单:

现象可能原因排查方向
检索结果里没有正确答案切分把答案拆碎先打印召回内容,再调切分策略
检索结果相关但顺序很差缺少重排环节加交叉编码器重排
回答里写“找不到”但资料里有score_threshold 太高下调阈值或检查 recall
答案和资料矛盾prompt 约束不足强制只依据资料,未提及则未知
响应太慢、请求超时候选集太大、模型窗口超限减小 top_k、缩短历史、加缓存
更新文档后仍返回旧内容索引没更新建立文档版本号,检索时加时间过滤
中文专有名词匹配不上向量语义泛化不足加 BM25 混合检索或同义词扩展

5. 从 RAG 走向 Agentic RAG

5.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 如何自主决定什么时候翻书、什么时候直接回答。先聊到这儿,希望这篇能帮你少踩几个坑。

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

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

立即咨询