☰
RAG落地三层架构:从文档解析到应用编排的完整流水线
2026/10/17 4:21:11 网站建设 项目流程

很多人刚接触RAG的时候,第一反应通常是:这不就是把文档切碎、转成向量存进库,再把用户问题也转成向量,搜出最相近的几个片段丢给大模型吗?我第一次做Demo也是这么想的,而且demo确实跑得很快,一个下午就能在演示环境里对答如流。等到正式项目启动,我才发现demo和能用的系统之间隔着一整条流水线。数据一多、问题一变、文档格式一杂,原先乐高积木一样拼起来的“检索+生成”就开始到处漏风。

这篇文章不打算从RAG概念讲起,而是想聊聊真正把它落到业务里需要什么样的结构骨架。这几年的落地经验总结下来,核心就是一句话:RAG不是一个组件拼接游戏,而是一条三层架构流水线——数据接入层、索引检索层、应用编排层。每一层都有独立要解决的问题,也有各自的坑。但凡你跳过其中一层去“省事”,后面总会在某个意想不到的时刻连本带利地还回来。

1. 为什么多数RAG项目会死在“打样成功、上线翻车”这道坎上

1.1 拼乐高式的搭建,三处最常见的翻车点

第一宗罪,是把检索和生成当成两个独立的模块。很多人画架构图的时候,左边一个向量库,右边一个大模型,中间连一条线,就以为完事了。实际上这两块之间还隔着查询理解、候选召回、相关性重排、上下文组装一整串环节。任何一个环节没做,系统就会在某一类特定问题上突然“失智”。Demo阶段问题太少,暴露不出来;正式上线后用户问法千奇百怪,漏风的地方就全冒出来了。

第二宗罪,是忽视数据输入的质量控制。文档进来是什么样就什么样:PDF里字体丢了、表格拆碎了、段落切错了,后面全部环节做得再花哨,结果都是空中楼阁。我见过太多团队花了大力气调模型参数、优化提示词,最后发现bad case的根本原因,是PDF里的文字压根没被解析出来。数据层的债,一定要在数据层还,指望检索层和应用层去兜底,代价翻倍。

第三宗罪,是没有任何评测集就上线。系统准确率说不出来,全靠人工看三五个case拍脑袋。等真实用户问题涌进来,才发现原来那几个“精选case”掩盖了多少回归问题。没有评测集,你连“这次改动是变好了还是变坏了”都判断不了,那后续所有的优化都只能靠手感,项目迟早变成玄学。

这三个问题不是孤立的,它们共同指向同一个错误认知:RAG是一堆积木,搭起来就能跑。可实际它是一条强依赖的流水线,前端的每一个细节都会被后端逐级放大。

1.2 三层架构拆的是哪三层,为什么顺序不能乱

层级核心职责常见痛点
数据接入层文档解析、清洗、切分、元数据提取格式杂、切分边界乱、字段缺失
索引检索层向量化、构建索引、混合召回、重排序模型不匹配、召回偏、精排误杀
应用编排层查询改写、意图路由、上下文组装上下文堆太满、幻觉、无法溯源

这个顺序不是随便排的,它有严格的依赖关系:数据接入层出错,索引检索层拿到的就是错误输入;索引检索层本身质量不够,应用编排层再怎么优化提示词,也拿不到该拿的资料。每一层都在为下一层“兜底”,但真正的源头问题只能靠源头解决。

这就像一条生产流水线:原料清洗都不过关,后面的加工和包装做得再精美,成品也是不合格的。我见过一个项目,团队在应用层反复调提示词想提高答对率,折腾了两周没效果,后来查了半天,发现是切分策略把一个完整合同条款切到了三个chunk里,模型根本看不到完整信息。这种问题靠调提示词,永远调不出来。

2. 数据接入层:文档怎么进系统,决定了知识库的骨架

2.1 文档解析:PDF、表格、扫描件各有什么要命的点

数据接入是RAG系统里最不性感、但最要命的一层。很多团队把精力全放在模型选型上,却忽略了源文档本身的质量。说句难听的,知识库里20%的文档是乱码和错位的话,系统最终准确率的天花板大概就卡在80%,模型换得再贵也突破不了。

