☰
Agent知识获取管道:RAG检索增强生成实战解析
2026/11/4 14:04:26 网站建设 项目流程

你在本地跑过 Agent 之后,大概率会撞上同一堵墙:模型什么都知道一点,但一问到你们团队的内部文档、你个人的笔记、最新的业务数据,它就哑火了。这不是参数不够,而是它的知识边界在训练那天就焊死了。所以这个系列走到第四篇,我们把注意力从"Agent 怎么思考"挪到"Agent 怎么拿知识"——也就是 RAG(Retrieval-Augmented Generation)这条知识获取管道。

RAG 不是新概念,但它在一线 Agent 项目里被反复提起,不是因为它能当搜索引擎,而是因为它能让模型在回答问题之前,先从一个可控的管道里取到证据。这篇我按自己的落地经验拆一下 RAG 的基础:它解决什么问题、一条最小管道怎么搭、Agent 和 RAG 怎么真正耦合,以及跑通之后那些文档里不会写的坑。

1. Agent 为什么需要一条"知识获取管道":先看上下文窗口的物理限制

很多朋友第一次做 AI Agent,第一反应是"把知识全塞进 system prompt 不就行了"。我刚开始也这么干过,把一个几十页的产品手册写成摘要丢进去,结果模型一回答问题就开始胡编。问题不在模型态度,而在知识取用的方式。

1.1 大模型的知识天生有"保鲜期"

模型参数的训练是有截止时间的,你用它做知识问答时,它只能回忆训练语料里见过的东西。业务的内部规范、你昨天刚写的技术方案、某产品的实时价格,这些根本不在它的记忆范围里。你问我为什么要专门给 Agent 修一条知识管道,就是因为模型自己没法感知时间、没法主动查询,它只有一条路——看你给它的输入里有没有。

有人想用"微调"来解决,这是另一个方向,但微调解决的是模型的行为习惯和回答风格,不是高频更新的业务知识。你没必要为了让模型知道某个新接口的文档,去重新训练一次模型。知识获取管道的第一条原则,就是让知识以"运行时输入"的方式进入模型,而不是以"训练时记忆"的方式存在参数里。

1.2 上下文窗口不是无限书柜

就算你把所有资料都拼进 prompt,上下文窗口也扛不住。你可以算一笔账:一份 5000 字的文档,大概 7000 个 token,一个 128K 窗口的模型,塞 10 份就吃掉一大半。而真实业务场景里的文档池,往往是几十万甚至上百万字的规模,不可能整包塞进去。

更麻烦的是注意力稀释。我做过对比实验:把相关段落杂在一堆无关背景里,模型回答准确率会明显下降。你以为它"看得见",但它在长上下文里定位关键信息的能力没有想象中那么强。所以 Agent 需要的不是更大的书柜,而是一个"先找书、再翻开确定页给你看"的过程。这就是 RAG 管道存在的意义。

1.3 RAG 解决的不是"知识不够",而是"信息取用"

RAG 的全称是 Retrieval-Augmented Generation,检索增强生成。它的核心思路拆成三句话就是:先把文档切成小块并向量化,存入检索库;用户提问时先从库里检索相关片段;把检到的片段塞进 prompt,让模型基于这些材料回答。

这套逻辑跟人查资料很像,模型就像那个坐在书桌前回答你的人,RAG 就是一个在你提问后主动帮你翻书、把相关段落摊开放在桌上的助手。它不改变模型本身,只改变模型"看到了什么"。这决定了 RAG 的上限:如果检索环节找错了材料,后续生成环节再强也救不回来。所以后面我讲实操时,会反复强调一个判断——你的瓶颈在召回,不在生成。

2. RAG 三段式的工程本质:切分、检索、拼接

RAG 看起来只是"查一下再回答",落到工程上其实是三个独立环节的拼接。很多人一上来就全链路调优,效果差的时候根本不知道是哪一段出了问题。我的建议是先把三段拆开,各自搞清楚评价指标。

2.1 索引侧:切分策略与向量化

建索引的第一步是切分(splitting)。你要把一个长文档切成小段,每一段是一个待检索单元。切分粒度直接决定召回质量。我用过一个粗暴的经验值:普通技术文档,按 300 到 500 个 token 切一段,重叠区设 50 到 80 个 token,这个量级在多数场景下表现稳定。但这不是死的,代码文件按函数切、合同按条款切、对话记录按轮次切,都比固定长度切更贴合语义边界。

