拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent外挂知识管道:RAG基础、分块与检索评估实战

Agent外挂知识管道:RAG基础、分块与检索评估实战

做Agent的都知道一个尴尬场景:模型推理能力再强,你问它一个私有文档里的细节,或者一个训练截止日期之后的新消息,它就开始用那种特别自信的语气编答案。你换更大的模型也解决不了,因为问题根本不在推理能力,而在“它没有那条知识管道”。所以从这篇开始,我们把视角从模型内部挪到模型外部,聊聊Agent怎么把外部知识接进来。RAG(Retrieval-Augmented Generation,检索增强生成)正是这条知识获取管道的地基。

这篇是“走进 AI Agent”系列的第四篇,内容定位是RAG基础。我会先讲RAG在Agent架构里到底承担什么角色、边界在哪,再手把手带你跑通一条最小可用的RAG管线,然后是检索质量的量化方式(Hit Rate、召回率这些到底怎么看),最后聊Agentic RAG和图谱增强这两个进阶方向。整个过程中会穿插我实际做项目时踩过的坑,尤其是中文场景下的那些坑。

这系列前面的文章如果有小伙伴没看,不影响这篇的阅读,核心概念我会都解释一遍。但如果已经理解Agent的规划、工具调用这些基础,这篇读起来会特别顺。

1. Agent为什么要外挂知识:RAG的定位与边界

1.1 语言模型的“记忆天花板”与Agent的知识来源

先抛一个反直觉的结论:大模型不是你想象中的“百科全书”,它更像是“把海量信息压进大脑参数的推理引擎”。的训练目标只是“预测下一个词”,它的知识是隐式地锁在几百亿参数里的,不是像数据库那样一条条能查的记录。

这就带来三个固有毛病。第一,参数化知识有截止时间,训练数据之后发生的事它一概不知道。第二,参数化记忆是“模糊记忆”,它记得一个概念大概是什么样,但记不住精确的数值、编号、专有名词的准确拼写,哪怕见过多次也会混淆。第三,幻觉问题:当它被问到一个知识盲区时,大脑机制会倾向于“编一个听起来合理的答案”,而不是老老实实说我不知道。

所以Agent想真正落地到业务里,面对的一定是“最新数据 + 私有文档 + 领域知识”这种组合,而这些恰恰是模型参数里缺失的部分。Agent的知识来源实际上有四条路:

  • 预训练权重:模型出生自带的通用知识,覆盖广但不精确、不实时。
  • 微调:把特定知识烙进权重,代价高、更新慢,适合风格迁移和高频行为对齐,不太适合塞事实类知识。
  • 工具调用/函数调用:适合“精确计算、实时查询、操作外部系统”,比如查天气、算订单金额、调员工API。
  • RAG检索增强:适合“从大量非结构化文本中找到相关信息并让模型基于这些信息作答”。

我自己的经验是,这四条路不是互斥的,而是一个Agent系统的不同闸门。RAG的核心价值,是把“模型不知道的事实性问题”从参数记忆里剥离出来,交给外部索引。模型的任务从“回忆你知道的”变成了“阅读我给你的材料然后推理”这其实是一个工作量上的巨大转移,也是目前控制幻觉最有效的手段之一。

1.2 RAG管道的基本结构:离线索引与在线检索

RAG这个名字听起来学术,但拆开看就是一个很朴素的思想:回答问题之前,先去资料库里翻找相关资料,把找出来的资料贴在上下文里,再让模型作答。

整个结构分成两半。一半是离线的知识索引管线:把文档载入系统,做解析,切成合适大小的块(chunk),用embedding模型把每个块编码成向量,然后把向量连同原文、元数据一起存进向量库。这个过程只需要在文档变更时执行。另一半是在线的检索问答管线:收到用户问题后,把问题编码成同样的向量空间里的查询向量,去向量库里做相似度检索,拿回Top-K个相关片段,经过重排序,拼装成上下文送给大模型生成答案。

