ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RAG与复合AI系统精读:组件化设计与证据位置优化

RAG与复合AI系统精读:组件化设计与证据位置优化

CS329Z这门某高校研究生研讨课,第一讲的阅读材料刚读完,三篇分别是《Compound AI Systems》《Searching for Best Practices in Retrieval-Augmented Generation》和《Lost in the Middle: How Language Models Use Long Contexts》。说实话,这三篇放在第一讲非常聪明:它们不是在教你怎么调参,而是在逼你先换一套看问题的框架。很多做RAG落地的人——包括我自己——一开始都执着于“换个更大更强的模型”,总觉得效果不好是模型智商不够。读完这三篇你会意识到,真正决定上限的往往是系统结构:谁负责检索、谁负责重排、谁负责生成、信息按什么顺序喂给模型,这些环节组合起来才是核心竞争力。这篇博文是我的精读笔记,目标是帮你把三篇材料的内在逻辑、核心结论和能直接落地的操作方法串起来。适合正在做知识库问答、RAG应用、Agent系统,以及想系统理解AI系统设计的工程师和学生。

1. 第一讲为什么用这三篇:一次“反直觉”的系统论开场

1.1 从“换个更强模型”到“重新设计系统”

先聊聊我观察到的一个普遍现象。过去一年里,凡是在做AI应用落地的人,几乎都有同一个思维惯性:任务效果不行,第一反应是换更大的模型、堆更长的上下文、写更复杂的提示词。这个思路不能说错,但它有个隐蔽的代价——你把所有赌注都押在了一个不可拆解的黑盒上。模型A不行换模型B,B不行再换C,到头来你可能都不知道问题到底出在哪一环。

CS329Z第一讲上来就打破这个惯性。它给你的核心框架是:不要问“哪个模型最强”,而要问“这个任务需要哪些组件、组件之间怎么协作”。RAG就是最典型的例子——它本身就是一个由索引、检索器、重排序器、生成器组成的复合系统,而不是“一个模型带检索”。你把RAG当成模型的外挂也好,当成检索增强的提示词拼接也好,本质上都低估了组件协作的复杂度。

这我可以用一个类比来解释。单一的大模型像一个全科医生,什么病都能看一点,但你看不出他到底是在“诊断”还是在“猜”。复合AI系统像一个专科门诊:有导诊台做意图分流,有检验科出客观证据,有专科医生基于证据下结论,有药剂科负责执行。每个环节都能单独检查、单独优化、单独替换。全科医生省事,但一遇到复杂病例,专科门诊的可靠性和可维护性就完全不是一个量级了。三篇阅读材料分别对应“为什么需要门诊”“门诊怎么排科室”“排科室时的关键证据”这三层问题。

1.2 三篇材料各自的角色与逻辑链条

这三篇不是随便凑在一起的,它们之间有明确的递进关系。我用一张表先给你一个全局视角:

材料类型核心问题对做系统的人的价值
Compound AI Systems立场文章(position paper)AI系统为什么从单一模型走向多组件组合提供系统设计的思维框架和分类方法
Searching for Best Practices in RAG系统化实证研究检索增强生成各模块怎么组合效果最好给出可直接照做的RAG模块选型与参数建议
Lost in the Middle小型实证研究大模型在长上下文中如何利用不同位置的信息用证据说明“输入侧”设计为什么重要

第一篇是世界观,告诉你AI应用已经进入组件的时代,单模型只是其中一个零件。第二篇是战术手册,把RAG拆成索引、检索、融合、重排、生成这几个环节,用大量对比实验告诉你哪些选择性价比高。第三篇是关键证据,它直接测量了模型对长上下文的注意力分布,证明了一个反直觉的事实:上下文越长,模型越容易漏掉中间位置的证据。这三篇合起来,其实就是一套完整的方法论:先建立系统视角,再按模块拆解优化,最后用位置和证据选择的意识去组织输入。

