☰
RAG进阶指南:AI Agent知识获取管道的设计与落地实践
2026/9/29 18:38:18 网站建设 项目流程

聊到 AI Agent,前面几篇我们把 Agent 的基础结构、工具调用、规划能力都过了一遍。这篇我们专门讲 Agent 的“记忆外挂”——知识获取管道,也就是 RAG 基础。

先纠正一个认知:RAG 在最火的那两年,被很多人等同于“文档问答机器人”,弄个向量库存一堆 PDF,用户问一句,系统检索几段拼给大模型,就算完事。但到了 Agent 时代,RAG 的地位完全变了,它不再是独立于 Agent 之外的一个"功能模块",而是 Agent 获取世界知识、私有知识、实时知识的核心管道。一个 Agent 如果在需要查资料、读文档、带规则决策的时候只能靠它训练时的那点记忆,那它和一本过期百科全书没有任何区别。

这篇文章我会用做实际项目的角度,把 RAG 如何内嵌进 Agent 的知识获取流程讲清楚,包括管道里每个关键环节的选型逻辑、参数取舍、踩坑记录,以及最后如何把整套管道封装成 Agent 的 Tool。不论你是准备给手头的 Java、Python 项目接入知识检索,还是在学习 Agent 开发,这篇文章都能给你一套可以直接落地的思路。

1. 为什么说 RAG 是 Agent 的知识获取管道

1.1 Agent 缺的从来不是“思考”,而是“带宽”

很多人构建 Agent 的第一反应是堆 Prompt,把要用的知识全塞进上下文里。这个思路在演示场景下确实有效,但业务系统里一碰就碎。你去观察真实的企业级 Agent,它面对的文档可能有几万份,每份几十页,产品的技术手册每天都在更新,客服话术每个季度都调整。全量塞 Prompt 既不现实,也不是成本问题,而是注意力被稀释的问题。

大模型的上下文窗口再大,它对长文本中间部分的关注度天然低于开头和结尾,业内叫 lost in the middle。把文档平铺给模型,看似信息都在,实际上模型处理起来的效果并不好,尤其在复杂推理任务里,混入大量无关文本会明显拉低准确率。这就是我说的带宽问题:模型处理信息的带宽是有限的,Agent 需要的是一个过滤器,把“当前任务相关”的知识精准提取出来,而不是把整个仓库的信息端到它面前。

RAG 恰好就是这个过滤器。它的核心价值不在于让模型“读”文档,而在于让模型“只读当前需要的部分”。从这个角度看,RAG 和 Agent 的关系不是拼接,而是内嵌:Agent 决定什么时候去查、查什么、怎么评价查回来的结果,RAG 负责把这个问题转化成一次高效的检索。

1.2 三条知识注入路线的取舍:Prompt、微调、RAG

谈到让模型掌握私有知识,绕不开三条路:直接塞 Prompt、做微调、做 RAG。我见过不少团队在这三个选项里反复挣扎,尤其是一些有算法背景的团队,一上来就倾向微调,觉得那才是“根治”。但从我实际案例看,选型逻辑根本不复杂,按下面三个维度判断就好。

先说微调。微调适合沉淀的是“不可言说的知识”,比如行话、语气、特定领域的输出格式、或者一个模型很难在推理中临时组织的思维范式。它把知识融进了权重里,响应速度不会变慢,但缺点是更新一次成本高,而且容易把模型原本的能力冲淡,也就是灾难性遗忘。我见过一个团队微调出一个带公司内部术语的模型,结果通用写代码的能力明显下降,最后不得不混合训练数据重新调。

再说 Prompt 注入,适合规则型、轻量级的知识,像某些操作规范、几条关键准则。这个方式直接但不适合大体量知识,而且现在很多 Prompt 注入方案会把文本精简成摘要再塞进 system prompt,本质上是把知识压成了指纹,损失了大量细节。