这个“先索引、后检索、再生成”的流程,就是你在各种博客、框架文档里看到的经典RAG架构。虽然现在衍生出了很多变体,比如Self-RAG、Agentic RAG、GraphRAG,但核心的骨架没有变过。你可以把索引管线理解成“给所有知识文件建立了一个带语义坐标的地图”,把检索管线理解成“用地图找到相关坐标,把对应段落取回来交给读者”。

我见过不少新手一上来就急着接LangChain的RetrievalQA,把文档灌进去就开始问问题,结果效果一塌糊涂。原因几乎都是把RAG当成一个“黑盒工具”而不是“一条可以逐环节调优的管道”。一旦你心里有这条管道的完整结构,调试起来就会顺畅很多。

1.3 RAG的适用边界:什么场景别用RAG

聊完RAG能做什么,也说说它不擅长什么,这个部分其实能帮你省很多时间。

RAG擅长的是“非结构化文本的语义召回”,典型场景包括:企业知识库问答、产品文档助手、客服知识检索、合同审查辅助、科研文献综览。它本质上假设的是“答案可以被一段连续文本覆盖”,所以问题越适合在文本里找答案,效果越好。

但有两种场景我基本不推荐硬上RAG。第一种是强结构化的精确查询,比如“上个月华东区销售额多少”,这种用自然语言转SQL、或者直接Function Call查数据库,结果又准又稳定。你用RAG去召回“销售数据”相关的文档,大概率召回一堆总结性的报表描述,里面数字还不一定对。第二种是高频、时效性极强的操作类查询,比如“帮我提交工单”“订这周日的会议室”,这类应该走工具调用,让Agent直接操作业务系统,而不是在文档里翻操作指南然后假装执行它。

还有一个我踩过的坑是“给RAG喂知识库时全量灌入、鱼龙混杂”。RAG检索器很难自动判断“哪份文档权威”,如果你把销售周期PPT和财务SOP放在同一个索引里,检索时它们会因为“都是公司文档”而被同时召回,回答就会串味。所以在索引设计阶段就应该用元数据区分文档类型和权限范围,甚至为不同业务域建不同的索引。

2. 一条最小可用的RAG管线:分块、向量化与召回实战

2.1 文档加载与解析:边界问题最多的起点

很多人觉得RAG的第一个难点是向量检索,但我实际做下来,真正最耗时间的反而是最不起眼的文档解析。你永远不知道用户会给你什么样的PDF:有的是文字层正常的“好文档”,有的扫描件,有的是PPT导出的读版式PDF,还有带双栏排版的论文。

我现在的处理习惯是这样的:优先用pymupdf(fitz)提取PDF文本层,顺手把每页的标题层级也带出来;对于扫描件,接OCR服务,但要注意OCR之后的文本是“扁平”的,很容易丢失标题层级。Word文档用python-docx按段落结构提取,尽量保留Heading层级,这对后面的结构化分块特别重要。HTML和Markdown则简单一点,转成纯文本后利用标签切分。

文档解析的目标不是“把所有字变成字符串”,而是“尽量保留语义结构”。我见过一个最典型的失败案例:某PDF合同里的“违约责任”条款恰好被放在一个表格里,用普通文本抽取直接丢了表格内容,导致RAG对“违约责任”类问题的召回结果几乎为空。这种问题你在检索层怎么调参都救不回来,只能在解析层解决。所以我给团队定的规矩是:每接入一类新文档格式,先做10个代表性样本的“解析到文本”人工检查,看格式损不损失,再看切块后的语义完整性,确认没问题再全量灌入。

2.2 分块策略:检索效果的大半

分块(chunking)是RAG管道里被低估最多、但影响最直接的一环。为什么要分块?因为向量检索是一个“用稠密向量表示语义”的过程,而embedding模型对输入长度极其敏感。你如果拿一整篇30页的合同去编码,向量会被稀释成“这是一份合同”,什么都检索不出来;如果切成小块,每块就能保留一个稳定主题,检索时更容易命中具体信息。