先拿PDF来说。文本型PDF看起来“能复制出文字”,但很多内嵌字体没有完整的字符映射,复制出来可能缺字或者乱码。这种问题在解析日志里不会报错,索引也照常构建,只有用户搜索具体内容时才发现抓瞎。扫描件更麻烦,OCR只是第一步,还需要版面分析。我处理过一批双栏排版的说明书,如果解析器按物理行读取,左右栏内容就会交错着混在一起,生成出来的文本段落逻辑完全是乱的。

表格也是重灾区。很多解析器会把三列表格按行流式输出,比如“张三 巡检员 1001 李四 司机 1002”,字段错位得一塌糊涂。检索“司机证件号”的时候,命中的chunk里全是莫名其妙拼起来的碎片,模型连谁是谁都分不清。除了这些,页眉页脚、水印、断行合并也都是常规问题,一页一个“第X页共X页”的页眉,会污染几十个chunk。

我的建议是,统一做一条清洗管线:去页眉页脚、合并断行、规范化全半角、把表格按语义结构重排,清洗之后的纯文本单独存一份留档。这步看起来很笨,但它是整个知识库骨架的地基。你后面每次想调索引调模型,都可以先问一句:这批语料干净了吗?

2.2 chunk切割策略:怎么切,很大程度决定了召回上限

为什么要切块?两个原因:一是向量表示模型有输入长度上限,二是大模型生成时希望拿到聚焦语境,而不是一整本百科砸过去。切多小合适呢?太短语义不完整,比如把一句“本合同自双方签字之日起生效”单独切出来,模型很难判断它在整个合同里是哪个角色条款;太长噪音太多,chunk里混了一堆无关描述,向量相似度会被稀释。

常见的切分策略有这么几种:固定窗口、按结构切分、语义切分、父子分块。它们各自的适用场景差别很大。

策略思路优点适用场景
固定窗口按字符数硬切,设置overlap实现最简单,稳定结构不规整的网页文本、聊天记录
按结构切分按标题、段落、列表边界切保持语义块完整结构清晰的制度文档、手册
语义切分用文本分割模型找语义边界切点落在自然语义处文档结构混乱、长短不均的语料
父子分块小chunk检索,大chunk给模型召回精准且上下文完整合同、论文、长报告等复杂文档

父子分块我认为是现阶段最值得优先试的方案,它解决的是一个“既要又要”的问题:用小chunk去向量库做召回,命中粒度细,避免无关内容混进来;命中之后不直接拿小chunk给大模型,而是返回它所在的父chunk——通常是一整节或一个完整章节——保证模型拿到的上下文是连贯的。

参数上我一般这么起步:子块300到500字,overlap设50到100字;父块放在整节或整小节级别,大概1000到2000字。overlap的作用是避免“句子前半在一个chunk、后半在另一个chunk”的情况,让相邻块在边界处共享信息。还有个容易忽略的点:chunk长度一定要小于向量模型的输入窗口,最好留出20%到30%的余量,否则长chunk会被截断,语义就缺了一块。

2.3 元数据:检索前先学会做减法

知识库用到一定程度,一定会遇到一个问题:全库检索太“泛”。用户问“去年安全事故报告”,如果不做任何限制,向量库在全量范围里搜,可能返回一堆规章制度、流程文档、培训材料,真正的报告反而排后面。这时候就该给文档打元数据标签了。

元数据是什么?就是文档的类型、来源部门、发布日期、所属业务线、文档编号这类结构化字段。在解析阶段顺带提取,回填到索引记录里。检索时先做过滤,再做相似度计算。这个步骤能让候选集从几十万条瞬间压到几百条,干扰项少了好几个量级,后续重排的准确率也跟着上去了。

我见过很多团队忽视这一步,觉得向量库能扛百万级数据不需要filter。但问题不是搜索性能扛不扛得住,而是语义噪声扛不扛得住。一个合同库和一个产品手册库混在一起,检索“赔偿条款”的时候,产品手册里的“赔偿免责说明”也会被召回,它们字面上确实相关,但业务语境完全不同。元数据过滤在某种意义上比模型调优更有效,因为它直接缩小了你要处理的问题范围。