RAG 在这三类知识里站在中间:它适合的是“一张表没法说清、一个模型记不住、但能通过查文档找到答案”的知识,比如产品手册细节、某个接口的变更记录、行业规定条文。它最香的地方是更新成本极低,文档变了,重建索引就行,模型本体的权重完全不动。从维护角度讲,RAG 才是企业知识长效沉淀的正解。

1.3 Agent 场景下 RAG 的特殊形态

传统 RAG 的交互模式是“用户发问一句话,系统返回一段答案”。Agent 场景下完全不一样,Agent 的任务往往由多个步骤组成,它可能第一步需要先检索公司内部的知识库,第二步拿出检索片段判断下一步该调用哪个工具,再下一步带着检索结果去回答用户。也就是说,RAG 不仅仅服务于最终答案的生成,它在决策的中间环节也在被反复调用。

这也是这两年 agentic rag、RAG as a service 被讨论得越来越多的原因。Agent 需要的不再是一个静态的检索接口,而是一个可以被编排的、支持多轮查询、能感知上下文、能返回结构化知识片段的管道。比如 LangChain4j、Spring AI 这类框架里,已经有人把 RAG 直接封装成 Agent 的 Tool,让大模型自己决定什么时候查知识库,而不是每次对话都强制执行一次检索。

这种形态下的 RAG 要求更高:要有明确的输入输出契约,方便模型调用;要支持过滤条件,比如某个产品线、某个时间段;还要有意图判断能力,能识别“这个问题该不该检索”。这些我们在第五节展开,先把基础管道搞清楚。

2. RAG 管道各环节拆解与选型

2.1 文档解析:源头的坑决定后面所有环节的坑

很多人搭 RAG 第一个动作就是写 chunking 代码,这个顺序是错的。RAG 第一条潜规则:垃圾进,垃圾出。如果源文档解析得乱七八糟,后面分块、向量化、检索做得再好也是白搭。

文档解析实际碰到的坑比理论多得多。PDF 分两类,一类是文本型 PDF,文字可以直接提取;另一类是扫描件,必须 OCR,这个步骤非常容易翻车,尤其有表格、页眉页脚、多栏排版的内容。Word 文档要考虑样式层级,Excel 要考虑行列语义,PPT 要考虑标题与正文的关系。我见过一个项目,文档里有个关键的金额数字被拆成了两行,解析出来变成两个不完整的数字,后续检索即使命中也答错了,排查了大半天才发现是解析层的坑。

实操建议很简单:别把解析当成“附加项”,把它当成管道的第一道数据处理工序。每引入一种新格式,都要人工抽样检查解析结果,尤其注意:

  • 表格内容有没有被拆碎,语义是否还完整;
  • 页眉页脚、页码等重复内容是否被过滤;
  • 多栏文本的阅读顺序是否正确;
  • 扫描件 OCR 后的错别字对检索关键词的影响有多大。

如果你处理的是网页、Markdown、技术文档这类半结构化内容,解析难度会低一些,但还是得做标题树提取。因为后面的分块策略强烈依赖文档结构,没有结构信息,所有文本就像一锅粥,chunking 再聪明也只能靠窗口滑动硬切。

2.2 分块策略:chunk size 与 overlap 的取舍

分块是整个 RAG 管道里最容易被低估的一环。不少初学者的做法是固定一个 chunk_size,比如 500 个 token,然后按窗口滑动。这个做法能用,但效果往往一般,因为真实文档里语义边界和固定窗口格格不入。

我总结了一个分层分块的心得,在实际项目中比纯固定窗口效果稳得多:

  • 第一优先是按文档语义结构切,比如 Markdown 的标题层级、Word 的段落大纲、PDF 的章节。一个标题下的内容尽量作为一个完整块,这样语义聚拢性好;
  • 第二优先是看语言边界,一个代码示例、一条完整 SQL、一段独立的表格说明,都应该作为独立块保留;
  • 最后才是按 token 窗口兜底,在特别长的无结构文本里,用 300 到 500 token 的窗口切,overlap 设在 50 到 100 token 之间。