这里我需要特别提醒一点:第三篇虽然发表时间早于后来很多长上下文模型的出现,但它的结论并没有过时。后来的不少研究也反复验证了相似的现象——扩展上下文窗口并没有根本解决模型对中间信息的忽视问题,仅仅是把“放不下的信息”变成了“放得下但会漏掉的信息”。所以它作为RAG存在的理由,至今依然成立。

1.3 我推荐的阅读顺序和记笔记方法

如果你也想精读这三篇,我建议的顺序和课程给的顺序不太一样。课程的安排是《Compound AI Systems》在前,但我的实际体验是:先读最短、结论最硬的《Lost in the Middle》,你会被“U形曲线”的图表震撼到,带着这个冲击再去读RAG最佳实践研究,就会一直问“这个模块选择是不是为了让证据出现在更好的位置”;最后读《Compound AI Systems》,这时候框架性的论述就变得非常自然了——你已经看到了证据和实践,再从高处去理解整体哲学,信息吸收效率最高。

记笔记的时候我习惯用一个固定模板,每篇论文只记三栏:核心主张、支撑证据、对我的系统有什么影响。别贪多,一篇论文哪怕只提炼出三个能落地的点,就已经值回票价了。比如读《Lost in the Middle》时,“核心主张”是模型中间位置信息利用率低,“支撑证据”是位置干扰实验的U形曲线,“对系统的影响”就是检索结果必须重排后按相关度降序喂给模型,而不是按原始文档顺序。这种笔记方式特别适合后续回看和对照实战。

2. Compound AI Systems精读:把单一模型当成一个组件

2.1 论文在反对什么:单一模型崇拜的三个幻觉

这篇立场文章在我看来,通篇在做一件事:拆穿“单一模型崇拜”。我给你归纳成三个幻觉,它们分别对应三种常见的错误思维。

第一个幻觉是“模型越强,系统越强”。你可能会觉得,我只要把模型能力往上顶,所有问题都自动溶解。但现实是复杂任务往往没有现成的单一模型能完美覆盖——你需要检索外部知识,需要执行代码,需要查工具API,需要多轮自我纠错。这些能力就算某个模型“会”,它的成本、延迟、行为可控性也会让你在生产环境里非常难受。更关键的是,系统里的各个组件是有协同效应的:一个在单项能力上只有80分的检索器,配合一个合适的生成器,整体效果可能超过“百亿参数模型硬记一切”。

第二个幻觉是“提示工程能解决所有问题”。提示词确实是强大的表达工具,但它只负责“一步”推理。真正的复杂任务往往需要多条推理链、多轮状态更新、外部信息访问,这些靠一个“万能指令”去压缩,既不高效也不稳定。你可以把提示词理解为给专科医生的会诊单——它写得再详细,也不能替代检验科和影像科的工作。

第三个幻觉是“端到端训练是唯一正道”。从纯研究的角度,端到端微调确实有理论上的优雅之处,但它是出了名的数据饥渴和算力饥渴。对一个几十万条数据的领域问答场景,你去微调一个几十亿参数的模型,成本大概率是检索器加点规则系统的几十倍,而效果未必赢。论文想强调的是:端到端只是组合方案中的一种,在工程上它的局部可更新性很差——换一个检索器,整个模型可能得重新训一遍,这在快速迭代的生产环境里是不可接受的。

2.2 组合系统的骨架:I/O合约、控制流与共享状态

如果你要实际构建一个复合AI系统,这篇论文给了一套很重要的概念工具。我提炼成三个关键词:I/O合约、控制流、共享状态。

I/O合约的意思是,每个组件的输入和输出必须有明确、稳定、可验证的接口。比如“检索器”这个组件,输入是一个查询字符串,输出是一组带相关性分数的文档片段,这个接口定下来之后,下游的重排序器不需要关心检索器内部用的是向量搜索还是倒排索引还是混合搜索。这就像车间的流水线每个工位只认上一步给的半成品规格,不关心上一工位用什么机床加工出来的。I/O合约越清晰,组件的可替换性越强,你的迭代速度就越快。