3. 索引检索层:让“找到”这件事从可用变成好用

3.1 向量表示模型:选型、上下文窗口与领域适配

说完了数据接入,就到索引检索层。这一层里最先要考虑的是向量表示模型。选型主要看三件事:输入长度约束、输出维度、在你的领域语料上的实际效果。

输入长度和上一节的chunk切割直接相关,chunk再长也不能超过模型窗口;输出维度则影响存储成本,有的模型输出768维,有的输出1536维,后者索引体积基本是前者的两倍,但维度高不代表效果一定更好,很多时候在领域数据上一测便知。另外要注意模型版本管理,换模型必须全量重建索引,新旧向量不能混在一个索引空间里,否则两个版本的向量分布不一致,距离度量完全没有意义。

领域适配是另一个常被忽略的坑。通用向量模型在专业术语上有时会“脸盲”,比如医疗、法律、机械工程这种专业词汇密集的领域,通用模型可能把“心包积液”和“心包填塞”编码得不够有区分度。有条件就做领域微调,没条件可以先用“同义词扩展”过渡:检索前把问题里的专业术语扩展成文档里常出现的表述,让召回不至于因为字面不匹配而漏掉。

最后还有一个很低级但容易踩的坑:向量归一化。相似度如果用余弦距离的话,要保证索引库和查询向量都以同一规则归一化,否则打分排序会有系统性偏差。很多向量库在使用时默认做了处理,但不是所有实现都这样,写入前务必确认。

3.2 混合检索:为什么纯向量会漏掉精确命中的内容

向量检索擅长“语义相近但字面不同”的匹配,比如用户问“请病假要准备什么材料”,文档里写的是“疾病原因请假所需资料”。但反过来,产品型号、错误码、合同编号这类标识符内容,向量模型的表现往往非常离谱。因为“XM-208”这种字符串几乎没有语义,它的价值全在字面上,向量模型会把相似但不同的编号编码得纠缠不清,而BM25这种传统关键词检索反而能精确命中。

所以现在主流的做法是混合检索:向量召回加BM25召回,再融合排序。BM25也不是拿来就用,中文场景要做分词、停用词处理、全半角归一化。很多团队的BM25效果差,不是算法本身的问题,而是预处理没到位,大小写不统一、分词粒度不对,都会让关键词匹配时有时无。

融合方式我建议优先试RRF。它不看具体分数只看排名:把同一个文档在向量排序和BM25排序里的名次拿出来,分别求一个按名次衰减的得分,然后累加,最终按总分排序。常用公式是每个名次贡献1/(k+rank),k一般取60。这样不用调融合权重,也不会因为两个检索模块分数量纲不一致而让某一方主导。从这个角度看,RRF比线性加权更省心。

这也解释了一个设计逻辑:几乎所有向量库检索都在做近似最近邻的“高效粗筛”,它的定位就是快速圈定候选集。粗筛完依然有不少噪声,这个噪声由下一节的重排环节来处理。

3.3 重排序:最后一关的相关性校准

为什么向量粗召回之后还要重排?因为双塔模型把query和doc分别编码,两者之间没有交互,相关性的判断是“近似”的;而交叉编码器模型会把query和doc拼接在一起过一遍,让注意力机制在两者之间充分交互,精度高很多,代价是速度慢。所以在粗召回top50上做重排,取top5到8送进生成,是速度和精度之间最划算的折中。

重排环节里最重要的不是模型,而是阈值策略。不是所有chunk都该送进大模型,相关性分数低于阈值的,直接丢掉,宁可让系统回答“资料不足”,也不能把弱相关甚至不相关的片段塞给模型凑数。从实际经验看,很多幻觉问题不是大模型“乱编”,而是系统把一个低分但并非完全无关的chunk给了它,模型只能硬着头皮把它编成答案。

重排模型的选择也别盲信榜单,有的模型对长文本友好,有的对中文口语化表达更好,拿自己的业务数据做一组样本测一下,比看任何宣传都可靠。重排完之后还有一个容易被忽视的动作:上下文去重和排序。多个chunk可能内容重叠,或者高分chunk被埋在一堆低分chunk中间,这些都需要在组装前处理干净。