分块策略我按优先级排,会是这样一个顺序:

  • 按文档结构分块:利用Markdown/PDF的标题层级,把章节当作天然分块边界。这是最优解,因为语义边界是文本自己的。
  • 固定大小窗口 + 重叠:没有结构信息时,按token或字符数固定切块,并设置10%~15%的重叠区域,避免语义断层。
  • 父子分块:小块负责精确召回,大块负责给模型足够上下文。这个策略改天单独聊,但基础RAG里已经可以让你们先了解。

我目前的默认配置是:父块=按标题层级得到的章节块,子块=512字符左右、带64字符重叠、按段落边界切分。索引里同时存父子两层结构,检索时用子块匹配,返回结果时用父块组装上下文。这样做的好处是:精确匹配到具体段落,但喂给模型的上下文带上章节全局信息,回答质量会明显更自然。

这里给一段父子分块思路的示意代码:

import hashlib def build_parent_child_chunks(text: str, heading_pattern, max_child_chars=512, overlap=64): """根据标题层级切父块,再按段落与窗口切子块。简化演示版。""" parent_chunks = split_by_headings(text, heading_pattern) # 按结构得到父块 chunks_with_meta = [] for parent in parent_chunks: doc_id = hashlib.md5(parent.encode("utf-8")).hexdigest() sub_chunks = [] start = 0 while start < len(parent): end = min(start + max_child_chars, len(parent)) sub = parent[start:end] sub_chunks.append({"text": sub, "parent_id": doc_id}) start = end - overlap if end < len(parent) else len(parent) chunks_with_meta.append({"doc_id": doc_id, "text": parent, "children": sub_chunks}) return chunks_with_meta

这里我只是演示逻辑,真正工程里还要处理很多细节,比如如何避免把一句话从中间截断。我的经验是:能按结构就按结构,没有结构时至少要在段落边界处切块,绝不硬按字符数从左往右切。因为切分边界处丢失的,往往是关系最紧密的前后文。

2.3 向量化选型:中文场景怎么做Embedding

分块之后要做embedding,这是把文本变成向量的一步。核心问题只有一个:选什么模型。

对中文场景,我实际测试过多个模型的检索效果,结论是:BGE系列的中文召回能力很扎实,尤其是BGE-M3这种多语言模型,它对中文、英文、代码混合的文档支持得很好;阿里的text-embedding系列在中文文档类的语义匹配上也表现优秀。这些模型基本都支持把文本映射成1024维左右的向量,并且有专门针对文本检索场景的“query指令”(比如BGE的query指令),在编码用户问题时需要加上,这是一个很多新手会漏掉的细节。

选向量模型的几个硬标准,按优先级排:

  • 领域适配性:你的文档是法律合同、医疗报告还是代码文档?拿一小批真实数据做召回测试,比看榜单分数靠谱得多。
  • 文本长度支持:输入上限至少512个token,最好能到1024+,否则分块策略会受限制。
  • 维度与存储:向量维度越高,存储和计算成本越大。如果不差钱,无脑选上游;如果本地部署,考虑轻量模型,比如BGE-small这种。

一个很常见的错误是:只用API模型做embedding,然后所有文本不管什么场景都直接用默认配置。实际上不同文档类型对向量模型的要求差别很大,跑一批真实RAG评估集去对比模型,是唯一可靠的方法。我在项目里通常会准备50~100条“问题+期望命中的段落”的测试集,然后对比2~3个embedding模型的Hit Rate,谁高用谁。

2.4 向量库选择与混合召回

向量库是整个RAG的存储层。选择逻辑很简单,我一般这样判断:

  • 数据量在万级以下、开发验证阶段:直接用FAISS。它只是一个内存索引库,不是真正的服务,但足以让你快速跑通流程。
  • 需要持久化、支持元数据过滤、并发查询:选Qdrant或Milvus。
  • 场景里要求极低延迟、海量数据:Milvus/Zilliz那套更重,但也更能扛。