overlap 的作用是防止语义被切点在中间截断,比如一句话跨了两个 chunk,检索的时候如果只命中后半段,语境就丢了。但 overlap 不是越大越好,它会让相邻块之间的重复信息变多,检索结果容易出现大量相似内容,反而稀释了有效信息。

分块参数没有绝对最优,行业里推荐的做法是拿一批有代表性的 query 做测试,对比不同 chunk_size 的命中率。不要凭感觉调参,让数据说话。至于具体调到多少,取决于你的文档类型和模型embedding能力,后面实操部分我会给一套完整测试方法。

2.3 Embedding 与向量库选型

分完块之后要做的就是把每个块向量化。选 embedding 模型的核心标准不是排行榜刷分多高,而是在你的领域文本上表现如何。通用模型对技术术语、行业黑话的编码往往不够精准,比如“RBAC”和“权限模型”在不同语境下的语义相似度,通用模型可能判断得不够准。

建议先拿一批有代表性的 query,到你的文档库里跑一个召回测试,比较几个候选模型的表现。嵌入维度也是一个考量点:维度越高,向量化存储占用越大,但语义表达通常更细。不过很多场景下,用 768 维还是 1536 维差距并不明显,真正拉开差距的是模型在垂直领域的实战表现。

向量库选型要考虑的维度有检索质量、写入吞吐、过滤能力、生态兼容。我自己的经验:

  • 如果项目已经用了 PostgreSQL,直接上 pgvector,少一个组件,很多企业项目默认选它,运维成本低;
  • 如果对检索性能要求较高、查询模式偏复杂,比如要做混合检索、要频繁更新文档,Milvus 或 Qdrant 这类专用向量库更合适;
  • 如果只是个人项目或快速原型,Chroma、FAISS 都够用,先跑通逻辑最重要。

有一点特别想提醒:很多人只关注向量库的“相似度检索”,忽略了它的过滤能力。实际上业务系统里,时间范围、产品线、文档类别这些过滤条件,往往比 Top-k 检索对结果的影响还大。建议选型时就把 filter 支持程度列进关键评估项,不然后面每次想要定向检索,都得把全量数据拉出来再过滤,效率会非常低。

2.4 检索与重排:为什么召回 top20 比 top5 更稳

检索环节有个反直觉的规律:先多召回,再精排,比单纯追求一次性命中 top5 更可靠。原因很简单,向量相似度只是语义层面的粗筛,它找到“相关主题”的能力不错,但找到“真正能回答问题的那一段”的能力有限。把召回范围扩大到 top20,再通过重排模型对“问题-段落”做精细打分,往往能把真正有用的一段排到前面。

重排模型的英文 reranker,常见的有 bge-reranker 系列、Cohere 的 rerank 接口,以及一些开源 Cross-Encoder 类模型。它们做的事本质上是一次“更聪明的匹配判断”:不仅看语义相似,还会看两个文本之间是否有直接的问答关系。这个环节通常能把命中率提升 10 到 20 个百分点,代价是多一次模型调用,增加了毫秒级的延迟。

所以流程上,我建议把“检索”和“生成”之间加一道重排。这一步在传统 RAG 教程里经常被跳过,但工程化落地之后,体感差距非常明显。另外,检索时建议同时保留关键词匹配和向量匹配:关键词保证精确术语能命中,向量保证语义泛化能兜底,两者融合后再重排,是当前比较稳的组合。

3. 实操:从零搭一个能被 Agent 调用的 RAG

3.1 最小可用的数据管道

下面我们动手。假设我们要给 Agent 接一套内部知识库管道,文档格式是 Markdown 为主、少量 PDF。先写一套最小可用的数据管道,用 Python 演示,思路可以平移到 Java 或 Go。