4. 应用编排层:RAG不止是把问题丢给大模型

4.1 查询改写:多轮对话里“它”和“那个”由谁来消解

真实用户不会像测试集那样把每句话都问得完整清楚。产品知识库里最常见的用户问法是:“那保修期呢?”如果直接把“保修期”这三个字拿去检索,召回结果五花八门。这时候就需要查询改写:把用户当前问题结合对话历史,补全成一句可检索的独立query。

改写有两种主流做法,一种基于规则,做代词指代消解和模板拼接;一种用大模型生成改写。两者我都试过,如果说有什么经验的话,那就是:改写结果只用于检索,回答仍然基于原始用户问题。否则大模型在改写的时候一旦发挥过了头,把核心实体改错,后面整个链路都白干了。更稳妥的做法是在改写时保留原问题中的关键词,只做补全而不是重写。

还有一个容易忽略的点:改写效果要有离线评估。不要觉得“改写了就肯定更好”,统计改写前后的召回命中率,有时候会发现改写反而把问题改成了一个更通用的版本,导致精确度下降。这种问题只能在数据上看出来。

4.2 意图路由:一个库该不该拆成多个知识域

当知识库里有制度库、产品手册库、报销FAQ库等多个业务域混在一起时,检索结果会频繁出现“张冠李戴”。用户问“报销需要走几步流程”,系统却把产品手册里“流程控制说明”也召回来,模型看到两份资料说法不一致,只好含糊其辞。

解决办法是先拆库,再路由。拆库按业务域分桶,每个桶单独建索引;检索前先判断用户问题属于哪个域,只检索命中的那一个或两个桶。路由可以是分类模型、规则关键词,也可以让一个小模型做判断,成本很低,收益却非常明显。

我见过一个真实项目,团队的知识库混了制度、产品、维修记录三个域,全库混搜的bad case率比路由后高出一倍还多。把库拆开之后,即使有些人觉得“检索链路变复杂了”,但答案质量提升是立竿见影的。路由这个动作,本质上是在给检索划定空间边界,这比单纯依赖向量相似度靠谱得多。

4.3 上下文组装:塞满上下文不等于答得好

很多人的习惯是:检索到前5个chunk,全塞给大模型。这个习惯在早期demo阶段好像没问题,但实际使用时会发现,检索到的第4、第5个chunk往往已经是弱相关甚至不相关的内容。这些内容混进去之后,大模型不会说“我不确定”,它会努力把这些无关信息也编织进答案里,谎言就这么来了。

更合理的做法是动态控制上下文规模:简单问题放2到3个高分chunk就够,开放式、需要综合比较的问题可以放到5到8个。同时在系统提示词里明确一条规则:只能基于提供的资料回答,资料不足以支撑问题就直接说“资料不足”。这看起来是限制,实际上是给模型减少发挥空间,副作用是幻觉率明显下降。

另外还有一个很值得注意的细节:当上下文序列很长时,模型对中间位置信息的注意力会变弱。把最高分的chunk放在开头附近,比随机排列或按分数倒序排在末尾效果都好。上下文组装不是简单的拼接,它也需要有编排逻辑。

4.4 可观测性:每个bad case都要能定位到责任层

系统上线三个月,效果下降,用户反馈“以前能搜到的内容现在搜不到”,这时候没有日志,排查起来基本靠猜。所以从项目第一天起就要建链路日志。每条query记录:原始问题、改写结果、路由结果、粗召回top20及分数、重排top5及分数、最终送进模型的chunk列表、模型输出、用户反馈。

有了这份日志,分析bad case时就可以按层切分责任:是改写出错把实体改丢了,还是召回到位但精排误杀,抑或是命中了但模型没按材料答。我曾经处理过一个case,用户问“某型号的安装高度”,日志显示粗召回里根本没有这个型号——问题就出在解析阶段,这个型号在PDF里被拆成了两行,索引里压根没有这个完整词。没有日志的话,这种问题可以让整个团队排查一星期。

可观测性建设在项目初期常被认为是“多余的工作量”,但它其实是系统上线后唯一能让你持续优化的抓手。RAG的调优是迭代式的,没有观测数据,每一次改动都是盲改。