我第二常用的是Qdrant,因为它部署简单(一个Docker就起来),元数据过滤和Payload存储都很直观。下面是一个用FAISS做检索的示例,方便你理解“向量检索”的本质:

import numpy as np import faiss def build_faiss_index(chunk_vectors: list[np.ndarray], dim: int): """把chunk向量灌入FAISS索引,返回index对象。""" arr = np.vstack(chunk_vectors).astype("float32") index = faiss.IndexFlatIP(dim) # 内积相似度,等价余弦 index.add(arr) return index

需要提醒的是,向量检索只是RAG检索引擎的第一层,也是基线层。长期使用的话,我会建议在向量之路之外再叠加一层混合检索:BM25关键词检索 + 向量语义检索,然后合并打分。原因是向量检索对“同义改写”很友好,但对“精确名词、编号、代码片段”的匹配不如BM25。比如用户问“服务器IP是啥”的精确IP地址,向量检索往往召回“服务器列表”的段落,BM25直接命中那个IP出现的段落。两条路合并召回,再用重排序模型统一打分,是目前工程上最稳的配置。

重排序(Reranking)是召回质量的关键一环。通常用Cross-Encoder的rerank模型(比如bge-reranker系列),把“查询+候选段落”成对输入模型,输出相关性分数,然后重新截取Top-K。这一步能让结果集的准确率上一个明显的台阶。我的建议是:Top-50候选进入rerank,截取Top-5喂给大模型,这个配置在大多数场景下性价比最高。

3. 检索质量怎么量化:Hit Rate、召回率与失败诊断

3.1 指标先行:先定义什么是“好检索”

很多RAG项目调了半天,效果忽好忽坏,原因就是没定义“什么是好”。你应该先有一套离线评估集,再谈调参。

检索评估里最有实用价值的指标是Hit Rate(也叫Recall@k):对每一个评估查询,我们人工标出“应该被召回的那个标准答案段落”,然后看系统召回的Top-K里有没有包含它。如果有,就算一次命中。Hit Rate = 命中查询数 ÷ 总查询数,是衡量“检索器到底有没有把答案捞出来”的第一指标。

举例说明。假设我准备了50个查询,每个查询标了它在知识库里的标准段落。设置K=5,系统运行一轮,50个查询里有42个的标准段落出现在Top-5结果里,那HitRate@5=84%。这意味着还有8个查询是“即使答案在库里也捞不出来”,属于检索器的硬伤,无论后面生成模型多强都救不回。

除了Hit Rate,还有几个指标按需使用。Precision@k衡量Top-K里有多少是有用的;MRR(倒数排名)衡量标准答案在结果里排得够不够靠前;NDCG则考虑位置折扣,适合理解“排序质量”。但作为日常迭代指标,我建议只看两个:HitRate@5监控整体召回能力,Top-1相关性抽样检查排序质量。

3.2 检索失败诊断:不看坏case等于白调

指标只能告诉你“哪里不行”,不能告诉你“为什么不行”。想要提高指标,必须去看那些失败的坏case。我在实际项目里有一套固定的排查流程。

第一步,把失败的查询和实际召回的Top-K片段打出来,人工看一遍。大概率会发现一类常见问题:查询是“服务器如何扩容”,知识库里相关的段落标题叫“资源扩容规范”,词汇完全不同,向量语义匹配失败。这种问题靠切分无法解决,得走查询侧优化——比如加同义词扩展、query改写,或者用混合检索把关键词召回补上。

第二步,对召回结果做相似度分数分布分析。如果所有候选片段的相似度都低于0.3,大概率是分块粒度的问题,块太大,向量被稀释;或者embedding模型与领域不匹配。如果分数普遍很高但命中内容不对,那要怀疑分块切到了语义断层处,两个话题被强行包进一个块。

第三步,回到索引侧检查。我遇到过一个很隐蔽的问题:某文档的标题和正文被解析成了两个独立块,而正文块丢掉了标题里的“附件四”,导致检索时全部正文块都匹配上了“附件四”相关的查询,但标题块排名很低,答案自然不对。这种问题只能在解析层连带修复,比如保留段落内的标题字段,让它随正文块一起进入索引。