切分之后是向量化(embedding)。这一步通过 embedding 模型把文本转成高维向量,让语义相近的文本在向量空间里距离更近。选 embedding 模型时我比较看重两点:一是中文效果,二是向量维度。常见的开源中文 embedding 模型在 768 到 1024 维之间,维度太高会占内存,太低又可能损失语义区分度。这个环节有个常见误解——不是模型越贵越好,而是嵌入模型要跟你的文档领域匹配。医疗、法律、代码、客服语料的最佳选择都不一样。

2.2 检索侧:向量召回与混合检索

检索的核心动作是"拿 user query 的向量去库里做相似度搜索",常用的算法有余弦相似度、点积、欧氏距离。向量检索的优势是能处理同义改写这类语义匹配问题,比如用户问"怎么退款",文档里写的是"退费流程",字面不同但语义接近,向量检索能把它们对上。

但纯粹靠向量检索有一个盲区——精确词匹配。合同编号、订单号、专业术语、版本号这类字符串,向量相似度反而容易模糊处理。所以完整一点的检索器通常做混合检索:一半向量召回,一半关键词召回(比如 BM25),然后再用重排序模型(reranker)把两部分结果合并排序。你可以把混合检索理解成"用语义理解大方向,用关键词抓精确锚点",两者互补。我在一个产品检索项目里做过对比,纯向量召回 hit rate 大约 72%,加上 BM25 和 reranker 之后能提到 88% 左右。

2.3 生成侧:拼接模板与引用溯源

检索完成之后,系统会把命中的片段按顺序拼进 prompt,再让模型生成最终回答。这里最容易翻车的是语境完整性:如果你只截取命中的那一小段,模型可能看不懂上下文,所以我通常会把命中片段前后各扩展一段原文,再一起拼进去。

拼接模板同样有讲究。我的写法是明确告诉模型"以下是从知识库检索到的参考片段,请基于这些内容回答,如果不足以回答问题,请直接说明不知道,不要编造"。这句话看着简单,但能明显减少幻觉。另一个重要设计是引用溯源:每段检索片段保留文档 ID 和页码信息,让模型在回答里标注来源。这一条既能方便用户核查,又能倒逼工程侧去检查"这个回答到底有没有参考依据"。

3. 从 0 到 1 搭一条最小 RAG 管道:写代码级别的实操

说再多原理,不如跑通一遍。我理解的基础标准是:能用本地文档建库、能检索、能基于检索结果回答。这一步走通了,后面加 Agent 工具、加记忆、加重排,都是在同一套骨架上面加东西。下面这条管道是通用的最小实现,我用伪代码来写,重点不在某个库的具体 API,而在链路本身。

3.1 环境与模型选型

二选一的常见方案是 LangChain 和 LlamaIndex,两者都能把加载、切分、向量化、检索串起来。我的建议很简单:如果你后面主要做 Agent,选 LangChain,因为它的工具调用生态更完整;如果你只是想把一堆文档变成问答服务,选 LlamaIndex 会更顺手。也可以用更轻的方式直接调 embedding API 和向量数据库,核心流程完全一样。

三个组件必须提前备好:一个 embedding 模型、一个向量数据库、一个生成模型。我测试时常用的组合是开源的 embedding 模型 + Chroma(本地轻量向量库)+ 兼容 OpenAI 接口的 LLM。Chroma 适合个人项目和原型验证,它足够简单,而且能本地跑,不需要额外的服务端。如果要上生产,我后面会换用更重的向量库,但测试阶段的逻辑是完全一样的。

3.2 文档切分与入库

先把文档读进来,切成小块,再批量向量化入库。核心代码逻辑如下:

# 伪代码:最小 RAG 管道 - 建索引部分 from document_loader import load_pdf, load_markdown, load_txt from text_splitter import RecursiveTextSplitter from embedding_model import get_embedding_function from vector_store import VectorStore # 1. 加载文档 docs = [] for path in ["manual.md", "faq.pdf", "api_notes.txt"]: docs.extend(load_pdf(path) if path.endswith(".pdf") else load_markdown(path) if path.endswith(".md") else load_txt(path)) # 2. 切分:每块 400 token,重叠 50 token splitter = RecursiveTextSplitter(chunk_size=400, chunk_overlap=50) chunks = splitter.split_documents(docs) print(f"文档切分成 {len(chunks)} 个片段") # 3. 向量化并写入向量库 embeddings = get_embedding_function() vector_store = VectorStore(collection_name="knowledge_base") ids = [f"chunk-{i}" for i in range(len(chunks))] vector_store.add_documents(ids=ids, documents=chunks, embeddings=embeddings)