5. 一次线上故障的完整排查链路:当召回突然坏掉时

5.1 现象与第一轮排查假设

某团队的知识库系统上线三个月后,突然有客服人员反馈:“产品手册里搜不到某个型号了,以前是可以的。”后台数据显示,整体回答满意度从87%降到了74%。这是一个典型的“效果悄悄变差”问题,没有报错,也没有明显的功能故障。

第一轮排查通常会看基础设施:向量库服务是不是挂了?嵌入模型是不是被升级过?基础链路是不是有延迟?但这些检查全部正常,服务状态也稳定。这时候如果链路日志没做好,排查就卡在这里了。好在该团队从第一天起就把每条query的检索过程记录下来,顺着日志往下看,很快就发现了端倪。

5.2 逐层定位:先从检索日志再往数据层走

链路日志显示,用户提到的那个型号在粗召回阶段其实被BM25命中了,但精排的分数全部低于阈值,被0.5的线拦在了上下文之外。为什么精排分数那么低?把这些chunk的原文拉出来一看就明白了:文本全是模板化的重复描述,具体的参数表格消失了。再往数据层查,发现上周定时任务把所有产品文档重新解析了一遍,解析配置文件里“保留版面结构”的开关被新版本默认覆盖成了“流式文本”,导致参数表被拆成碎片。

同时,切分策略也被覆盖成了固定窗口,原来的语义切分加父子分块全部失效。“型号标题块”和“参数说明块”被硬生生拆开,召回模型匹配到了标题块,但标题块里没有具体参数;参数块里又因为没有型号关键词,BM25也兜不住。这一串连锁反应,就是一条不起眼的配置变更顺着数据管线和检索链路逐级放大的典型过程。

5.3 修复方案、回归验证与监控补位

修复本身不复杂:回滚解析配置,恢复语义切分加父子分块的策略,全量重建索引。重建索引大概花了一个多小时,重建完单独验证这个型号,能正常召回和回答了。但光修复还不够,还要跑了一遍评测集回归,确认不只是这一个型号恢复了,其他类别文档也没有产生新的回归问题。

这次故障最大的教训是监控缺位。解析任务的输出结果没有任何校验,所以数据管线已经坏了一周都没人知道。之后他们加了一项自动化检查:解析任务结束后自动统计chunk数量、平均长度、表格结构保留情况,一旦chunk数量波动超过阈值就直接告警。这种数据管线层面的质量检查,比等用户反馈要早得多,也便宜得多。

6. 落地一年后的几点体会:评测集、回流与场景取舍

6.1 评测集越早建越好,它是你的质量护城河

没有评测集,后面所有的优化决策都像闭着眼睛开车。建议从项目一开始就收集100条左右的真实用户问题,标注好期望答案或期望来源文档。每次改动解析、切分、索引、模型、提示词,都跑一遍评测集,看看整体指标的升降方向。只有这样,你才能发现“这次修复解决了三个case,但弄坏了另外两个”,而不是被单点成功蒙住眼睛。

6.2 不是所有问题都适合塞给RAG

运行时间长了你会发现,有些高频问题压根不该走RAG。查状态、算费用、查编号这类标准化问题,直接查业务库、走规则引擎更稳定,延迟更低,也不会出现检索不到或推理出错的情况。RAG擅长的是“答案分散在多份文档里、表述不固定、需要综合归纳”的开放式问题。把场景选准了,系统的稳定性和成本都会健康很多。

6.3 上线只是开始:数据回流与持续调优

用户的行为反馈本身就是信号:点踩、点开引用、追问、复制答案,这些动作都在告诉你答案质量到底如何。定期把未命中词、低分case、用户追问集中出现的问题拉出来,一部分是文档缺失,需要补资料;一部分是改写不够聪明,需要调策略。RAG系统本质上是一条不断进化的流水线,上线那一刻不是终点,而是数据回流调整的起点。

如果有人再拿“拼乐高”来形容RAG,我会建议他先打开知识库底层那批PDF看看。能把那些乱七八糟的文档稳稳当当变成干净、可检索、可追溯的语料,才算真正摸到了这条流水线的门把手。

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

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

立即咨询