# ingest.py - 数据管道核心流程 import os from langchain.text_splitter import MarkdownHeaderTextSplitter from langchain.embeddings import HuggingFaceEmbeddings from langchain.vectorstores import Milvus # 1. 加载文档 with open("docs/company_manual.md", "r", encoding="utf-8") as f: content = f.read() # 2. 按标题结构分块 headers_to_split_on = [ ("#", "H1"), ("##", "H2"), ("###", "H3"), ] splitter = MarkdownHeaderTextSplitter(headers_to_split_on) chunks = splitter.split_text(content) print(f"生成 {len(chunks)} 个文本块") # 3. 对每个文本块添加元数据 for i, chunk in enumerate(chunks): chunk.metadata["source"] = "company_manual.md" chunk.metadata["chunk_index"] = i # 4. 向量化并写入向量库 embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-m3") vector_store = Milvus.from_documents( documents=chunks, embedding=embeddings, collection_name="company_manual", connection_args={"host": "localhost", "port": "19530"}, ) print("索引构建完成")

这是最基础的一版,几行代码就能跑。但我建议你在生产环境里把它扩展成三个环节独立跑:解析层、分块层、索引层各做各的事,方便单独更新或排查问题。

解析和分块是两个最容易引起定位困难的环节,一旦检索结果不准,从头排查时解析和分块日志要能单独输出。我习惯在每个异步任务里加一个 trace_id,从文档加载一路带到 chunk 写入,后面调问题才能追根溯源。

3.2 检索接口与 rerank 实现

管道建好之后,Agent 需要的是一个干净的检索接口,而不是直接暴露向量库访问。这个接口应当接受一个 query,返回一组片段。我们在这加一道重排逻辑:

# retriever.py - 检索与重排 from langchain.vectorstores import Milvus from langchain.embeddings import HuggingFaceEmbeddings from sentence_transformers import CrossEncoder embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-m3") vector_store = Milvus(embeddings=embeddings, collection_name="company_manual") # 重排模型,初始化一次复用 reranker = CrossEncoder("BAAI/bge-reranker-base") def search(query: str, top_k: int = 20) -> list[dict]: # 1. 向量召回 top20 raw_results = vector_store.similarity_search_with_score( query, k=top_k ) # 2. 对召回结果做重排打分 pairs = [(query, doc.page_content) for doc, _ in raw_results] scores = reranker.predict(pairs) # 3. 按重排分数取前5作为输出 ranked = sorted( zip(raw_results, scores), key=lambda x: x[1], reverse=True )[:5] return [ { "content": doc.page_content, "score": float(score), "source": doc.metadata.get("source", ""), "page": doc.metadata.get("chunk_index", 0), } for (doc, _), score in ranked ]

这个接口已经具备可以被 Agent 调用的雏形:输入一个字符串 query,输出一段带来源和分数的结构体。注意返回结果里必须带 source,因为 Agent 需要把答案和证据挂在一起,否则用户问“你怎么知道的”,系统变成无源之水,这在很多业务场景是不可接受的。

召回参数 top_k=20 不是随手写的。我在多个场景跑过,top10 和 top20 的差距在复杂问答里尤其明显,top20 给重排器提供了更充分的候选池。top30 以上收益递减,反而增加重排调用耗时。这个值你可以用自己的一套测试集去验证,不同领域可能有差异。

3.3 把 RAG 封装成 Agent 的 Tool

管道就绪,下一步要让 Agent 用起来。最自然的方式是把检索封装成一个 Tool,让模型在 Need 时调用,而不是每次都硬插上下文。下面是封装 Tool 的示例,以 LangChain 的 Tool 接口为例:

# agent_tool.py - 把检索封装为 Agent 工具 from langchain.tools import tool from retriever import search @tool def knowledge_search(query: str, category: str = "") -> str: """ 从公司内部知识库中检索信息。 参数说明: - query: 用户想查询的问题关键词,建议使用原始问题直接传入。 - category: 可选的类别过滤,如"产品手册"、"技术规范"、"客服话术"。 返回: 与查询相关的知识片段列表,包含内容、来源和匹配分数。 """ results = search(query, top_k=20) # 如果有类别要求,在返回前做一次过滤 if category: results = [r for r in results if r.get("source", "").startswith(category)] if not results: return "未检索到相关内容,请尝试换一种表达方式。" # 拼接成模型容易理解的文本结构 output = [] for idx, item in enumerate(results): output.append( f"[{idx}] (来源: {item['source']}, 相关度: {item['score']:.3f})n" f"{item['content']}" ) return "n".join(output)

