RAG 这条赛道,从 2023 年火到现在,已经过了"搭个 LangChain 加个向量库就能 demo"的阶段了。现在真正在做自研 RAG 的团队,普遍卡在同一个地方:开源方案跑起来很快,但一旦要接真实业务、要控成本、要保证召回质量、要处理权限和增量更新,就发现到处是坑,改到最后代码里一半是开源框架的胶水,一半是自己的补丁,维护成本高得离谱。我过去一年多陆续拆过六款开源 RAG 产品,从轻量级的本地知识库工具到带 Agent 编排的完整框架都有,目的不是直接用它们,而是想搞清楚一件事:这些产品在架构上到底做了哪些取舍,哪些设计是可以直接抄进自研系统的,哪些是它们为了通用性不得不背的包袱。这篇就把这套逆向工程的结果摊开讲,从需求拆解、架构分层、检索链路、增量与权限、评测这几个维度,给出一套可复用的自研蓝图。不管你是刚开始调研 RAG 架构,还是已经自研了一版但觉得哪里不对劲,应该都能从里面找到能直接落地的思路。
1. 先想清楚自研 RAG 到底要解决什么,而不是先选框架
很多人一上来就问"用哪个开源框架好",这其实是把顺序搞反了。开源 RAG 产品的架构,本质上是它们对"通用场景"的一组假设,而你的业务大概率不满足这些假设。所以逆向工程的第一步,不是看它们怎么实现,而是先明确自研要解决的核心矛盾是什么。
1.1 开源 RAG 的通用性假设,哪些在真实业务里会失效
我拆的这六款产品,覆盖了从纯本地文档问答到带多轮 Agent 编排的不同形态。它们有一个共同点:默认知识库是"静态、公开、无权限、以文档为主"的。这个假设在 demo 阶段没问题,但真实业务里几乎每一条都会失效。
- 静态假设失效:业务文档每天都在变,开源方案大多靠全量重建索引,几千篇文档还行,上到几十万篇就是灾难。
- 公开假设失效:企业知识库有部门、角色、密级,检索时必须做权限过滤,而多数开源方案的向量检索是"先召回再过滤",权限一加召回率直接崩。
- 无权限假设失效:同上,权限不是加个 where 条件那么简单,它会影响索引结构的设计。
- 文档为主假设失效:真实知识库里有表格、图片、代码、FAQ、工单记录,纯文本切分根本处理不了。
把这四条列出来,自研 RAG 的需求边界就清晰了:你要解决的不是"能不能检索到",而是"在动态、有权限、多模态、大规模的前提下,还能稳定检索到对的片段"。这个认知决定了后面所有架构选择。
1.2 从"能跑"到"能扛":自研 RAG 的四条硬指标
逆向工程时我给自己定了一个判断标准,一款开源产品的设计值不值得抄,就看它有没有认真处理这四件事:
| 硬指标 | 开源方案常见做法 | 自研必须达到的状态 |
|---|---|---|
| 召回质量 | 单路向量召回 | 多路召回 + 重排,可解释 |
| 增量更新 | 全量重建 | 文档级增量,秒级生效 |
| 权限控制 | 召回后过滤 | 索引层权限隔离,召回即合规 |
| 可评测 | 靠人工看 | 有离线评测集,指标可回归 |
这四条不是并列关系,而是有优先级的。召回质量是底线,增量更新决定运维成本,权限决定能不能上生产,评测决定你能不能持续优化。我见过太多团队把精力全砸在换 embedding 模型上,结果增量更新一塌糊涂,每次更新停服半小时,这种系统上线就是给自己找罪受。
1.3 逆向工程的正确姿势:拆架构而不是抄代码
这里要强调一个方法论。拆开源产品,重点不是读它的每一行代码,而是回答三个问题:它的数据流是怎么走的、它的关键抽象是什么、它在哪个环节做了妥协。代码会过时,但架构决策背后的权衡逻辑是长期有效的。
我一般会画一张数据流图(这里用文字描述):文档进来 → 解析 → 切分 → 向量化 → 入索引 → 查询改写 → 多路召回 → 融合重排 → 上下文组装 → 生成。然后逐个环节问:这一步开源是怎么做的,为什么这么做,如果换成我的场景会怎样。这套方法拆完六款产品,你会发现它们的差异其实集中在三四个关键节点上,其余部分大同小异。抓住这几个节点,自研蓝图就成型了。
2. 六款开源 RAG 产品的架构分层对比
拆完六款产品后,我把它们的架构抽象成五层:接入层、解析切分层、索引存储层、检索编排层、生成层。不同产品的差异,基本都能映射到这五层的某一层上。下面按层来讲,重点讲每层的设计取舍。
2.1 接入层与解析层:文档进得来,才谈得上检索
接入层看着简单,其实是最容易被低估的一层。开源产品里,轻量级的通常只支持文件上传,重一点的会支持网页抓取、API 同步、数据库直连。这里的关键决策是:知识来源是"推"还是"拉"。
- 推模式:业务系统主动把文档推给 RAG,适合文档变更频繁、需要实时性的场景。
- 拉模式:RAG 定时去数据源同步,适合数据源稳定、对实时性要求不高的场景。
我自己的选择是混合:核心知识库用推模式保证实时,辅助资料用拉模式定时同步。这个决策直接影响后面的增量更新设计。
解析层是坑最多的地方。PDF 解析就是典型,开源方案大多用 PyPDF 或 pdfplumber,遇到双栏排版、表格、扫描件就歇菜。我实测下来,纯文本 PDF 用 pdfplumber 够用,带表格的必须上专门的表格识别,扫描件得走 OCR。这里没有银弹,务实做法是按文档类型分流:普通文本走轻量解析,复杂版式走重型解析,扫描件单独走 OCR 队列。这个分流逻辑,六款产品里只有两款做了,其余都是"一把梭",结果就是复杂文档解析质量惨不忍睹。
2.2 切分策略:固定窗口是最省事也最坑的选择
切分是 RAG 里最被轻视、但影响最大的环节。开源产品默认基本都是固定窗口切分,比如 512 token 一段,重叠 50 token。这个策略实现简单,但对语义完整性是毁灭性的——一个完整的论述被从中间切断,检索出来半截话,生成质量自然差。
我拆的产品里,做得好的会引入语义切分:先按段落、标题等结构切,再对超长段落做语义相似度切分,保证每段是一个相对完整的语义单元。还有的会做父子块设计:检索用小块保证精度,生成时返回父块保证上下文完整。这个父子块设计我认为是自研必须抄的一个点,它用很小的存储代价换来了召回精度和上下文完整性的平衡。
具体参数上,我的经验是:中文文档块大小控制在 300 到 500 字,重叠 10% 到 15%;英文按 token 算,256 到 512 比较稳。但这个不是死的,要结合你的 embedding 模型的最优输入长度来调。有些模型在 256 token 上表现最好,你硬塞 512 反而稀释了语义。
2.3 索引与存储:向量库不是唯一答案
一提到 RAG 索引,大家第一反应就是向量库。但拆完这几款产品我发现,纯向量检索在真实业务里几乎不够用。原因很简单:向量擅长语义相似,但对精确匹配、关键词、结构化过滤很弱。用户搜一个具体的订单号或者错误码,向量检索经常召回一堆"语义相近"但完全不对的内容。
成熟的开源方案会做混合索引:向量索引 + 全文索引(BM25 之类)+ 结构化字段索引。查询时多路并行召回,再融合。这个设计是自研的必选项。存储选型上,我的建议是别一上来就上重型分布式向量库,除非你的数据量真的到了千万级。中小规模用支持混合检索的轻量方案完全够,运维成本低得多。
这里有个容易忽略的点:元数据的设计要和权限、增量更新一起考虑。比如每段要带文档 ID、版本号、部门标签、密级、更新时间。这些字段不是装饰,它们是后面权限过滤和增量更新的基础。我见过自研系统上线后想加权限,发现元数据里根本没存部门信息,只能全量重建,代价极大。
2.4 检索编排层:多路召回加融合重排是分水岭
如果说前面几层决定 RAG 的下限,检索编排层就决定上限。这也是六款产品差距最大的一层。
基础款:单路向量召回,top-k 直接喂给模型。 进阶款:向量 + 关键词双路召回,用 RRF(倒数排名融合)合并。 高级款:多路召回 + 交叉编码器重排 + 查询改写。
我的判断是,双路召回加 RRF 融合是自研的及格线,交叉编码器重排是拉开差距的关键。重排模型虽然增加延迟,但它对 top-k 的精度提升非常明显,尤其是当召回结果里有大量语义相近但实际不相关的内容时。实测下来,加一层重排,最终上下文的相关性能提升一大截,这个投入产出比很高。
查询改写也值得单独说。用户的问题往往口语化、有指代、缺上下文,直接拿去检索效果很差。好的方案会做查询扩展(同义改写)、查询分解(多跳问题拆成子问题)、指代消解(把"它"还原成具体实体)。这几步做不做,直接决定复杂问题的回答质量。
2.5 生成层:上下文组装比模型选择更重要
很多人把精力花在选哪个大模型上,但拆完产品我发现,上下文组装的质量对最终回答的影响,往往比换模型更大。同样一个模型,喂给它干净、相关、结构清晰的上下文,和喂给它一堆噪声,输出质量天差地别。
上下文组装要解决几个问题:召回片段怎么排序(相关的放前面还是后面)、怎么去重、怎么控制总长度不超模型窗口、怎么标注来源。我比较推荐的做法是:重排后的片段按相关性排序,相邻片段做去重合并,总长度留出 20% 余量给模型输出,每个片段带上来源标识方便溯源。这些细节看着琐碎,但它们是产品能不能用的关键。
3. 从开源产品里逆向出的四个关键设计决策
前面按层讲了架构,这一节把最值得抄的四个设计单独拎出来讲透。这四个决策,是我拆完六款产品后认为对自研价值最高的。
3.1 父子块索引:用小块检索,用大块生成
这是我认为最值得抄的一个设计。传统做法是切多大块就用多大块检索和生成,但这里有个矛盾:块小,检索精度高但上下文不完整;块大,上下文完整但检索精度下降(语义被稀释)。
父子块的做法是解耦这两个需求:索引和检索用小块(比如 200 字),但每个小块记录它所属的父块 ID;检索命中小块后,返回的是父块(比如 1000 字)给生成层。这样检索精度和上下文完整性兼得。
实现上要注意两点:一是父块和子块的映射关系要存好,二是去重逻辑要处理——多个子块可能属于同一个父块,返回时要合并。这个设计存储开销增加不多,但效果提升明显,属于典型的"小改动大收益"。
3.2 索引层权限隔离:召回即合规
权限这块,开源方案普遍做得粗糙,大多是召回后再过滤。问题是,如果召回 top-100 里有 80 条是用户无权访问的,过滤完只剩 20 条,召回质量直接崩,而且还有信息泄露风险(过滤前的中间结果如果被日志记录)。
正确做法是把权限下沉到索引层。具体有两种实现:一是按权限维度做索引分区,查询时只在用户有权的分区里检索;二是在向量检索时带上权限过滤条件,让检索本身就在合规范围内进行。前者适合权限结构清晰的场景,后者更灵活但依赖向量库对过滤的支持。
我倾向第一种,因为它把权限做成了物理隔离,性能和安全性都更好。代价是权限变更时需要重建对应分区,所以权限模型要设计得相对稳定,别天天变。
3.3 文档级增量更新:别动不动就全量重建
增量更新是运维的生命线。开源方案里,做得好的会做文档级增量:文档变了,只重新解析、切分、向量化这一个文档,然后更新索引里对应的片段。做得差的直接全量重建,数据量一大就没法用。
文档级增量的关键是版本管理。每个文档要有版本号,每次更新生成新版本,索引里同时保留新旧版本的映射,查询时只查最新版本。删除文档时,不是物理删除,而是标记失效,后台异步清理。这样既保证了更新的实时性,又避免了更新过程中的查询不一致。
再进一步可以做片段级增量:只重新处理文档里变化的片段。但这个实现复杂度高,除非文档变更非常频繁,否则文档级增量已经够用。
3.4 离线评测闭环:没有评测就没有优化
这是最容易被忽略、但长期价值最高的一环。没有评测集,你换 embedding 模型、调切分参数、加重排,全靠感觉,根本不知道是变好了还是变差了。
评测闭环要包含三样东西:评测集、评测指标、回归流程。评测集是一批"问题 + 标准答案 + 标准相关文档"的样本,覆盖你的典型查询场景。指标上,检索环节看召回率、MRR、NDCG,生成环节看答案相关性、忠实度(有没有胡编)。回归流程是每次改动后自动跑一遍评测集,指标不降才能上线。
我自己的做法是,评测集不用一开始就很大,先攒 50 到 100 条高质量样本,覆盖主要场景,然后随着线上 badcase 不断补充。这个投入前期看着慢,但它是你后面所有优化的地基。
4. 自研 RAG 蓝图的落地路径与踩坑记录
讲完设计,这一节讲怎么落地。我会按一个从零到一的顺序,把关键步骤和踩过的坑讲清楚。
4.1 最小可用版本:先跑通链路,别追求完美
第一步千万别想着一步到位。我的建议是先搭一个最小可用版本:单路向量召回 + 固定切分 + 简单生成,把整条链路跑通。这个版本可能效果一般,但它让你对数据流、延迟、成本有了真实感知。
这个阶段最容易犯的错是过度设计。我见过有团队一上来就搞多路召回加重排加 Agent,结果链路太长,出了问题根本不知道是哪一环。先跑通单路,再逐步加复杂度,每一步都能定位问题。
4.2 逐步增强:按收益排序的优化顺序
跑通最小版本后,优化要有优先级。我总结的顺序是:
- 切分优化:从固定切分升级到语义切分加父子块,这个改动收益最大、成本最低。
- 混合召回:加一路关键词召回,用 RRF 融合,解决精确匹配问题。
- 重排:加交叉编码器重排,提升 top-k 精度。
- 查询改写:处理口语化、多跳、指代问题。
- 权限与增量:上生产前必须补齐。
这个顺序的逻辑是:先解决"检索到的东西对不对",再解决"复杂问题能不能处理",最后解决"能不能上生产"。别跳步,每一步都要用评测集验证。
4.3 踩坑记录:那些文档里不会写的教训
讲几个我实际踩过的坑,都是常规文档里不会提的。
坑一:embedding 模型换了,索引必须重建。这个看似常识,但很多人忘了。不同模型的向量空间不兼容,混用会导致检索结果完全错乱。所以选模型要慎重,一旦定了,换的成本很高。
坑二:切分重叠不是越大越好。重叠太大,索引膨胀,检索时大量重复片段挤占 top-k。我实测 10% 到 15% 的重叠比较合适,超过 20% 就开始有负面影响。
坑三:重排模型的延迟要算进总预算。交叉编码器重排效果好,但它对每个候选都要跑一次模型,候选多的时候延迟很可观。我的做法是先召回 50 条,重排后取 top-5,在效果和延迟之间找平衡。
坑四:上下文不是塞得越满越好。模型窗口有限,塞太多无关内容反而干扰生成。我一般控制在窗口的 60% 到 70%,留出余量,效果比塞满更好。
坑五:评测集要防泄漏。如果你的评测集问题是从知识库里直接摘的,那评测结果会虚高。评测集的问题要重新表述,模拟真实用户的问法。
4.4 成本与延迟的平衡:自研绕不开的现实问题
自研 RAG 绕不开成本和延迟。向量化、检索、重排、生成,每一步都要花钱花时间。我的经验是:
- 向量化:文档入库时一次性成本,可以接受;查询时的向量化成本很低。
- 检索:向量检索本身很快,混合召回和重排是延迟大头。
- 生成:最贵也最慢,所以要控制上下文长度和调用次数。
优化思路上,能缓存的都缓存(比如高频查询的结果),能异步的都异步(比如文档入库),能批量的都批量(比如向量化)。这些工程手段对成本的影响,往往比换模型更直接。
5. 自研与开源的边界:哪些该自己写,哪些该用现成的
最后聊一个务实的问题:自研不等于什么都自己写。合理的边界是,核心链路的可控部分自己写,通用能力用成熟方案。
5.1 值得自研的部分:决定产品差异化的环节
这几块我建议自己写:切分策略、检索编排、权限模型、评测体系。原因是它们和你的业务强相关,开源方案给不了你想要的定制,而且它们是产品差异化的来源。尤其是检索编排和权限,直接决定你的 RAG 能不能上生产。
5.2 可以复用的部分:通用能力别重复造轮子
这几块用现成的就行:向量库、embedding 模型、重排模型、大模型。这些是通用能力,开源和商业方案都很成熟,自己写纯属浪费。选型上关注稳定性、成本、和你的技术栈的契合度即可。
5.3 一个务实的自研比例参考
我的经验是,一个健康的自研 RAG 系统,自己写的代码大概占 30% 到 40%,其余是集成成熟组件。这个比例不是绝对的,但它反映了一个原则:自研的价值在于编排和定制,不在于重造基础组件。把精力集中在决定效果的环节上,通用能力交给专业方案,这才是自研 RAG 的正确打开方式。
拆完这六款产品,我最大的体会是,开源 RAG 的价值不在于直接用,而在于它帮你把坑都踩了一遍,你站在它的肩膀上,能更快看清哪些设计是本质的、哪些是权宜的。自研的意义,就是把这些本质设计拿过来,结合自己的业务重新组装。这个过程没有终点,因为业务在变、模型在变、用户预期也在变,但只要你的架构分层清晰、评测闭环健全,每一次变化都只是局部调整,而不是推倒重来。