控制流解决的是“组件之间的执行路径”。最基础的是管道式,比如检索→重排→生成,一条线走到底;再往上是路由式,根据查询类型决定走哪种处理分支,比如事实型问题走RAG,数学问题走代码解释器;再高阶一点是循环式,生成器发现自己答案缺少论据,再触发一次检索,形成多轮迭代。很多Agent系统本质上就是路由加循环的组合。

共享状态则是指组件之间的记忆和中间结果。比如缓存检索结果、保存对话历史、存储已生成但还没验证的计划,这些都是需要在多个组件之间共享的。很多人在设计Agent时忽略共享状态,结果就是每个组件都“失忆”,系统整体显得非常笨拙。我自己的体会是:动手搭系统之前,先用方框画出所有组件,用箭头画出数据流,把每个接口的输入输出写清楚,这一步至少值一个下午的时间,而且能在后面节省你一周的调试时间。

2.3 可靠性才是组合系统的第一回报

论文里详细论证了组合系统在性能上的优势,但我觉得对工程实践更有价值的是它带来的可靠性。单模型系统出错了,你面对的是一个黑盒:你是该改提示词?还是该换模型?还是在输入侧多给点信息?你很难定位。但组合系统不一样,每一层都有边界,每一层都可以单独观测、单独验证。

举个例子,我搭过一个内部FAQ助手模拟项目,一开始就是“一个模型加一大坨背景资料”的单体结构,用户反馈“答案经常牛头不对马嘴”。我根本判断不了是背景资料的召回缺了关键信息,还是模型把信息理解歪了。后来改成组合结构,在检索层和生成层之间加了日志和接口检查,问题立刻暴露:大部分错误根本不在生成端,而是检索端召回了一批无关文档,把噪声喂给了模型。这个定位能力就是组合系统最直接的回报。你可以在检索器和生成器之间加一个“证据检查点”,记录每条答案用了哪几个文档片段,一旦出问题,是召回的问题还是推理的问题一目了然。

组合系统还有个隐性回报是模块可替换。今天你用的向量检索效果不行,换成混合检索引擎,生成模块完全不用动;明天你想把生成器从A模型换成B模型,检索和重排层也完全不受影响。这种“换了零件不用重装整机”的感觉,才是系统工程该有的样子。

3. Searching for Best Practices in RAG精读:RAG的正确打开方式

3.1 作者到底比了什么:模块拆解与交叉组合

这篇研究读起来非常解渴,因为它终于不跟你讲玄学了。作者把RAG拆成了一层层可以独立替换的模块:数据切分策略、嵌入模型、检索算法、结果融合、重排序方式、生成器提示写法。然后像做正交实验一样,把这几十种组合方案放在多个基准数据集上做了大规模对比。关键是,它不止看最终生成的准确率,还看每一层的中间指标,比如检索召回率、文档利用率,这就让结论可以精确定位到某个模块上。

这种“控制变量”的思路对工程实践特别有指导意义。很多人调RAG是玄学调试——这调一下那调一下,最后效果好了也不知道哪个变更起了作用。正确做法是每一次实验只动一个变量,并且把你关心的中间指标记录成表格。我在复现这个研究思路时建过一张实验记录卡,每行是一个组合方案,每列是召回率、重排命中率、最终答案准确率、端到端延迟。就靠这张表,我从“感觉A方案好”变成了“数据说明B方案在长尾查询上比A多5个点”。

这个研究还特别注意了查询类型的差异。事实型问答、多跳推理、时间敏感类查询,对检索模块的要求完全不同。比如时间敏感类查询,你不能只依赖文档向量的相似度,还得考虑时效性的过滤,这时候关键词检索或元数据过滤往往比纯向量检索更稳。

3.2 三个能直接抄的结论

第一个结论:混合检索显著好于任何单一检索方式。也就是把向量检索和关键词检索(类似BM25的思路)的结果做融合。我在实际系统里的感受特别明显:向量检索对语义相近的表述很友好,但对精确的专有名词、代码变量名、订单编号这类内容非常不友好,而关键词检索恰好擅长抓这些稀缺但有决定性的词。两者融合后,长尾查询的召回稳定性会明显改善。