这里有一个容易踩的坑:Tool 描述一定要写清楚参数含义,因为模型是在“读描述”来决定怎么传参的。如果你的 query 参数描述写得模糊,模型可能自作主张提取关键词再传,结果检索效果反而不如直接传原句好。实测下来,让模型直接传入原始用户问题,检索效果通常最好,因为 RAG 管道的向量检索对自然语言表达比“精简关键词”更友好。

Agent 拿到检索结果后,下一步是判断这个结果够不够回答用户。这块有两种处理方式:一是让模型直接把检索片段作为事实依据组织答案,这是最基本的 Retrieval-augmented Generation;二是让模型再走一步推理,看要不要二次检索补充细节,这就进入 agentic rag 的范畴了。第五部分会展开。

4. 常见问题与排查实录

4.1 检索命中率不理想,问题可能出在哪

命中率是 RAG 项目最让人头疼的指标。我整理了一份排查顺序,按下面顺序查,基本能覆盖绝大多数问题:

  • 第一看 query 和文档的匹配度:如果用户的提问方式和文档表达方式差异太大,比如文档写“报销单审批流程”,用户问“我填完单子多久能通过”,向量检索很可能召回不够直接。这类问题需要靠改写查询词或加同义扩展解决。
  • 第二看分块是否切碎了关键内容:有时候答案其实是完整的,只是被切到了不同块里,每个块都只讲了一半。解决办法是查一次召回结果里相邻块,如果发现答案确实分散,说明分块策略要调整。
  • 第三看 embedding 模型对领域词的支持:用一批术语 query 单独测,如果涉及专有名词的命中率明显偏低,就要考虑换更强的领域 embedding 模型或加词典。
  • 第四看向量库的归一化问题:不同向量库对相似度得分的处理方式不同,有的返回的是内积,有的是余弦值,如果比较得分时混用不同度量,容易误判。

实际项目中,我见过 40% 以上命中率问题出在分块上,不是 embedding。所以排查时别急着换模型,先拿命中的片段人工看一眼,往往就明白问题了。

4.2 元数据过滤:被忽略的精度利器

很多 RAG 实现把检索做成纯粹的“全局语义搜索”,结果一搜全库,最相近的内容全出来了。但真实业务里,知识库往往有极强的结构,比如“某产品线的手册”和“另一产品线的手册”用词非常接近,单靠向量相似度很难区分。

解决办法是为每个 chunk 打上丰富的元数据,并在检索时强制过滤。常见的元数据字段有:来源文件名、文档版本号、发布日期、所属部门、产品线、文档类型。有了这些字段,Agent 查询时就可以带上过滤条件,比如“只查 V2 版本的接口说明”,准确率会大幅提升。

我自己做项目的经验是:每次写索引时都把元数据当作一等公民设计,宁可冗余也别偷懒。因为后续所有检索优化都依赖这些标签,想补的时候往往得重建全量索引,成本非常高。

4.3 成本与延迟的平衡

RAG 管道的延迟大头通常不在向量检索,而在重排模型调用和 LLM 生成。向量检索一般能做到十几毫秒到几十毫秒,重排需要几百毫秒,如果还有大模型回答案,整体延迟就看生成长度了。

针对这个,我有几个实操心得:

  • 重排虽然增加延迟,但不要轻易省。如果实在要省,可以把重排模型换成更小的变体,或者对较短的候选集做重排,比如召回 top10 再重排,相比 top20 能省近一半时间。
  • 缓存可以大幅压低重复查询的延迟。同一种问法短时间内频繁出现时,直接在缓存层返回结果。企业场景里用户体验提升非常明显。
  • 对实时性要求极高的 Agent 动作,比如需要秒级响应去调工具,可以考虑把检索结果预先缓存到 Agent 的上下文里,而不是实时查库。
  • 控制输入到 LLM 的片段长度。每段返回 200 到 300 token 足够,太长反而降低答案精准度。