这里特别提一个困扰很多人的问题:为什么答案明明在文档里,模型却总说“找不到”?十次里八次是切分问题——标准答案跨了两个块的边界,检索器把两块都召回了,但你的上下文组装可能默认只取命中的第一个块,第二个块没进去,于是上下文缺少关键后半句。解决方法是父子分块或取Top-K后按原文档顺序拼接段落,别只取单个碎片。

4. 从单路到多路:Agentic RAG与图谱增强路径

4.1 Agentic RAG:Agent不再只是搬运上下文

基础RAG可以概括为“一个查询进去,一批文档出来,拼上下文,生成答案”。这个模式的问题在于,查询本身往往是复杂的、含糊的、多跳的。比如“帮我总结一下A项目的合同里关于违约责任和甲方付款义务的区别”,这种查询本质上需要拆解成多个子问题、检索多个文档、跨越多个文档做推理。

于是就有了Agentic RAG——把RAG检索器当成Agent手里的一个可调用工具,由Agent自由决定“查几次、查什么、怎么组合结果”。相比基础RAG的固定管道,Agentic RAG里多了一个“思考-检索-再思考”的控制循环:

  • Query策略:Agent可以改写原查询、拆解成子查询、并行召回。
  • 检索决策:Agent可以决定是“直接向量检索”“走混合检索”“先查数据库拿元数据再过滤文档”。
  • 多项证据的权衡:当多份文档说法不一致时,Agent能做交叉确认,甚至调用别的工具来验证。
  • 自校验:这里的思路很像Self-RAG,生成前先判断“我有足够材料吗”,推理后再判断“我的回答是否忠于材料”,拿不准就再查一轮。

你可以想象一个做法律调研的Agent:收到问题后先自己拆成“合同条款检索”“判例检索”“法条检索”几个分支,每个分支都走各自的RAG索引,最后再综合成一份含引用的报告。这就是典型的Agentic RAG。

在做项目时,我的经验是:不要一上来就做Agentic RAG。先把单路RAG跑通、把索引质量和指标建好,再逐步放开控制权。因为没有可靠的“基准检索”,Agent的每一步决策都是在沙地上盖楼。

4.2 知识割裂怎么解:GraphRAG与本体RAG的取舍

基础RAG还有一个结构性问题,业内常叫“知识割裂”——知识在物理上是分散的,在逻辑上却是连着的。一个客户实体、一个项目、一条职责链,它们的信息打散在各个文档的不同段落里,向量检索只能按语义相似度找回“局部”,很难跨文档把这些碎片串成一张完整的图。

近两年的GraphRAG和本体RAG就是冲着这个问题去的。GraphRAG的做法是:先让大模型从文档里抽取实体(比如“甲项目部”“合同编号A-2024”)、抽取关系(甲项目部负责A-2024的履约”)和属性,存进图数据库,再把图按社区的聚合关系做分层摘要。回答时,先走图谱找到相关实体和多跳路径,把这些“有结构的证据”结合原文一起交给大模型。

本体RAG更进一步:它不是让模型自由抽取实体关系,而是先定义一套业务本体(schema),比如“合同”“项目”“风险条款”“负责人”等类型,以及这些类型之间的约束关系,然后让抽取过程往这套schema上靠。这个思路对垂直行业特别有价值,因为业务的实体和关系本来就是有限的、明确的,定义好本体之后,检索结果可以严格对齐业务语义,避免模型自由发挥。

但是注意,图谱不是银弹。我测过的小规模场景里,如果把文档数量控制在几千篇且以单文档内问答为主,GraphRAG的收益并不明显,成本却高得多——你要维护抽取质量、图结构、更新机制。它真正发威的场景是:跨文档多跳问答、强关联推理、需要全局概览与聚类总结的大型知识库。我给团队的建议是,先看你的用户问题里“跨文档关联”的占比有多少,低于三成不要上图谱,先把向量+混合检索做到极致。