这里我想特别说两个容易忽视的地方。第一,切分器要按文档类型选,RecursiveTextSplitter 是通用兜底,但对 Markdown 文档最好保留标题层级,对代码文件则要按函数切,否则语义会被拦腰切断。第二,入库时一定要保存元数据,至少包括来源文件、页码、标题路径。后面做引用溯源和过滤检索都要靠这些字段,等出了问题再回去补元数据,会非常痛苦。

3.3 检索测试与链路联调

库建好之后,先不要急着接 LLM,单独测检索这一步。原因是:如果检索结果本身就不相关,你接下来怎么调 prompt 都没用。我会写一段独立的检索测试脚本,输入几个真实用户问题,打印出命中的片段和相似度分数。你一眼就能看出来命中的内容是不是对的。

# 伪代码:单独测试检索环节 query = "客户在哪里可以修改订阅计划?" hits = vector_store.similarity_search(query, k=5) for i, hit in enumerate(hits): print(f"[Top {i+1}] score={hit.score:.4f}") print(hit.text[:120]) print("---")

检索没问题之后再把生成环节接上,把命中的片段拼进 prompt 调 LLM。我第一次搭的时候,检索结果明明是对的,模型回答还是偏了——后来发现是 prompt 里没有明确"只依据参考内容回答",模型把常识和检索内容混在一起了。把指令改成"仅根据参考片段回答,片段之外的不要推测"之后,输出质量立刻提升。

到这里,一条最小管道就通了。整个过程快的话半小时能跑通。接下来真正花时间的不是管道本身,而是对切分粒度、embedding 选择、检索召回的反复调优。

4. Agent 与 RAG 的三种耦合方式:从"工具调用"到"Agentic RAG"

管道通了,它只是一个被动的工具:用户提问,它检索,它回答。但放在 AI Agent 体系里,RAG 不能只是孤零零的问答接口。Agent 有自己的任务目标,有规划,有多步操作,RAG 要嵌进这个过程才算完整。

4.1 方式一:把 RAG 封装成 Agent 的一个检索工具

最简单、也是最常见的做法,是把 RAG 检索器封装成一个函数(或者说工具),注册进 Agent 的工具列表里。Agent 在思考过程中判断"这个问题需要查知识库",就调用这个工具,把检索结果拿回来作为下一步决策的上下文。

这一层的好处是解耦。你的 Agent 可以做别的任务,需要知识时随时"调"一下知识库。我在做客服 Agent 时就是这么接的:主 Agent 负责判断意图,如果想了解产品规则,就调 RAG 工具,拿到片段后再组织回答。这种方式很稳妥,适合大多数业务场景。工具的描述信息很重要,比如"在公司产品知识库中检索售后政策相关内容,输入为问题原文",这能帮助 Agent 决定什么时候该用、什么时候不该用。

4.2 方式二:让 Agent 自己决定检索策略(Agentic RAG)

耦合再深一层,是让 Agent 不只会"查一次",还会自己决定怎么查。比如它先检索到一个大概念,发现信息不够,再决定拆成多个子问题继续检索;再比如它发现检索结果太旧,就换关键词重新查。这被称为 Agentic RAG——检索的步骤、次数、重查策略都由 Agent 动态规划,而不是一次性固定检索。

我做过一个对比测试,同样的知识库,普通 RAG 在一道多条件组合问题上答得比较差,因为单个 query 很难覆盖全部条件。Agentic RAG 会把问题拆成"价格区间""适用平台""限制条件"三个子查询,分别检索再汇总,准确率高很多。代价是:调用次数增加,延迟和成本同步上升。所以我现在的实践是,简单问题走普通 RAG,复杂问题才放 Agent 自主规划检索。判断标准可以是一个简单的意图分类器,也可以让 Agent 自己判断。

4.3 方式三:知识管道与规划、执行的协同

如果你的 Agent 已经具备"规划—执行—观察"的循环能力,RAG 就可以更进一步变成整个循环里的知识底座。Agent 规划出的每一步都可以引用知识库里的信息来校验;执行完一个操作后,也可以把观察结果和新知识写回知识库,形成闭环。

这种方式最适合知识密集型的运维或分析场景。比如排查一个问题时,Agent 先从知识库里找到相关的排障手册,执行检查命令,把输出结果和手册对照,判断下一步动作。RAG 在这里不再是"回答用户问题的附件",而是 Agent 做决策的证据源。代价是链路复杂度上来很多,你得处理知识写入、版本覆盖、上下文累积等一连串问题。我的建议是:前两种方式先跑稳,再考虑第三种。

5. 跑通之后必须面对的五个实际问题