第二个结论:切分粒度粗一些,配合重排序,效果比细粒度切分更好。很多人有个直觉——把文档切得越小,检索粒度越精细,效果就越好。但研究发现,过度细分的块会因为上下文丢失,导致后续生成时论据不完整。粗粒度切分(单个片段能容纳一个大段论述)再加上重排序环节,把最相关的段落在生成前捞出来,整体表现反而更好。我自己的经验是切分时还要保留段落标题和层级结构信息,这样就算一个答案跨了几个小块,生成器也能拼回完整的论证背景。

第三个结论:重排序是性价比极高的模块。即便用一个不算特别强的交叉编码器做重排序,端到端效果都有稳定提升。原因也不难理解:检索器给的是“相关候选”,重排序器是在这些候选中找“精确证据”,它能有效压缩噪声。重排序器慢一点没关系,你只需要在检索结果前20个里面重排取前5个,这个计算量完全可控。

3.3 两种推荐方案:模块化流水线学端到端微调

这篇研究给出了两套值得推荐的方案,有意思的是,它完全不迷信端到端。方案A是模块化流水线——一个由混合检索、结果融合、交叉编码器重排、生成器组成的标准链路,用现成的通用模型和经典算法拼装。这个方案的优点是可解释、可替换、成本低,适合大多数起步场景和中小规模数据。

方案B是端到端微调——把整个检索生成链路作为一个整体进行训练。它的优点是在数据充足、任务模式非常固定的场景下,可能达到更高的上限;缺点是训练成本高、更新周期长、中间环节不可观测。论文的比较结论其实暗含了一个更实用的判断标准——如果你的数据量不足以支撑端到端训练的规模,模块化流水线往往是更理性的选择。

我的实际倾向是:除非你有明确的领域数据优势和长期迭代预算,否则默认先做模块化流水线。先用这套方案把系统跑起来、把评估指标拿到手,再根据失败案例去决定要不要局部优化。我在模拟项目中就是这么做的——先用通用嵌入模型加BM25加交叉编码器重排加通用生成模型拼了一套线,效果已经可以满足需求,而训练预算一分钱没花。

3.4 落地时容易理解错的几个点

这篇研究虽然给出很多可操作的结论,但我发现不少人在落地时还是理解偏了。偏差点之一是“检索是万能的”。实际上,查询本身如果很模糊,比如用户问“帮我看看那个上个月改过的文件”,直接拿这个查询去检索效果很差——你得先做查询改写,把指代消解掉,变成“上个月修改且包含某关键词的文件”,再送进检索器。

第二个容易偏的点是“top-k越大越好”。很多人觉得把检索结果多塞点进上下文,模型信息更足。但结合第三篇论文的结论你就知道,塞得越多,中间位置的噪声干扰越强,模型反而抓不住重点。在做了重排序之后,真正留给生成器的证据应该是精炼的3到5个片段,而不是10个20个堆进去。

第三个常见的错位是“只调最终提示词”。我见过不少项目,检索召回率明明只有40%,却花了两周时间研究生成端的提示词模板。提示词再怎么写,也救不回来了信息本身。先看每一层的中间指标,再决定在哪一层动手,这才是这套方法论教给你的精髓。

4. Lost in the Middle精读:为什么长上下文模型拯救不了RAG

4.1 实验是怎么做的:把答案放在不同位置

这篇论文的设计很干净:给模型一段很长的上下文,里面混入若干文档,其中一份包含回答问题所需的关键信息。然后系统性改变这份关键文档在上下文中的位置——放在最前面、放在中间、放在最后——再测量模型回答问题的准确率。任务类型包括阅读理解问答和多项选择,测试了多个当时的主流模型。它的巧妙之处在于,控制了“信息内容完全相同”,只改变“信息所在的位置”,从而精确测量模型的位置偏差。

这个思路对做系统的人特别有启发。它把“注意力分配”变成了可测量的变量,而不是停留在直觉层面。你可以在自己的模型上复现一遍这个实验:拿一个长文档集,把答案句分别放在第1段、第50段、第100段,去看准确率怎么变化。做完这个实验你会对“给模型喂多少上下文”有一个非常体感的认识。

