1. 先把 RAG 的结构“望远镜”端起来,看清我们到底在聊什么
在刚过去的这一年里,我做过的所有 RAG 相关咨询和技术方案复核,几乎开场都会先问对方同一个问题:“你现在用的是哪种结构?”结果十个人里有七个会愣一下,然后回答“就是那种先把文档切开,做向量化,然后检索拼接给大模型,对吧?”
这就是问题所在。很多人把 RAG 默认理解成唯一的一种管线:解析文本、切块、向量化、向量检索、拼上下文、输给大模型。一旦检索效果不好,就开始换 embedding 模型、换向量库、调 Top-K,反复折腾了好几个星期,结果发现还是检索不到关键信息。根据我这两年代码评审和性能调优的经验,八成以上“RAG 效果差”的案例,本质都不是参数没调好,而是结构层级不匹配——你用单跳的直筒结构去解多跳的逻辑问题,用只有向量召回的结构去扛强筛选、强排序的业务要求,用静态切块的粒度去承接需要全局理解的文档类型,怎么可能效果好?
所以这篇文章我不想对着索引、召回精度这些细枝末节的指标做机械罗列,而是想一次性把 RAG 的几种主流结构拆开,放在同一张工作台上对比,讲清楚每种结构适合什么、不适合什么,以及在迭代过程中怎么判断自己是不是选错了。
先总结一句:RAG 不是一套固定管线,而是一组可以自由组合的模块集合。模块之间的拓扑顺序、粒度设计、召回策略和数据回流方式,才是决定检索效果的核心变量。理解了这一点,你再回头看那些“为什么我换了更好的向量模型还是不准”“为什么知识库里明明有答案却查不到”的问题,会清晰得多。
全文我会按照下面的顺序展开:先从朴素的单跳向量结构讲起,因为它是一切问题的起点;然后引入混合检索和重排序结构,解释为什么召回只是第一步;再往上走到多跳和 Agent 化结构,看看复杂推理场景到底需要什么;最后给一套可落地的“结构诊断与迁移”方法,帮你判断当前系统是不是结构出了问题,以及怎么平滑切换。
阅读这篇文章不需要你有多深的算法功底,但如果你已经写过 RAG Demo、部署过向量库,或者正在生产环境上被检索效果折磨,那很多坑你会感同身受。我会尽量用“做过项目和调试过系统”的口吻来写,不堆理论,只讲真实环境里跑得通的判断标准。
2. 解构朴素的单跳向量结构——为什么它是默认选项,却又最容易被误用
2.1 单跳向量结构的标准管线与真实表现
我们先把最经典的 RAG 管线摆到台面上。它的运行逻辑是:文档进入系统后,先做清洗,再按固定长度切块(常见的是 256~512 个 token 一段),然后用 embedding 模型把每个块变成向量,写入向量数据库。用户提问进来后,同样做向量化,在向量库里做近似最近邻检索,把相似度最高的 K 个块取出来,拼接成提示词,交给生成模型处理。
这套结构的优点是清晰、模块解耦、上线快。我早期给某团队做一个内部知识库问答 PoC 的时候,全流程从数据清洗到跑通 Demo,大概只用了一个下午。这也解释了为什么很多人默认 RAG 就是这样的——它是进入这个领域最快的路径,也是绝大多数开源教程展示的范例。
但它的检索能力上限,取决于两个前提:信息在单个文本块内是自洽的,以及用户问题是线性能被单块匹配的。如果文档把某个结论的论据分散在不同章节,或者用户的问题需要把多个证据组合起来才能回答,那么单跳向量结构检索出来的块,往往会显得“意思相关但信息不完整”。
举个例子,某次我帮一个团队排查“为什么合同问答系统总是答非所问”,用户在问“我们公司和对方公司约定的违约金比例是多少”,而合同文本里约定了“如果甲方逾期交货,按合同总额的 3% 支付违约金;如果乙方逾期付款,按未付金额的每日万分之五支付违约金”。如果用固定 256 token 切块,这两条责任条款可能散落在两个不同的块里,向量检索出来的 Top-1 块只有其中一条。模型拿到的证据不完整,回答自然就会变成“根据合同约定,违约金为…”,却说不清适用条件,甚至可能把 3% 用在了付款逾期的场景上。
这不是 embedding 模型不够好,而是结构本身的粒度假设出了问题。单跳向量结构隐含一个前提:答案可以从单一文本块中直接找到。一旦内容出现跨块依赖、条件分支、需要归纳整合,单跳向量结构就会显出明显疲态。
2.2 为什么单纯升级 embedding 模型解决不了“结构病”
我见过太多人把检索效果差的原因归结为“模型不行”,于是从开源的向量模型一路换到更大参数量的商用模型,实测效果提升得很有限。这背后的逻辑其实很简单:embedding 模型负责的是语义相似度计算,它擅长的是判断“这句话和那句话在语义上相近”,但很难判断“这个文本块是否包含回答某个问题的完整证据链”。
现代向量模型的语义能力已经很强,比如同样一句“把门关上”,它可以理解不同的语境变体,但如果你给它两个文本块:一个是“甲方逾期交货,需要按照每天千分之三支付违约金”,另一个是“乙方逾期付款,需要按照每天万分之五支付违约金”,它的向量相似度都能和用户问题“违约金怎么算”匹配上,却无法区分哪个才是当前问题真正需要的限定条件。
换向量模型最多只能微调相似度的排序,让“更相关”的块排得稍微靠前一点,但无法改变“信息不在一个块内”的底层事实。所以我才反复强调一个观点:当你的 RAG 检索失败是因为证据不完整,而不是因为相似度排序不准时,更换 embedding 模型几乎没有意义。
要从结构上解决这个问题,通常有两个方向。第一个方向是改变切块粒度,增大块的大小,尽量让一个块包含更完整的条件约束。这个方案简单直接,但会带来新的副作用——块太大之后,向量表示会被稀释,精确匹配的准确率反而会下降。第二个方向是保留小切块,但在检索之后增加一层合并或重排序逻辑,把相关的块重新组装成更完整的证据段落。后面我会展开讲这两种方向的具体实现路径。
2.3 切块策略的实操细节:从固定长度到结构感知
聊到单跳向量结构,就一定绕不开切块。切块这个动作看似简单,实际是决定检索效果的第一道关卡。最常见的错误做法是把所有文档无差别地按 512 token 硬切,完全不关心文档本身的语义结构。
一个相对务实的做法是:先做结构识别,再做内容切分。以最常见的 PDF 技术文档为例,我会先用规则或者解析模型把文档的标题列表提取出来,识别出章节层级(章、节、小节),然后再在章节内部按语义边界切块。有些文本块可能天然比较短(比如一个列表项只有几十个 token),那就把它跟相邻内容合并;有些章节内部有明显的主题转折(比如“要点如下”后面带一小段排比),那就在主题转折处切开,而不是生硬地在第 350 个字符处一刀切下去。
这里有一个我在实践中验证过很多次的参数组合,可以供你参考:
| 文件类型 | 首选切块策略 | 块大小参考 | 重叠 token |
|---|---|---|---|
| 技术文档 | 按章节结构感知切分 | 300~600 token | 50~100 |
| 合同法律文本 | 按条款编号切分 | 单条或多条合并 | 0~50 |
| 问答型知识库 | 按自然段切分 | 100~300 token | 20~50 |
| 长文报告 | 先按章再按小节切分 | 500~800 token | 100~200 |
另外强烈建议保留每个文本块的元数据,比如来源文件名、章节路径、页码等。这听起来不像检索结构的一部分,但当你后续做结果溯源、权限控制、甚至基于章节做重排序的时候,元数据会成为最可靠的结构化锚点。我实际调试过的一个系统里,只在切块时加了一列“章节路径”的元数据,就解决了之前 30% 的误召回问题——因为只要看到返回的块属于“附录”而不是“正文”,直接过滤掉即可。
2.4 单跳向量结构最适合什么场景
单跳向量结构虽然被我用上面这些话指出了很多限制,但它在某些场景下依然是性价比最高的选择。比如:文档是标准化的 FAQ 集合,一个问题对应一个明确答案;知识库是按条目管理的,每个条目自包含;用户问题偏短,且答案通常在单个段落内即可找到。
以电商客服知识库为例,如果条目是“退货政策是什么——支持七日无理由退货”这类问答对,那么任何一个向量检索结构都能做到 95% 以上的准确率,这时候没必要引入复杂拓扑。另外在原型验证、内部工具、非关键业务场景里,单跳向量结构也是最合理的起步方案,因为它代码量少、调试成本低、上线速度快。
关键在于,你要有一个明确的“结构失效信号”,当它出现时就知道该系统该升级结构了。这个信号我在第 5 节会详细展开,你可以先记在心里:如果 Top-K 里似乎有“沾边”的块,但拼起来之后模型还是答不出完整信息,通常就意味着单跳结构已经到天花板了。
3. 在召回与生成之间加一道结构层——混合检索、重排序与上下文组装
3.1 强过滤场景下,为什么“向量为主”不够用
很多实际业务里,检索的难点不在语义理解,而在精确的约束匹配。举一个我印象很深的例子:某团队做一个“设备故障排查助手”,知识库里存了几百种设备型号的维修手册。用户提问“某某型号的报警代码 E002 怎么处理”,如果只做向量检索,返回结果里经常混入其他型号的相似报警代码,因为这些手册里的大段文本结构几乎完全相同,型号和报警代码这些关键实体反而是区别项。
这种场景下,向量相似度能告诉你“这是一段维修手册”,但很难精准区分“这是你要的那一型号的那一条”。传统的解决方式是走混合检索路线,把稀疏检索(关键词/BM25)和稠密检索(向量)的结果合并起来,让精确的型号编号和报警代码通过关键词命中。
我在实际项目里推荐的合并策略是倒数排名融合(RRF)。它的公式不复杂:对于每个文档,把在两种检索方式下的排名,按照 1/(k + rank) 的方式换算融合分。k 一般取 60 左右。这个方法的优点是不需要调权重标定,而且对两种检索结果的数量差异不敏感。如果你的场景里关键词匹配非常关键,也可以在 RRF 基础上对稀疏检索分加一个小权重。
混合检索解决的是“精确标识符召回”问题,但它也会带来新的问题:召回结果变多了,且多种来源的块在排序时互相干扰。这时候就需要重排序结构上场。
3.2 重排序不是锦上添花,而是承上启下的结构关键
很多开源 RAG 框架会把向量检索 Top-K 直接喂给大模型,跳过了重排序。在笔者的项目实践中,这个省略造成的效果损失非常明显。向量检索的 Top-K 排序依据是向量相似度,这个尺度上的“相关”,和问答场景真正需要的“包含答案证据”,存在两步偏差。
比较可靠的做法是:第一轮向量检索取比较宽的候选集(比如 Top-50 或 Top-100),然后送入一个重排序模型,对候选集做更精细的相关性打分,最后取 Top-3 到 Top-5 输入给生成模型。这样增加了重排序层的开销,但能明显提高最终答案质量。跨编码器(Cross-Encoder)类的重排序模型,会把问题和候选文本当作一个整体输入来打分,而不是像向量模型那样把问题和文本分别编码后再算余弦相似度。
我遇到过一个很典型的对比场景:某个系统用向量检索直接 Top-5 拼接给大模型,回答满意度在 62% 左右;加入重排序层之后,同样的问题集满意度提升到了 83%。最核心的变化不是召回了更多相关文档,而是排除了干扰项——大模型拿到的上下文更干净了。
需要提醒的是,重排序模型有长度限制(常见的是 512 或 1024 token),所以它通常在切块后单独执行,而不是对超长文档统一处理。如果你的块大于模型的长度上限,重排序前需要做截断或分窗处理。
3.3 上下文组装:让大模型拿到“有结构的证据”而不是“碎片拼接”
重排序输出 Top-3 到 Top-5 候选块后,很多人就直接用“\n\n”把它们拼进提示词。这样做能跑通,但碰到需要区分主次、需要并列对比、需要引用来源的问题时,模型输出的条理性会受影响。
我建议在拼接前做一次简单的上下文整理,步骤是:先按重排序分数从高到低排列,再给每个块标注来源元数据,最后用明确的格式组织。比如:
来源文档1:设备A维修手册,章节3.2 内容:xxxx 来源文档2:设备A维修手册,章节3.3 内容:xxxx这样做的价值不仅在于让大模型更容易定位关键信息,也在于让“哪些内容来自哪份文档”变成模型可见的结构线索。很多时候模型答错不是因为找不到信息,而是因为把来源 A 的信息和来源 B 的信息混在一起,无法区分条件边界。
另外,组装上下文时还可以考虑“去冗余”:重排序出来的多个块,可能都在讲同一个主题,内容有重叠。这种冗余会稀释大模型对关键信息的注意力。简单做法是按块与块之间的相似度去重,比如新块与已选块的内容重复度超过一定阈值,就跳过。这一步在有些场景里能明显减少生成模型的“车轱辘话”。
3.4 从单跳到多跳:当问题必须串联多个证据才能回答
混合检索和重排序解决的是“顺序不对、证据不干净”的问题,但还有一类问题它们解决不了:用户问题的答案需要综合分布在多个位置的证据才能得到。比如“某型号设备在温度超过 40 度时的报警阈值是多少”,答案可能要查维修手册里的“工作温度范围”,再查“报警参数表”,还要看“环境要求”章节。这些信息可能分散在三四个块里,任何一个单块都无法直接回答。
这类多跳问题在单跳向量结构下表现非常差,因为向量检索是“一步到位的相似度匹配”,很难规划检索路径。解决这个问题有几条路,按引入复杂度从低到高分别是:
首先是“问题分解”,把一个复杂问题拆成多个子问题,分别检索后再合并答案。这种方式不需要复杂的 Agent 框架,只要在提示词里让模型输出子问题列表,然后分别检索,最后合并即可。缺点是子问题的拆解质量直接决定最终答案质量,而且如果涉及子问题之间的依赖关系,比如第二个问题的答案取决于第一个问题的检索结果,这种方式就会失效。
其次是“迭代式检索”,规定一定的轮次上限,每轮根据已有的上下文生成新的检索意图,继续召回新证据,直到模型认为信息足够回答用户问题。这个方向已经偏向 Agent 化了,但可以控制在一个相对收敛的流程里。
再次是“图谱结构”,把文本块之间的引用、共性实体、逻辑依赖关系建模成语义图或知识图谱,检索时从用户问题对应的节点开始,沿着关系边遍历多跳路径,把路径上的节点作为检索证据。这种结构对数据质量的要求很高,构建和标注成本不低,但它在多跳推理上的表现,确实比单纯靠向量检索稳定得多。
多跳问题的结构选型,核心判断依据是:问题是否需要隐含路径规划。如果答案是确定的因果关系链,比如“原因导到事件再到影响”,那考虑图谱结构或迭代式检索是合理的。如果问题只是需要把几段并列条件拼在一起,那用“问题分解 + 并行检索”就够了,没必要上重结构。
4. 进阶拓扑拆解——面向业务复杂度的 RAG 结构演进路线
4.1 Agentic RAG:从“检索一次”走向“检索 + 行动交替”
如果检索的目标不再只是“回答问题”,而是“基于检索结果完成一系列动作”,那么你的 RAG 结构就已经向 Agent 化演进了。Agentic RAG 和传统 RAG 的本质区别,在于它把检索变成决策循环的一部分,而不是一次性的上下文供给动作。
举一个实际落地过的例子:某团队做一个“内部采购政策问答助手”,用户会问“我能不能用部门经费买一台高端显示器,如果可以,需要谁审批”。如果是传统 RAG,系统检索出几条相关采购政策,然后直接根据政策文本生成答案。但政策通常有例外条款、金额分级、申请流程等多个环节,答案的完整性依赖于多个政策的联合判断。Agentic RAG 会先把问题拆成几个检索意图,比如“部门经费适用范围”“高端显示器的资产分类”“不同金额的审批流程”,每轮检索到结果后,判断是否需要继续追问或检索,最后汇总所有证据生成带审批路径的回答。
这种结构在技术上并没有那么玄妙,核心是把检索动作编排成了可循环的子任务。它和普通“多轮 RAG”的区别在于,它有明确的终止条件判断,比如“确认所有子问题都已经找到对应政策条款,且没有冲突”,而不是机械地固定检索两轮就结束。
落地 Agentic RAG 的代价是系统复杂度显著增加。你需要控制检索循环的轮次上限、设计路由规则、处理多轮检索之间的上下文一致性,还要面对更长的响应延迟。所以在选择这个结构前,先认真评估问题复杂度是否真的需要这种编排。我见过不少团队把简单的问答系统硬做成 Agent 结构,结果延迟高了、成本涨了,准确率并没有明显提升。
4.2 父子切块与段落重组结构:解决“粒度矛盾”的一招
文本切块的大小,存在一个天然矛盾:块越小,向量表示越精确,但语义完整性越差;块越大,语义越完整,但向量表达越被稀释。父子切块结构就是为了缓解这个矛盾提出的。
具体做法是:把文档先切成较大粒度的“父块”,比如按章节或较长段落(800~1500 token),把文档的语义上下文锁定在父块层面。然后在父块内部再切出更小的“子块”,比如 200~300 token,用于向量检索。检索时,用子块的向量去做相似度匹配,但最终输入给大模型的却不是子块,而是它所属的整个父块。
这种结构的好处是:检索的匹配精确度来自小子块,语义完整性来自大父块。比如用户问“合同到期后自动续约的条件是什么”,子块检索可以精准命中“自动续约条款”所在的那一小段,而输入给模型的则是包含该条款的完整合同章节,模型能看到期限、条件、通知义务等完整的约束环境。
实际实现的关键是维护好父块和子块的映射关系。我通常会在子块的元数据里记录 parent_id,重排序结束之后,把命中的子块对应的父块拉取出来,做去重后组装。这样做会有个副作用:上下文变大、token 消耗变多,但很多场景下这是值得的。
4.3 图结构 RAG:让实体关系成为检索路径
图结构 RAG 是这几年的热点,但我必须坦诚地说,它并不是适合所有场景的万能结构。它的核心思路是:在文档解析后,额外构建实体关系图,比如从合同里抽取“项目方”“服务方”“金额”“期限”这些实体,以及它们之间的关联关系。检索时,不再只做文本相似度匹配,而是结合实体关系路径来推断哪些内容是相关的。
适合图结构 RAG 的典型场景包括:法律关系、复杂产品规格关联、多文档联合问答(比如“A 产品的变更会不会影响 B 产品的接口协议”)。这种问题的答案在文本层面是分散的,但通过实体关系图可以找到“产品 A——变更记录——接口协议——产品 B”这样的路径。
图结构 RAG 的构建成本很高,因为实体抽取错误会被放大成路径错误。在项目资源有限的情况下,我的建议是先用父子切块 + 混合检索应对大部分问题,等到你发现跨文档的关联问题占到了总问题量的一定比例(比如超过 20%),再考虑用图结构去补足那一块需求,而不是一开始就上重结构。
4.4 结构化数据与文本的联合结构:SQL 与 RAG 的握手
还有一种经常被忽略的“结构用错”,是静态地把结构化数据塞进文本块做向量检索。比如某些团队把订单记录、用户信息等表格数据格式化后作为文本块入库,结果检索效果非常差,因为表格数据的语义相似度和行级精确匹配完全是两回事。
正确的做法是采用“混合路由结构”:对于结构化的、需要精确聚合的查询,比如“最近三个月订单金额排名前十的产品”,走 SQL 或 API 查询;对于开放式的、需要语义理解的查询,比如“退货率异常的可能原因是什么”,走向量检索。在系统入口处做一次查询路由判断,再分发给不同的执行模块。
这种结构的关键在于路由的准确性。可以用一个轻量分类器或大模型分类器,把用户问题识别为“结构化查询”或“文本语义查询”,但要做好兜底——路由判定错了,结果会一整条链路偏掉。我在生产环境里通常会让路由模块额外输出一个置信度,低于阈值时同时走两条链路,再在结果层做融合或二选一。
5. “效果差”不等于“模型差”——结构诊断与迁移的实操框架
5.1 用分层诊断法定位真正的瓶颈
最终落到实操层面,很多读者真正想知道的是:我怎么判断自己的系统结构是不是用错了?我这里给出一套可复现的分层诊断流程,不需要什么高级工具,只需要准备一批带标准答案的测试问题集。
第一层,检查召回质量。随机抽取 30~50 个测试问题,人工看 Top-10 召回结果里是否包含真正的答案来源块。如果答案来源块根本不在 Top-10 里,说明召回环节已经失守,这时候大概率是切块粒度、索引策略或混合检索设置出了问题。如果答案来源块在 Top-10 里但不稳定,排名时高时低,问题大概率出在排序策略上。
第二层,检查重排序质量。在召回没问题的基础上,看 Top-1 到 Top-3 里是否有能直接支撑答案的块。如果没有,可能是重排序模型与文档类型不匹配,或者是父块太大导致重排序模型输入窗口丢失了关键信息。
第三层,检查生成质量。如果召回和重排序都没问题,但最终回答仍然不对,那要看是不是提示词结构导致的上下文利用不充分。比如多个证据块之间缺乏关联说明,或者块与块之间存在冲突信息没有进行消解。
用这套流程诊断过的项目里,至少有 60% 的问题最终定位到了第一层,而不是生成模型上。这是个很能说明问题的比例——大多数团队都在“模型不够好”上反复烧钱,实际上结构缺陷早在召回阶段就决定了最终结果的上限。
5.2 结构迁移的最小改动原则
假设你已经确认了问题是结构层面的,接下来要思考的就是怎么改。我强烈建议你不要推倒重来,而是做最小改动迁移。所谓最小改动,是指在原有管线中插入或替换一个模块,而不是推翻整个实现。
例如,当你发现上下文粒度不够时,可以先加一个“父块映射”模块,而不是立刻重写全部切块逻辑;当你发现问题需要多跳证据时,可以先加一个“子问题分解 + 并行检索”模块,而不是直接上完整 Agent 框架。这样做的理由很现实:每引入一级结构,系统的延迟、成本和故障点都随之增加。结构升级应该像打补丁,哪里痛打哪里,而不是为了追求架构上的“前沿感”。
另一个重要的实操建议是:每做一次结构变更,都要用同一套测试问题集做回归。我在某个项目里会固定一个约 200 道题的评测集,覆盖不同的文档类型和问题类型,任何结构改动之后都跑一遍,看综合指标是否提升。这样能避免你在修 A 类问题的时候,副作用波及 B 类问题而不自知。
5.3 常见失败模式速查表与规避建议
| 症状 | 可能的结构缺陷 | 推荐调整方向 |
|---|---|---|
| 答案总是缺少限定条件 | 单跳向量结构导致证据不完整 | 增大切块粒度或引入父子切块结构 |
| 精确的编号、代码类问题误召回 | 纯向量检索对精确标识符不敏感 | 接入稀疏检索做混合召回 |
| Top-K 里看似相关但答案总偏 | 缺少重排序,上下文干扰项过多 | 插入重排序模块,收敛到 Top-3 |
| 跨文档关联问题完全答不出来 | 单跳结构无法多跳规划 | 问题分解或迭代检索,必要时上图 |
| 高延迟 + 复杂 Agent 但准确率没提升 | 结构过度设计 | 简化到传统 RAG + 重排序即可 |
| 结构化数据被塞进向量库 | 结构类型与数据形态不匹配 | 改走 SQL/API + 文本检索混合路由 |
这张表是我从项目复盘里整理出来的,基本上涵盖了我会在评审中被问到最多的几类问题。你可以把它当作自查清单,任何一条命中,对号入座去调整结构,比盲目调参数要高效得多。
5.4 我的实操体会
在这几年的 RAG 系统调试经历里,我最深的一个体会是:检索效果的提升从来不是线性的,而是跳跃的。很多时候你觉得模型越换越好、参数越调越精细,但效果提升只有几个百分点;而某一次你调整了结构,比如把固定切块改成结构感知切块,或者插入了一个简单的重排序模块,效果反而会猛地跳上一个台阶。原因很简单,模型和参数是连续变量,就算调到最优,也只是在当前结构上限内的微调;而结构是离散变量,每换一种结构,系统的检索能力上限就会重新改写一次。
我也越来越认同一个判断:RAG 系统设计的第一要务,是搞清楚你的问题和文档之间的结构关系,而不是急着选哪个 embedding 模型。文档内容是并列的、递进的、分散的还是高度关联的?用户问题是单点查询还是多跳推理?明确了这些,结构的轮廓就自己浮现出来了。
最后再分享一个小技巧:在接到任何一个 RAG 需求时,我建议你先抽出半天时间,人工浏览 20~30 个有代表性的问答对,标注出“答案通常落在文档的哪个位置、需要几段内容才能完整作答”。这个动作不需要任何代码,却能最直观地告诉你应该用单跳、多跳、父子还是图结构。我几乎每次做结构决策前都会做这个标注,虽然它看起来不够“技术”,但它能帮你少走一整根管线的弯路。