另外,Embedding 模型如果用的是在线 API,延迟和费用都不好控制。批量写入索引的时候建议用本地或内网部署的模型,不然后台建索引时带宽被占住,线上检索也会一起卡壳。

5. 从 RAG 到 Agentic RAG:下一站怎么走

5.1 多轮检索和路由:Agent 什么时候该查,什么时候不该查

聊完基础管道,最后说说走向生产环境的 Agent 化改造。

传统的单轮 RAG 是“问一下查一次”,但真实使用中用户经常说一句话包含多个问题,或者问题本身依赖上下文。比如用户先问“报销流程是什么”,接着又问“要多久”。第二个问题在纯 RAG 里很容易查偏。Agent 化的 RAG 需要把多轮对话的信息汇总成完整的检索单元,再决定是否需要检索,这个环节业内叫 query rewriting 或 query routing。

我推荐的做法是让 Agent 先做一次意图分类,判断当前问题是否需要检索:

  • 两个原因导致 Agent 倾向去查:要么内部知识里明确有,要么用户提问的实体词和知识库里的术语高度匹配;
  • 反之,闲聊、通用常识、或者前一轮已经检索过的内容,可以直接靠模型记忆回答,不触发检索。

这个判断本身就是一个函数调用问题。可以让模型决定调不调 knowledge_search 工具,这在 LangChain 的 Agent 框架里天然支持。之前封装 Tool 那段代码里,Agent 会依据问题判断是否调用,这已经算是最简版的 Agentic RAG 雏形了。

5.2 结构化知识图谱与 RAG 结合

传统 RAG 处理非结构化文本很顺手,但碰到强关系、多跳逻辑的时候就吃力了。比如“A 产品的 B 缺陷影响了哪些同样用了 B 模块的其他产品”,这类问题纯向量检索很难答好。社区这两年在热的方向是 ontology rag、graphrag:把实体和关系抽出来构造成知识图谱,Agent 可以通过图谱的路径查多跳关系,也可以把图谱检索和向量检索混合使用。

实现上通常分两步:先做离线图谱抽取,用到 LLM 或专门的 NER 模型把文档里的实体、属性、关系抽出来,存入图数据库;在线阶段,根据用户问题决定走“向量通道”“图谱通道”还是“混合通道”。这块复杂度不低,但对企业级 Agent 的价值很大,尤其是产品检索、故障排查这类高精度场景。

不过我的建议是,别急着上图谱。如果业务的核心问题都能通过单跳检索解决,图谱投入产出比很低。等你能明确说出哪类问题是因为“跨文档的知识碎片化”而答不对,再考虑引入图谱,效果会明显很多。

5.3 RAG as a Service:把管道服务化

随着 Agent 产品越来越多,RAG 本身也在被抽象成一种服务。这波趋势在 Spring AI、AgentScope 这类框架里变得尤其明显。原因很简单:知识库的构建和检索,实际上是一种很通用的能力,不该每个 Agent 重复造轮子。

如果你要做成服务,核心要考虑三件事,缺一不可:

  • 多租户数据隔离:不同业务线之间的知识不能混,要在向量库里做 collection 隔离或元数据隔离;
  • 灵活的 Schema 定义:不同知识库有不同的元数据需求,服务应支持动态注册字段;
  • 版本管理与灰度发布:索引和检索算法升级时,不能一把梭,要能快速回滚。

这个方向做扎实了,其实就变成一个内部的知识中台,后端所有业务 Agent 都能复用,价值会远远超过一个个独立的知识库。

写到最后,这套管道从零到能跑通,我反复验证下来的核心感受是:RAG 的瓶颈从来不在模型多强,而在管道设计得是否贴近真实业务。先吃透文档结构和查询模式,再选工具和调参,效果往往比盲目堆新技术要好得多。希望这篇能给你搭出自己的知识获取管道带来一些可落地的参考。

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

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

立即咨询