4.2 三个发现:U形曲线、干扰效应、提示无效

第一个发现是经典的U形曲线:关键信息位于上下文开头时准确率最高,位于末尾时其次,位于中间时大幅下降。这说明模型对输入序列的开头和结尾有明显的注意力偏好,而中间位置变成了“注意力的荒漠”。这个现象我在自己的长文档问答实验里复现过,准确率的跌幅可以超过20个百分点,属于非常显著的系统性效应。

第二个发现是:上下文越长,即便包含正确答案,整体准确率反而下降。这个看起来反直觉,但背后的机制很清晰——无关信息越多,模型花费在理解噪声上的注意力就越多,关键证据的有效性就被稀释了。这个结论直接击穿了那种“把上下文窗口堆到百万token就万事大吉”的迷思。窗口能容纳信息,不代表模型能有效利用信息。

第三个发现有点扎心:实验者尝试在提示中加入“注意中间位置”之类的指令,想引导模型关注中间段落,但效果很微弱。这意味着位置偏差不是简单的“模型没被提醒”,而更像是注意力机制的固有倾向。靠提示词补救不如靠系统设计补救:要么把信息放在开头,要么通过重排序把关键证据提前。后来我用一个简单的位置重排实验做了验证——把检索结果按相关度降序排列后再喂给模型,答案命中率比按原始文档顺序排列时高出一截。成本几乎为零,收益却很稳定。

4.3 对RAG系统的启示:证据位置也是排序问题

这篇论文对RAG最直接的启示是:检索结果的组织方式本身就是一种优化空间。很多RAG管线把检索到的文档“按原始顺序拼接”,给你一种“我忠实反映了检索结果”的感觉,但这恰恰是最吃亏的做法。正确的姿势是,重排之后的证据按照“最相关、最必要的放前面”的原则组织,让生成器最先看到最有价值的论据。

实践中我会做一个“证据位置优化”的步骤:重排序器输出前5个片段之后,再按一个简单规则调整顺序——包含直接答案的片段永远是第一个,其次是包含推理链关键前提的片段,最后才是背景补充性的片段。这个规则不需要复杂的模型,就是一个几十行的排序逻辑,但它在我的几个问答数据上都能带来可感知的提升。它的底层原理就是顺着模型的注意力分布到上游去做设计,而不是在下游勉强修补。

另外一个实际建议是:限制真正进入生成上下文的证据数量。与其给20个群杂的片段,不如用重排序砍到3到5个高质量片段。这样既降低了伪造噪声,也避免了中间位置的注意力浪费,还省了token成本。这一步对RAG系统的性价比高得惊人。

5. 三篇合读:把“系统思维”落到RAG落地流程

5.1 一个决策框架:什么时候用长上下文,什么时候用RAG

三篇材料读下来,你手里其实已经有一张决策地图了。我帮你整理成一个可直接套用的框架,核心是回答三个问题:任务是否依赖外部知识?知识总量有多大?更新频率和延迟要求如何?

我用一张决策表来总结:

任务场景推荐方案理由
固定规则、简单模板问答直接用生成模型加提示词不需要外部证据,组合系统属于过度设计
知识量小且基本不变,完全能塞进上下文长上下文单次填完省去检索系统成本,但注意把关键信息放在上下文开头
知识量大、持续更新、严格依赖最新信息RAG(混合检索加重排再生成)每次只喂最相关的证据,避免中间位置丢失和噪声干扰
需要调用外部工具、多步推理、多轮自我纠错完整的复合AI系统,RAG只是其中一个环节需要路由、循环、共享状态等组件协同

注意表格里的第二行,我也特意标注了“关键信息放在开头”。这不是多余提醒,而是直接来源于第三篇的U形曲线结论。就算你选择用长上下文方案,也要在组织材料时把最重要的答案句放在最前面,而不是放在腰部。

5.2 最小可行RAG:五步搭建流程

