1. 为什么我要去逆向六款开源RAG产品去年下半年我接手了一个企业知识库的项目客户的要求很直接把散落在各个业务系统里的文档、工单、会议纪要整合起来做一个能问答的内部助手。当时我的第一反应是上开源方案毕竟RAG这个概念已经火了一年多GitHub上star过万的项目一抓一大把随便挑一个改改应该就能交差。结果真动手了才发现事情远没有想象中简单。我前后试了六款不同定位的开源RAG产品有的是全流程框架有的是轻量级检索库有的是带界面的知识库应用。每一款跑起来都能用但一旦往生产环境推问题就集中爆发检索召回率忽高忽低、文档切分后语义断裂、多轮对话里上下文丢失、图片和表格内容完全检索不到。更让人头疼的是这些开源项目的架构设计思路差异极大有的把检索逻辑写死在链路里有的把向量库和业务逻辑耦合在一起想改一处往往牵动全身。那段时间我几乎每天都在读别人的源码一边读一边在笔记本上画架构图。慢慢地我发现这些开源产品虽然各有各的问题但它们在架构设计上其实沉淀了不少值得借鉴的思路。有的在检索链路上做了多路召回有的在文档解析阶段就做了结构化处理有的把知识库的存储层抽象得非常干净。这些设计决策背后都是真实业务场景打磨出来的经验。于是我做了一个决定不再纠结于选哪一款开源产品直接用而是把这六款产品的架构拆开来看搞清楚它们各自解决了什么问题、在哪些地方做了妥协、哪些设计可以复用。这个过程持续了大概两个月最终我提炼出了一套适合自研RAG的架构蓝图。这篇文章就是把这套逆向工程的思路和结论完整地分享出来适合正在做RAG选型、或者准备自研RAG系统的朋友参考。不管你是刚接触RAG的新手还是已经踩过一些坑的老手应该都能从中找到对自己有用的东西。2. 六款开源RAG产品的架构拆解与定位差异2.1 全流程框架型LangChain系产品的链路设计LangChain应该是大多数人接触RAG的第一个入口。它的核心思路是把RAG拆成一条可编排的链路文档加载、文本切分、向量化、存储、检索、生成每个环节都是一个独立的组件通过Chain或者LCEL把它们串起来。这种设计的好处是灵活你可以自由替换任何一个环节的实现比如把默认的文本切分器换成按语义切分的或者把向量库从FAISS换成Milvus。但实际用下来这种灵活性的代价是链路复杂度高。一个典型的RAG Chain可能涉及十几个组件的嵌套调试的时候很难快速定位问题出在哪一环。而且LangChain的抽象层次比较多有时候为了改一个小逻辑需要理解好几层封装。我在项目里就遇到过检索结果明明没问题、但生成阶段总是丢上下文的情况最后排查发现是Prompt模板里的变量替换顺序出了问题。从架构角度看LangChain系产品最大的价值在于它定义了一套RAG的组件标准。你可以不直接用它的实现但可以参照它的组件划分来设计自己的系统。比如它把Retriever抽象成一个独立接口这个思路就非常值得借鉴——检索逻辑和生成逻辑解耦之后你可以针对不同的知识库类型配置不同的检索策略。2.2 轻量级检索库型专注召回环节的极致优化第二类产品走的是另一条路不做全流程只专注把检索这一件事做到极致。这类产品的典型代表是一些专门做向量检索的库它们不关心你怎么切文档、怎么调模型只提供高性能的向量索引和相似度计算能力。我重点研究了其中一款它的核心设计是把向量索引和元数据过滤做了深度整合。传统的做法是先做向量检索再过滤元数据或者先过滤再检索但这两种方式在数据量大、过滤条件复杂的时候性能都会急剧下降。这款产品采用了一种混合索引结构把标量字段和向量字段放在同一个索引里查询的时候可以同时做向量相似度和标量条件的联合优化。这个设计对我的启发很大。在企业知识库场景里元数据过滤几乎是刚需——用户往往只想在某个部门、某个时间段、某类文档里检索。如果检索层不支持高效的联合查询上层再怎么优化都是白搭。后来我在自研系统里就借鉴了这个思路在向量库之上加了一层元数据索引把常用的过滤字段单独建索引检索的时候先做元数据粗筛再做向量精排整体响应时间从秒级降到了百毫秒级。2.3 知识库应用型开箱即用背后的架构取舍第三类产品是带完整界面的知识库应用用户上传文档就能用不需要写代码。这类产品看起来最省事但架构上的取舍也最明显。为了做到开箱即用它们通常会把文档解析、切分、向量化、检索、生成全部封装在一个服务里对外只暴露上传和问答两个接口。这种设计的问题在于可扩展性差。我试过在其中一款产品里接入自定义的Embedding模型结果发现模型配置写死在代码里只能通过改源码的方式替换。另一个问题是文档处理链路不透明上传一个PDF之后你很难知道它到底是怎么切分的、切成了多少块、每块的内容是什么。出了问题只能看日志而日志信息又非常有限。不过这类产品也有值得学习的地方。它们在文档解析阶段做了很多工程化的处理比如自动识别PDF里的表格和图片、对扫描件做OCR、对超长文档做分层切分。这些处理虽然不一定完美但至少提供了一个可用的基线。我在自研系统里就参考了它们的文档预处理流程把解析、清洗、切分、元数据提取做成了独立的Pipeline每个阶段都可以单独替换和调试。2.4 六款产品的横向对比与共性提炼把这六款产品放在一起对比会发现一些很有意思的共性。首先几乎所有产品都在文档切分环节做了特殊处理没有一个是简单按固定长度切的。有的按段落切有的按语义相似度切有的用递归切分再合并。这说明文档切分对RAG效果的影响远比想象中大固定长度切分虽然简单但会破坏语义完整性导致检索出来的片段答非所问。其次多路召回正在成为标配。单一向量检索的召回率在很多场景下都不够用尤其是当用户查询包含关键词、实体名、专业术语的时候。我测试过纯向量检索在专业术语查询上的召回率只有60%左右加上关键词召回之后能提升到85%以上。这个提升幅度足以决定一个RAG系统能不能用。第三几乎所有产品都在检索之后加了重排序环节。向量检索返回的Top-K结果里真正相关的可能只有前几个后面的噪声很大。重排序模型可以对候选结果做精细打分把真正相关的内容排到前面。这个环节虽然增加了延迟但对最终答案质量的提升非常明显。产品类型核心优势主要短板可复用设计全流程框架型组件丰富、灵活可编排链路复杂、调试困难组件接口抽象、检索生成解耦轻量级检索库型检索性能极致、索引设计精妙功能单一、需要自行组装混合索引、元数据联合查询知识库应用型开箱即用、文档处理完善可扩展性差、链路不透明文档预处理Pipeline、分层切分3. 从逆向工程中提炼的自研RAG核心架构3.1 分层架构设计把检索和生成彻底解耦逆向完六款产品之后我最大的收获是自研RAG一定要做分层架构而且检索层和生成层必须彻底解耦。很多开源产品把检索和生成耦合在一起导致你想换一个Embedding模型或者换一个LLM都要动整个链路。正确的做法是把系统拆成四层文档处理层、索引存储层、检索服务层、生成服务层。文档处理层负责把各种格式的文档解析成纯文本同时提取元数据。这一层的输出应该是结构化的文档块每个块包含内容、来源、页码、章节标题等元信息。索引存储层负责把文档块向量化并存入向量库同时建立元数据索引。检索服务层对外提供统一的检索接口内部可以组合多种召回策略。生成服务层负责把检索结果和用户问题组装成Prompt调用LLM生成答案。这样分层之后每一层都可以独立演进。比如你想把Embedding模型从OpenAI换成BGE只需要改索引存储层的向量化逻辑重新索引一遍数据就行检索服务和生成服务完全不用动。再比如你想加一种新的召回策略只需要在检索服务层加一个召回器不影响其他层。3.2 文档处理Pipeline切分策略决定检索上限文档切分是RAG系统里最容易被低估的环节。我见过太多项目直接用固定长度切分结果检索出来的片段要么缺头少尾要么把不相关的内容拼在一起。从逆向工程中我总结出一套分层切分策略实测效果比固定长度切分好很多。第一层是按文档结构切分。PDF、Word、Markdown这些格式本身就有章节、段落、列表的结构信息解析的时候要尽量保留这些结构。比如一个PDF里的二级标题应该作为其下所有段落的元数据检索的时候可以用标题做过滤或加权。第二层是按语义相似度切分。对于没有明显结构的长文本可以用滑动窗口计算相邻句子的语义相似度在相似度骤降的地方切分。这样切出来的块语义更完整不会出现一个块里混了两个不相关话题的情况。第三层是重叠切分。不管用哪种切分方式相邻块之间都要保留一定的重叠内容通常是块大小的10%到20%。这样做的目的是防止关键信息刚好落在切分边界上导致检索时丢失上下文。注意切分块的大小不是越小越好。块太小会导致检索结果碎片化LLM拿到一堆不完整的片段反而容易产生幻觉。我的经验是中文文档每块300到500字比较合适英文文档每块200到300词。3.3 多路召回与重排序把召回率从60%拉到90%单一向量召回的问题在专业领域特别明显。我做过一个测试用纯向量检索去查“ISO 27001认证流程”相关的文档Top-10里只有4条是真正相关的。换成关键词召回Top-10里有7条相关。把两种召回结果合并再去重Top-10里有9条相关。这个实验说明多路召回不是锦上添花而是刚需。我的自研系统里目前配置了三路召回向量召回、关键词召回、元数据召回。向量召回负责语义匹配关键词召回负责精确匹配元数据召回负责按部门、时间、文档类型等条件过滤。三路召回的结果合并之后用一个轻量级的重排序模型做精排最终返回Top-K给生成层。重排序模型的选择也有讲究。我试过用Cross-Encoder做重排序效果确实好但延迟太高单次查询要几百毫秒。后来换成用LLM做相关性打分延迟降到了100毫秒以内效果只比Cross-Encoder差一点点。对于大多数企业知识库场景这个 trade-off 是完全可以接受的。3.4 知识库存储的元数据设计让检索有的放矢元数据设计是自研RAG和开源产品拉开差距的关键点。开源产品通常只存文档内容和向量元数据字段很少。但在企业场景里元数据决定了检索的精度和效率。我的做法是在文档处理阶段就提取尽可能丰富的元数据包括文档来源、创建时间、修改时间、作者、部门、文档类型、章节标题、关键词、实体列表。这些元数据不仅用于过滤还可以用于加权。比如用户问的是“最近三个月的销售政策”检索的时候就可以给近期文档更高的权重。元数据的存储也有讲究。我试过把元数据全部塞进向量库的payload里查询的时候用向量库自带的过滤功能。这种方式在数据量小的时候没问题但数据量上到十万级之后过滤性能下降很明显。后来改成把元数据单独存一份到关系型数据库检索的时候先在数据库里做粗筛拿到候选文档ID再去向量库做精排。这样虽然多了一次查询但整体性能反而更好。4. 自研RAG落地过程中踩过的坑与解决方案4.1 检索结果相关性差从查询改写开始排查自研系统上线第一个月用户反馈最多的问题就是“答非所问”。我一开始以为是检索策略的问题调了召回数量、换了重排序模型效果都不明显。后来把用户的实际查询拿出来分析发现问题的根源在查询本身。用户的提问往往很口语化比如“我们公司去年那个关于报销的新规定是啥来着”。这种查询直接拿去做向量检索效果肯定差。正确的做法是在检索之前加一步查询改写把口语化的问题改写成适合检索的形式。我用了两种改写策略一种是基于规则的把“去年”“最近”这类时间词替换成具体日期范围另一种是基于LLM的让模型把问题改写成多个检索友好的查询。查询改写之后检索召回率有了明显提升。但新的问题又来了改写后的查询可能偏离用户原意。我的解决办法是保留原始查询和改写查询两路召回合并结果后再做重排序。这样既保证了召回率又不会因为改写错误而丢失相关信息。4.2 多轮对话上下文丢失会话状态管理方案多轮对话是RAG系统里另一个容易踩坑的地方。用户问了一个问题系统回答了用户接着追问“那第二条呢”这时候如果系统不理解“第二条”指的是什么就会答非所问。我试过几种方案。最简单的是把历史对话全部拼进Prompt但这样会导致Prompt过长而且早期的不相关内容会干扰当前问题。后来改成只保留最近三轮对话并且对历史对话做摘要压缩。再后来发现更好的做法是把历史对话里的关键实体和意图提取出来作为当前查询的上下文补充。具体实现上我在会话状态里维护了一个实体列表和一个意图栈。每次用户提问先从当前问题里提取实体如果提取不到就从实体列表里取最近提到的实体。意图栈用来记录用户当前是在问什么类型的问题比如是在问流程、问定义、还是问对比。这样即使用户的追问很简短系统也能结合上下文理解意图。4.3 图片和表格内容检索不到多模态文档处理思路“RAG知识库能存储图片吗”这个问题在热搜上出现过说明很多人都遇到了这个痛点。我的项目里也有大量带图表的技术文档纯文本检索完全覆盖不到这些内容。我的解决方案是在文档处理阶段做多模态解析。对于PDF里的图片先用OCR提取文字如果图片是流程图或架构图再用视觉模型生成一段描述文字。对于表格解析成结构化数据后同时保留原始表格的文本表示和结构化表示。检索的时候图片描述和表格文本都作为独立的文档块参与索引。这个方案的效果比预期好。用户问“那个系统架构图里数据库用的什么”系统能检索到架构图的描述文字并给出答案。当然多模态解析会增加文档处理的时间和成本但对于技术文档密集的场景这个投入是值得的。4.4 检索延迟高索引优化与缓存策略系统刚上线的时候检索延迟平均在2秒左右用户体感很差。排查下来发现瓶颈主要在向量检索和元数据过滤的联合查询上。向量库的索引结构没有针对元数据过滤做优化每次查询都要全量扫描。优化分两步走。第一步是调整向量库的索引参数把常用的元数据字段加到索引的payload里并且开启向量库的预过滤功能。第二步是在检索服务层加缓存把高频查询的结果缓存起来设置一个较短的过期时间。两步做完之后检索延迟降到了300毫秒以内。缓存的粒度也有讲究。我一开始是按完整查询缓存但发现命中率不高因为用户的提问方式千变万化。后来改成按查询的向量表示缓存相似度超过阈值的查询直接返回缓存结果。这样命中率提升了不少而且不会因为措辞不同而错过缓存。5. 自研RAG的进阶优化方向与长期演进5.1 从RAG到Agent让系统学会主动检索基础的RAG是被动检索用户问什么系统检索什么。但在实际场景里用户的问题往往需要多步推理才能回答。比如“对比一下我们和竞争对手的产品定价策略”这个问题需要先检索自家产品的定价文档再检索竞争对手的定价信息最后做对比分析。我的做法是在RAG之上加一层Agent调度。Agent负责把复杂问题拆解成多个子问题依次调用检索服务获取信息最后汇总生成答案。这样系统就从“一问一答”变成了“主动规划检索路径”。实现上可以用LLM做任务规划也可以用规则引擎做简单的流程编排。对于大多数企业知识库场景规则引擎加LLM兜底的方案性价比最高。5.2 知识库的持续更新增量索引与版本管理企业知识库不是静态的文档每天都在新增和修改。如果每次更新都全量重建索引成本太高。我的方案是做增量索引新文档进来之后只对新文档做向量化和索引同时更新元数据。对于修改过的文档先删除旧索引再插入新索引。版本管理也很重要。有时候用户会问“上个月的报销政策是什么”这时候需要检索历史版本的文档。我在元数据里加了版本号和生效时间字段检索的时候可以根据时间范围过滤。这样既能查到最新政策也能回溯历史版本。5.3 效果评估体系没有度量就没有优化RAG系统的优化不能靠感觉必须有量化的评估指标。我搭建了一套简单的评估体系包括检索召回率、答案准确率、答案完整性、响应延迟四个维度。评估数据集是从真实用户查询里采样的每个查询都人工标注了标准答案和相关的文档片段。有了评估体系之后每次调整检索策略或Prompt模板都可以快速验证效果。我建议自研RAG的团队从第一天就建立评估集哪怕只有几十条数据也比没有强。评估集要持续更新把线上发现的bad case加进去这样才能驱动系统不断迭代。5.4 成本控制Embedding和LLM调用的优化空间RAG系统的成本主要来自两块Embedding调用和LLM调用。Embedding的成本相对固定因为文档索引是一次性的查询时的Embedding调用量也不大。LLM调用才是成本大头尤其是当检索结果很多、Prompt很长的时候。我的优化策略是分级处理。对于简单的事实型问题用检索结果直接拼一个短Prompt调用小模型生成答案。对于复杂的分析型问题才用大模型和长Prompt。另外检索结果不要全部塞进Prompt先用重排序模型筛一遍只把最相关的几条传给LLM。这样既降低了成本又减少了噪声对生成质量的干扰。6. 一些实操中的个人体会逆向六款开源RAG产品的过程对我来说更像是一次系统性的学习。以前用开源产品遇到问题就查文档、提issue很少去想背后的设计逻辑。但当你真正把源码拆开、把架构图画出来的时候会发现每一个设计决策背后都有它的道理也都有它的代价。自研RAG不是要把所有环节都重写一遍而是要在理解开源方案的基础上做出适合自己业务场景的取舍。比如文档切分开源方案可能提供了五种切分器但你的业务场景可能只需要两种。比如检索策略开源方案可能支持十种召回方式但你的数据规模可能只需要向量加关键词两路召回。关键是搞清楚每种方案的适用边界而不是盲目追求功能多。另外一点体会是RAG系统的效果上限在文档处理阶段就决定了。如果文档解析不干净、切分不合理、元数据缺失后面再怎么优化检索和生成都是事倍功半。我在项目里花在文档处理Pipeline上的时间大概占了整个项目的一半以上但我觉得这个投入非常值得。最后分享一个小的技巧在自研RAG的早期阶段不要急着上复杂的架构。先用最简单的方案跑通全流程把文档处理、索引、检索、生成这条链路走通然后再逐步替换和优化每个环节。这样你能快速拿到反馈知道瓶颈在哪里而不是一开始就陷入过度设计的泥潭。