5. 工程落地盘点:我踩过的坑和绕过的路

5.1 增量更新比首次入库难十倍

接RAG项目时,最容易低估的不是首次建索引,而是后续的增量更新和权限变化。文档是活的,今天改了一版合同,明天上线了一份新规,后天某个部门的数据要全部下线。如果你的知识库不支持增量更新,每次全量重灌,成本和时间都会失控。

几个实用的做法。第一,每个chunk带版本号或更新时间字段,重建时同一doc_id先逻辑标记为下线(墓碑机制),新版本灌入后通过元数据过滤只查有效版本。第二,向量库的删除不是即时的,要靠后台任务处理;不要在业务查询路径上做全量删除。第三,文档的下线比增加更重要,被删文档的chunk如果不去掉,下一次检索必然召回“过时资料”,模型会一本正经地引用一个已经不存在的版本。

5.2 上下文爆炸与引用可信度

RAG项目到了生成阶段会遇到另一个坎:上下文太长。Top-K每个块取512字符,Top-5就是2500字符的上下文,再叠加上System Prompt和对话历史,模型很快吃满上下文窗口。

我常用的降载方案有三层:第一层,召回后在rerank阶段严格截断,比如只保留得分最高的Top-3,每个块限制在400字符以内。第二层,对长块做“上下文压缩”,抽取出关键句子或摘要,而不是整块塞进去。第三层,对某些确定性高的场景,直接用“引用列表”替代大段原文,让模型基于引用的片段走推理。这个压缩层是现在RAG优化的蓝海,做得好能让系统多扛好几轮对话。

引用可信度是我特别想强调的一点。RAG不是万能的护身符,模型仍然会幻觉,尤其当召回了内容模糊的相关段落时。我在生产系统里强制要求:模型的回答必须附带召回来源的文档ID和片段定位,前端渲染成可点击的引用角标。一旦用户能点开引用验证,你会惊讶地发现错误率直线下降——因为用户自己会交叉核对了,而模型的压力也小了很多。

5.3 中文场景的预处理细节和其他杂项

中文RAG里一些看似小的细节,对你的Hit Rate影响比想象中大得多。检查你的文本要不要做简繁体归一,全角半角统一;遇到“B2B”这种术语和“B2B平台”这类词的切分,中文分词器的词表直接影响BM25召回,一定要用领域词表做扩充再走关键词检索。

另外一个被忽略的是Query侧的正则化。用户可能把“2024-08-01”写成“2024/8/1”,“身份证号”里带不带空格,这些在你要走精确匹配时会造成召回失败。建议在缓存检索结果和做评估前,先统一规范化文本。

5.4 评估体系建设建议:先建评测集再优化

这一篇反复提到“评估集”,最后分享一下怎么低成本建立一个能持续用的RAG评测集。不用一开始就标几百条人工数据,那样成本太高。我的做法是:从线上日志里拿真实用户query,挑出离线的种子问题集,然后用一个较强的LLM对每个问题生成“答案摘要”,再人工抽样纠正一批,作为种子标注集。每个月再从新日志里抽一批新query加入。

有了评测集,优化顺序就清晰了:先跑一遍现有链路,算HitRate@5;然后看失败case归类原因;改进某一段(解析/分块/embedding/重排)后再重算指标。这个闭环一旦跑起来,后面几天你都能感受到效果稳定改善。我见过最快的一次,仅靠把分块从“固定窗口”改成“按结构切块”,HitRate@5就从58%升到了79%,效果极其明显,而之前大家还在反复换模型。

做个简单总结,基于我自己的实操经验,RAG这条管道的核心价值不是“炫技”,而是给Agent一个可靠、可观测、可迭代的知识入口。无论接下来你准备尝试GraphRAG、Agentic RAG,还是把它嵌入到某条业务流水线,都绕不开基础的索引、召回、评估这三个环节。先把地基打稳,进阶的路会顺很多。

返回列表