结合三篇材料,我可以给你一套最小可行的RAG搭建流程,每一步都可以直接照抄。第一步是数据准备与切分:清洗重复内容,按语义段落做粗粒度切分,每个片段保留标题和层级信息作为元数据,片段之间可以加少量重叠以避免切分切断关键句子。第二步是建双路索引:一路用通用嵌入模型做向量索引,一路用关键词统计方式做稀疏索引。第三步是双路检索与融合:对查询同时发给两路,各取前15到20个候选,用加权融合算法合并排序,目标是让两路的优势互补。第四步是重排序:用一个交叉编码器在融合后的候选里重新打分,取前3到5个进入生成环节。第五步是组织证据并生成:按相关度把片段排序,最重要的放最前面,然后构造提示词让生成器基于这些证据作答并附引用来源。

我给你一段简化的伪代码,方便你理解数据流:

def build_rag_pipeline(query, top_k=20, rerank_num=5): # 双路检索 dense_hits = vector_index.search(query, top_k) sparse_hits = bm25_index.search(query, top_k) # 融合排序 merged = reciprocal_rank_fusion(dense_hits, sparse_hits, k=60) # 重排序:交叉编码器取前N reranked = cross_encoder.rerank(query, merged[:top_k])[:rerank_num] # 证据前置:相关度最高的放最前面 ordered_ctx = [doc.text for doc in reranked] # 构造提示并生成 prompt = build_prompt_with_context(query, ordered_ctx) return llm.generate(prompt)

这套流程里每一步都有对应依据:双路检索来自最佳实践研究的混合检索结论;重排序和证据前置来自Lost in the Middle的位置敏感结论;整体的组件化设计来自Compound AI Systems的I/O合约思想。你不需要一上来就搞很复杂的Agent编排,把这条最小链路跑通,拿到基线指标,再逐步加路由、缓存、查询改写这些增强模块。

5.3 我在实践里踩过的几个坑

最后分享几个我在模拟项目中实际踩过的坑,希望能帮你少绕路。

第一个坑是只做向量检索,召回质量在长尾查询上崩盘。当时我处理一批包含大量产品型号的语料,用户拿型号来问,向量检索经常召回不到精确匹配的片段,反馈“明明资料里写了,却答不上来”。后来按最佳实践研究的方法加上关键词索引做双路融合,这个问题的消失速度非常快。这提醒我:不要被“语义检索万能”的观点带偏,精确词匹配在很多场景下是刚需。

第二个坑是切分粒度切得太碎。一开始我按固定的小窗口切,结果很多答案被拦腰截断,生成器拿到的是没有上下文的残片,回答质量自然上不去。后来改成按语义段落切分,并保留段落标题作为前缀,配合粗一点的粒度,效果立竿见影。

第三个坑是top-k开得很大又不做重排。我图省事把检索结果前15个全部塞进上下文,结果模型老是被中段位置的无关片段带偏。后来严格跑了一遍重排,只留5个证据,并按相关度排序,端到端准确率不降反升,token成本还降了。这完全是Lost in the Middle那篇的结论在真实数据上的再验证。

第四个坑是评估只看最终答案,不看中间指标。有一阵我盯着准确率调了好几天都上不去,后来给检索层加了召回率日志,才发现问题一直在检索端。现在我的原则是:上线前必须给检索、重排、生成三层各设一个最低指标,哪层不达标就只改哪层,不要拿生成端的提示词去掩盖检索端的缺陷。

说回读三篇材料的整体收获。我最明显的变化是不再一上来就纠结模型选型,而是先拿张纸画组件图和数据流,把每个环节的输入输出定义清楚,把可观测性设计进去,再去选模型、调参数。我还养成了一个习惯:把Lost in the Middle的U形曲线图存在手机里,每次设计提示词和检索流程时都拿出来看一眼,提醒自己“模型最关注的是开头和结尾,把关键证据放在前面”。这个小习惯听起来简单,但对RAG系统的优化方向有持续而稳定的影响。建议你也试试:下次搭RAG之前,先拿一个问答对把你的语料塞进模型,把答案句的位置从头移到尾,亲眼看看准确率的变化,你就明白这三篇材料为什么被放到第一讲了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进