最小管道跑通只是起点。问题会在你真正用起来之后浮出来,而且每一个都跟"效果不好"直接挂钩。下面五个问题我基本都在项目里踩过,有些是调参能解决的,有些必须改设计。

5.1 召回质量怎么评估:命中率之外还有重排

很多团队只看"最终回答好不好",但 RAG 管道的上游是检索,必须单独评估。基础指标是命中率(hit rate):对测试集里的每个问题,检查 top-k 结果里是否包含人工标注的正确片段。更深一点要评估平均倒数排名(MRR),看正确答案排在第几位。

如果你发现命中率不达标,先别急着换 embedding 模型,大概率是切分粒度问题。切块太大,语义混杂,检索模糊;切块太小,上下文不足,模型看不懂。我调过一次文档切分粒度,从 512 降到 256,命中率反而涨了 6 个百分点。另外,在检索环节之后加一个重排器,是性价比很高的做法:top-20 的粗召回结果经 reranker 精排后,取 top-5 送给 LLM,能显著减少"前几个结果看着相关但其实不是最优"的问题。

5.2 切分粒度不是越小越好

新手最容易走极端:切得碎,以为这样检索精确。结果片段太短,语义不完整,模型拿到手根本不知道在讲什么。我的经验是,切分粒度要配合下游指令一起看。如果你的 prompt 里明确告诉模型"参考每一段时,注意它所属的章节标题",那么切分时保留标题元数据会比单纯缩小 chunk 更有效。

另一个细节是重叠区。切分时让相邻片段保留一小段重叠文本,可以避免一个完整句子或关键术语在切缝处被截断。重叠不是越大越好,我控制在 10% 到 20% 之间。重叠太大会引入大量重复片段,导致检索结果里前后两条几乎一样的内容,白白浪费上下文窗口。

5.3 多轮对话中的上下文污染

Agent 几乎都是多轮对话场景。你第一轮问了"这个功能怎么配置",第二轮追问"那权限呢",此时如果直接把上一轮检索结果继续拼进这一轮的 prompt,会产生严重的上下文污染。

我踩过最明显的坑是:第二轮模型把上一轮的配置步骤带进了权限问题的回答里。解决思路是:每一轮的检索上下文只与当轮 query 相关,历史对话只保留用户意图摘要,不要保留历史检索片段。更精细的做法是让 Agent 先判断"这个问题是不是对上一轮的延续",如果是,就把上一轮关键结论压缩成状态摘要,再和新的检索结果一起输入。这一条对体验影响非常大。

5.4 更新与删除:知识库的"版本管理"

RAG 最难维护的部分之一不是建库,而是文档更新。今天改了产品手册,如果只把新文档重新入库而没有删除旧的碎片,检索结果里会出现新旧版本打架的情况。尤其是技术方案类的文档,版本冲突会让回答自相矛盾。

我在项目里的做法是把整套知识库按"数据源版本"管理。每次同步时,先删掉这个数据源对应的所有旧 chunk,再写入新文档的 chunk。同时给每个 chunk 记录更新时间,检索拼接时可以做一层过滤,比如"优先取最近 3 个月的数据"。如果你用的是支持按元数据过滤的向量数据库,这类版本控制实现起来不复杂,关键是别省掉这一步。

5.5 成本与延迟:每一步检索都要算账

RAG 管道的额外成本来自三块:embedding 调用、向量库存储、重排序。其中重排序的延迟最容易被低估,一次 rerank 可能增加 200 到 500 毫秒,对实时问答场景是不可接受的开销。我的实践是:简单问题跳过 reranker,只做向量召回,快;复杂问题才启用完整链路,准。

成本上我还有一个不算技巧的技巧:把 embedding 结果做本地缓存。同一个 query 在一段时间内重复出现的情况非常多,缓存命中能直接省掉 embedding 调用。另外,向量库的存储量要有意识控制,按"当前业务真正需要被检索的内容"来建库,而不是把所有历史文件一股脑全塞进去。知识管道不是仓库,它是"取用频率高的资料抽屉"。

我在实际项目里最深的体会是:RAG 的参数和策略没有一个通用最优解,它高度依赖你的文档形态、用户提问方式和延迟要求。所以如果你做完一遍之后发现效果不够好,不要沮丧,这很正常。比较好的处理方式是把你自己的测试集固定下来,每次改动只动一个变量,慢慢逼近适合你场景的配置。知识获取管道本身不是一个"做完就结束"的功能,它是一个需要持续维护的工程环节。希望这篇能帮你把 RAG 的基础链路跑通,也让你在看到 Agentic RAG 这类进阶方案时,心里知道那些能力是在什么地基上长出来的。

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

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

立即咨询