
1. 为什么我要去拆六款开源RAG产品做自研RAG这件事最怕的不是技术难而是方向错。我见过太多团队闷头写了三个月检索链路最后发现连最基础的分块策略都没调对召回率一直卡在60%上不去。更常见的情况是产品经理拿着一个“智能问答”的需求过来技术团队第一反应就是“上RAG”但问到具体用什么架构、怎么评估、知识库怎么组织大家就开始含糊了。我自己做RAG项目前后加起来有一年多时间从最早的LangChain全家桶到后来自己手写检索层中间踩过的坑足够写一本小册子。去年下半年我集中花了两周时间把市面上六款有代表性的开源RAG产品从代码层面拆了一遍目的很明确不是要找一个能直接用的轮子而是要搞清楚一套自研RAG系统到底应该长什么样。这六款产品覆盖了不同的技术路线有的偏重检索质量有的偏重工程化部署有的在知识图谱融合上做了很多探索。我把它们的架构图、核心模块、关键参数都整理了出来然后反向推导出一套可复用的自研蓝图。这篇文章就是这次逆向工程的完整记录适合正在做或准备做RAG系统的朋友参考不管你是刚入门还是已经踩过一轮坑应该都能从中找到一些有用的东西。提示本文涉及的“逆向工程”指的是对开源项目进行代码阅读和架构分析不涉及任何破解或违反开源协议的行为。所有分析均基于公开代码和文档。2. 六款开源RAG产品的架构拆解2.1 选型逻辑为什么是这六款市面上开源RAG项目很多我不可能全部拆完所以定了几条筛选标准。第一代码质量要过得去至少能跑起来文档不能太离谱第二架构要有代表性不能全是同一个套路第三要有一定的社区活跃度说明这个方案经过了真实场景的检验第四覆盖不同的知识库类型包括纯文本、结构化数据、图谱等。最终选定的六款产品我按它们的核心侧重点分了三类类型代表产品核心特点检索优化型产品A、产品B在分块、嵌入、重排序上做了大量工程优化工程部署型产品C、产品D强调可扩展性、多租户、API化管理知识融合型产品E、产品F尝试将RAG与知识图谱、结构化知识库结合这个分类不是绝对的很多产品同时具备多个特点但侧重点不同拆解的思路也不一样。2.2 检索优化型产品的核心设计产品A和产品B是我最早拆的因为它们的代码结构最清晰。这两款产品在检索链路上花了很多心思核心思路是把检索当成一个多阶段排序问题来处理。产品A的检索流程是这样的首先对文档做语义分块不是简单的按字数切而是用嵌入模型计算句子间的相似度在语义边界处切分。然后对每个块生成多向量表示包括原始文本嵌入、摘要嵌入、关键词嵌入。检索时先用关键词做粗筛再用向量做精排最后用一个轻量级的交叉编码器做重排序。这个设计的好处很明显召回率和精确率可以分开优化。粗筛阶段追求高召回精排阶段追求高精确。但代价是索引体积会膨胀三到五倍检索延迟也会增加。产品A的解决方案是引入了一个分层索引结构把高频访问的块放在内存里低频的放在磁盘上算是一个工程上的折中。产品B的思路不太一样它更强调查询理解。用户输入一个问题后系统会先做查询改写生成多个不同角度的子查询然后分别检索再融合结果。这个思路借鉴了多路召回的思想对于复杂问题效果很好。但产品B的代码里有一个细节值得注意它在查询改写阶段用了一个小型的本地模型而不是调用大模型API。这样做的好处是延迟低、成本可控缺点是改写质量受限于小模型的能力。实操心得如果你的场景里用户问题比较短、比较直接查询改写的收益可能不明显反而会增加延迟。我建议先做一个A/B测试看看改写前后的召回率差异再决定是否启用。2.3 工程部署型产品的架构选择产品C和产品D是面向企业级部署的它们的架构设计更多考虑的是稳定性、可扩展性和运维便利性。产品C采用了一个微服务架构把文档解析、向量化、检索、生成拆成了独立的服务。每个服务可以单独扩容比如文档解析服务可以多开几个实例来应对大批量导入检索服务可以根据QPS动态调整。服务之间通过消息队列通信保证了解耦。这个架构的缺点是部署复杂度高至少需要维护五六个服务对小团队来说负担不小。产品D走的是另一条路它做了一个单体应用但模块化设计。所有功能打包在一个进程里但内部通过清晰的接口划分模块。这样做的好处是部署简单一个Docker容器就能跑起来。产品D的代码里有一个很聪明的设计它把向量索引和元数据存储分离向量索引用专门的向量数据库元数据用传统的关系型数据库。检索时先查元数据做过滤再查向量索引做相似度匹配。这个设计让它可以支持复杂的过滤条件比如“只检索最近三个月内、标签为技术文档的内容”。产品D还有一个细节让我印象深刻它的配置系统做得非常完善所有关键参数都可以通过环境变量或配置文件调整而且每个参数都有详细的注释说明适用场景。这对于自研系统来说是一个很好的参考因为RAG系统的参数调优往往需要反复试验一个清晰的配置系统能省很多时间。2.4 知识融合型产品的探索产品E和产品F是我拆得最费劲的因为它们涉及知识图谱与RAG的结合代码复杂度明显高一个档次。产品E的核心思路是用知识图谱来增强检索。它先把文档中的实体和关系抽取出来构建一个轻量级的图谱然后在检索时同时使用向量检索和图谱检索。图谱检索负责找到实体之间的关联路径向量检索负责找到语义相似的文本块最后把两者的结果融合。这个方案在处理多跳问题时效果很好比如“A公司的CEO毕业于哪所大学”这种需要两步推理的问题。但产品E的代码里也暴露了一些问题。首先是图谱构建的成本很高需要调用大模型做实体抽取和关系抽取对于大规模文档来说这个开销不可忽视。其次是图谱的更新和维护比较复杂文档更新后需要同步更新图谱否则会出现不一致。产品E的解决方案是做了一个增量更新机制但代码逻辑相当绕我看了好几遍才理清楚。产品F的思路更激进一些它尝试把结构化知识库和RAG统一起来。用户可以用自然语言查询系统会自动判断应该走结构化查询还是向量检索或者两者结合。这个思路很吸引人但实现难度也最大。产品F的代码里有一个查询路由模块用一个小型分类模型来判断查询类型。这个模块的准确率直接决定了整个系统的表现而产品F在这方面的训练数据明显不够充分导致路由准确率大概只有80%左右。注意知识图谱融合方案听起来很美但落地成本很高。如果你的文档量不大、问题类型比较单一我建议先把纯向量检索做扎实不要过早引入图谱。3. 从开源产品中提炼的自研RAG核心蓝图3.1 整体架构设计分层与解耦拆完这六款产品后我脑子里逐渐形成了一个自研RAG的架构蓝图。核心思路是分层与解耦把整个系统分成四个层次第一层是数据层负责文档的接入、解析、分块和索引。这一层的关键是可插拔不同的文档格式PDF、Word、Markdown、HTML用不同的解析器不同的分块策略可以配置切换。产品A和产品D在这一层都做得很好值得借鉴。第二层是检索层负责查询理解、多路召回和结果融合。这一层的关键是可组合向量检索、关键词检索、图谱检索可以自由组合每种检索方式的结果通过一个统一的融合模块处理。产品B的查询改写和产品E的图谱检索都可以作为这一层的可选模块。第三层是生成层负责把检索结果和用户问题组装成提示词调用大模型生成回答。这一层的关键是可替换不同的大模型API可以无缝切换提示词模板可以配置。产品C和产品D在这一层都做了抽象把模型调用封装成了一个统一的接口。第四层是应用层负责对外提供API、管理知识库、监控系统状态。这一层的关键是可观测需要记录每次检索的召回率、生成的质量、用户的反馈等指标。产品D的配置系统和监控模块在这一层做得比较完善。这个四层架构的好处是每一层都可以独立演进。比如你想换一个更好的嵌入模型只需要改数据层想尝试新的检索策略只需要改检索层。各层之间通过清晰的接口通信不会互相影响。3.2 数据层设计文档解析与分块策略数据层是RAG系统的地基地基没打好上面盖什么都是歪的。我在拆产品A的时候发现它的文档解析模块支持十几种格式每种格式都有专门的解析器。PDF用PyMuPDFWord用python-docxHTML用BeautifulSoupMarkdown用mistune。每个解析器都做了异常处理比如PDF解析失败时会尝试OCRWord解析时会保留表格结构。分块策略是数据层最核心的部分。我总结下来有三种主流方案固定长度分块是最简单的按字符数或token数切分通常设置一个重叠窗口来保持上下文连贯。产品D默认用的是512个token一块重叠128个token。这个方案的优点是实现简单、索引均匀缺点是可能把完整的语义单元切碎。语义分块是产品A的做法用嵌入模型计算相邻句子的相似度在相似度低谷处切分。这个方案能保证每个块在语义上是完整的但计算成本高而且分块结果不稳定同样的文档两次分块可能得到不同的结果。结构化分块是产品F的做法利用文档本身的结构信息来分块。比如Markdown按标题层级分块HTML按DOM树分块PDF按段落和表格分块。这个方案的效果最好但依赖于文档本身的结构质量。我的建议是混合使用优先用结构化分块如果文档结构不清晰退回到语义分块如果语义分块成本太高再用固定长度分块。产品D的代码里有一个分块策略选择器根据文档类型和内容特征自动选择分块方式这个设计很实用。实操心得分块大小对检索效果影响很大。我实测下来中文文档用300到500字一块比较合适英文文档用200到400个token。重叠窗口设置在块大小的20%到30%之间。但这个参数没有绝对的最优值需要根据你的文档特点和查询类型来调。3.3 检索层设计多路召回与融合排序检索层是RAG系统最复杂的部分也是最能体现技术水平的地方。拆完六款产品后我总结出一个三阶段检索流程第一阶段是查询理解。用户输入的问题往往不够精确需要做一些预处理。产品B的做法是查询改写生成多个子查询。产品A的做法是查询扩展用同义词和关联词扩充原始查询。我的建议是两者结合先用一个小模型做查询改写再用一个词典做查询扩展。但要注意控制改写和扩展的程度过度改写会导致检索偏离原始意图。第二阶段是多路召回。这是检索层的核心我建议至少做两路召回向量召回和关键词召回。向量召回用嵌入模型计算语义相似度关键词召回用BM25或TF-IDF计算词频相似度。两路召回的结果合并后去重得到一个候选集。如果场景需要还可以加入图谱召回从知识图谱中查找关联实体。第三阶段是融合排序。候选集里的文档块需要重新排序把最相关的排在前面。产品A用的是交叉编码器做重排序效果最好但速度慢。产品D用的是加权融合把向量相似度和关键词相似度按权重相加速度快但效果一般。我的建议是先用加权融合做粗排再用交叉编码器对Top-K结果做精排这样可以在效果和速度之间取得平衡。召回方式优点缺点适用场景向量召回语义理解强对精确匹配不敏感自然语言问题关键词召回精确匹配强语义理解弱术语、代码、专有名词图谱召回多跳推理强构建成本高实体关系复杂的问题3.4 生成层设计提示词工程与模型选择生成层看起来简单就是把检索结果塞进提示词然后调用大模型但实际做起来有很多细节。提示词模板的设计很关键。我拆产品C的时候发现它的提示词模板有五个版本分别对应不同的查询类型。事实型问题用一个模板分析型问题用另一个模板对比型问题又用一个模板。每个模板都明确规定了回答的格式、引用的方式、不确定时的处理策略。这个做法值得学习因为不同类型的查询对生成的要求确实不一样。上下文组装也有讲究。检索回来的文档块不能直接拼接需要做一些处理。产品D的做法是按相关性排序后截断只保留最相关的几个块总长度控制在大模型上下文窗口的70%左右。留出30%的空间给系统提示词、用户问题和生成结果。这个比例是我实测下来比较合理的既能提供足够的上下文又不会让模型“迷失”在太多信息里。模型选择方面我的建议是分级使用。简单的事实型问题用小型模型或本地模型复杂推理问题用大型模型。产品B的代码里有一个查询复杂度评估模块根据问题的长度、实体数量、是否需要多跳推理来判断复杂度然后路由到不同的模型。这个设计能显著降低成本。注意不要迷信大模型。我实测下来对于简单的知识库问答一个经过微调的小型模型比如7B参数级别效果可以接近大型模型但成本只有十分之一。关键是要有高质量的微调数据。4. 自研RAG的实操落地与参数调优4.1 环境搭建与基础组件选型自研RAG的第一步是搭环境。我推荐用Python作为主要开发语言生态最成熟。核心组件包括向量数据库的选择很关键。我试过Milvus、Qdrant、Weaviate和Chroma。Milvus功能最全但部署最重Qdrant性能最好但生态稍弱Weaviate平衡性不错Chroma最轻量适合原型验证。我的建议是开发阶段用Chroma生产环境用Qdrant或Milvus。产品D用的是Qdrant代码里的集成做得很干净可以参考。嵌入模型方面中文场景我推荐用BGE系列或M3E英文场景可以用OpenAI的text-embedding-3或开源的E5系列。产品A用的是BGE-large-zh效果不错。但要注意嵌入模型的维度和最大输入长度这两个参数直接影响索引大小和分块策略。大模型的选择取决于你的预算和场景。如果预算充足GPT-4或Claude系列效果最好。如果要求本地部署可以考虑Qwen、Baichuan或Llama系列。产品C支持多种模型后端它的抽象层设计值得参考。# 一个简单的嵌入模型封装示例 from sentence_transformers import SentenceTransformer class EmbeddingModel: def __init__(self, model_nameBAAI/bge-large-zh-v1.5): self.model SentenceTransformer(model_name) self.dimension self.model.get_sentence_embedding_dimension() def encode(self, texts, batch_size32): return self.model.encode( texts, batch_sizebatch_size, normalize_embeddingsTrue )4.2 文档处理流水线的搭建文档处理流水线是数据层的核心我把它分成四个步骤第一步是文档接入。支持多种来源本地文件、对象存储、数据库、API。产品D的做法是定义一个统一的文档接口所有来源的文档都转换成统一的格式包含内容、元数据、来源信息。这个设计让后续处理不需要关心文档来源。第二步是文档解析。根据文档类型调用不同的解析器。PDF用PyMuPDF提取文本和表格Word用python-docx保留样式HTML用BeautifulSoup去除标签。产品A的解析器支持表格提取能把PDF里的表格转成结构化数据这个功能很实用。第三步是分块。按照前面说的混合策略先尝试结构化分块不行再语义分块最后兜底固定长度分块。每个块都要保留元数据来源文档、位置、标题层级、块类型。这些元数据在检索时可以用来过滤。第四步是索引。把块向量化后存入向量数据库同时把元数据存入关系型数据库。产品D的做法是双写先写向量库再写元数据库用一个事务ID关联。如果其中一个写失败会记录到重试队列。实操心得文档处理是最耗时的环节。我建议做一个处理进度追踪功能记录每个文档的处理状态待处理、处理中、已完成、失败。失败的要能重试而且重试时不要重复处理已完成的块。4.3 检索效果的评估与调优检索效果评估是自研RAG最容易被忽视的环节。我见过很多团队做完系统后只靠“感觉”来判断效果好不好。这是不行的必须要有量化指标。我常用的评估指标有三个召回率、精确率和MRR平均倒数排名。召回率衡量的是“该找到的是否都找到了”精确率衡量的是“找到的是否都相关”MRR衡量的是“最相关的是否排在最前面”。评估数据集的构建很关键。我建议人工标注一批查询-文档对至少100条以上。每条查询标注哪些文档是相关的相关程度分等级高度相关、部分相关、不相关。然后用这些数据来测试不同参数下的检索效果。参数调优方面我总结了一个优先级顺序分块大小和重叠窗口影响最大优先调嵌入模型影响很大但更换成本高召回路数和融合权重影响中等调优空间大重排序模型影响中等但增加延迟查询改写策略影响因场景而异产品A的代码里有一个自动调优脚本会遍历不同的参数组合用评估数据集打分然后输出最优参数。这个思路很好但实际用下来自动调优的结果不一定比人工调优好因为评估数据集的质量和覆盖度很难保证。4.4 生成质量的把控与优化生成质量是最终用户能感知到的部分也是最难把控的。我总结了几条经验引用来源是必须的。每个生成的回答都要标注引用了哪些文档块这样用户才能验证。产品C的做法是在提示词里要求模型输出引用编号然后在后处理阶段把编号替换成实际的文档链接。不确定时的处理很重要。如果检索结果的相关性都不高模型应该明确说“根据现有知识库无法回答”而不是强行编造。产品D的做法是设置一个相关性阈值低于阈值的检索结果不传给模型直接返回“未找到相关信息”。回答格式要统一。我建议在提示词里明确规定回答的结构先给结论再给依据最后给引用。这样用户看起来清晰也方便后续做自动化处理。注意大模型的“幻觉”问题在RAG场景下依然存在。即使给了正确的上下文模型也可能生成不准确的内容。我的经验是在提示词里反复强调“只根据提供的上下文回答”并且在生成后做一个事实一致性检查用另一个模型或规则来验证回答是否与上下文一致。5. 常见问题与排查技巧实录5.1 检索效果差的排查思路检索效果差是最常见的问题表现是“明明知识库里有答案但就是检索不到”。排查思路如下先检查分块。把检索到的块和原始文档对比看看是不是分块把关键信息切碎了。我遇到过一个案例一个完整的操作步骤被切成了三块检索时只召回了其中一块导致生成的回答不完整。解决办法是调整分块策略对这种步骤型内容用更大的块或特殊的分块规则。再检查嵌入模型。用一些典型的查询测试嵌入模型的相似度计算是否合理。有时候嵌入模型对某些领域的术语理解不好导致语义相似度计算偏差。解决办法是用领域数据微调嵌入模型或者换一个更适合的模型。然后检查召回策略。如果只用了向量召回试试加上关键词召回。有些查询包含精确的术语或代码向量召回可能不如关键词召回。产品B的代码里有一个召回策略诊断工具会分析查询的特征建议使用哪种召回方式。最后检查重排序。如果召回没问题但排序不对那就是重排序的问题。可以试试换一个重排序模型或者调整融合权重。问题表现可能原因排查方法解决方案召回率低分块不合理对比检索块与原文调整分块策略召回率低嵌入模型不匹配测试相似度计算微调或更换模型精确率低召回路数单一分析查询特征增加关键词召回排序不对重排序模型弱检查Top-K结果更换重排序模型多跳问题失败缺少图谱召回分析问题类型引入图谱检索5.2 生成质量不稳定的应对生成质量不稳定表现为同样的问题有时候回答很好有时候答非所问。这个问题通常出在上下文组装和提示词上。上下文组装方面我建议按相关性排序后截断而不是随机选择。产品D的代码里有一个上下文选择器会根据块的相关性、长度、多样性来选择一个最优的上下文组合。这个设计能显著提升生成质量的稳定性。提示词方面我建议固定模板不要每次动态生成。模板里的变量只包括用户问题和检索结果其他部分保持不变。这样模型的输出格式会更稳定。温度参数也要注意。生成回答时温度不要设太高0.1到0.3之间比较合适。温度太高会导致输出随机性大温度太低又会导致输出过于死板。5.3 性能瓶颈的定位与优化RAG系统的性能瓶颈通常出现在三个地方文档处理、检索和生成。文档处理的瓶颈通常是解析和向量化。解析大PDF很慢向量化大批量文本也很慢。优化方法是并行处理用多进程或多线程同时处理多个文档。产品C的代码里用了Celery做任务队列支持分布式处理。检索的瓶颈通常是向量相似度计算。如果索引很大暴力计算会很慢。优化方法是使用近似最近邻算法如HNSW、IVF牺牲一点精度换取速度。Qdrant和Milvus都支持这些算法配置一下就行。生成的瓶颈通常是大模型调用。优化方法是流式输出让用户先看到部分结果减少等待感。产品D支持流式输出代码里用SSE实现可以参考。实操心得性能优化不要过早进行。先把功能做对再考虑做快。我见过很多团队在功能还没稳定的情况下就开始优化性能结果优化了半天功能一改又白费了。5.4 知识库更新的处理策略知识库不是一成不变的文档会新增、修改、删除。如何处理更新是一个容易被忽视但很重要的问题。新增文档比较简单走正常的处理流水线就行。但要注意去重避免同一份文档被重复索引。产品A的做法是用文档的哈希值做去重如果哈希值已存在就跳过。修改文档比较麻烦。如果文档内容变了需要重新分块、重新向量化、更新索引。产品D的做法是先删除旧版本的所有块再插入新版本的块。这个操作要保证原子性否则会出现新旧版本混杂的情况。删除文档需要同时删除向量索引和元数据。产品D的做法是软删除先标记为已删除检索时过滤掉然后定期做物理删除。这样避免了删除操作影响检索性能。增量更新是一个高级话题。产品E尝试做增量更新但代码复杂度很高。我的建议是如果更新不频繁直接全量重建索引简单可靠。如果更新频繁再考虑增量方案。6. 自研RAG的扩展方向与个人体会6.1 多模态RAG的探索纯文本RAG已经比较成熟了下一步的扩展方向是多模态RAG。所谓多模态就是知识库里不仅有文本还有图片、表格、图表等。用户可以用自然语言查询这些多模态内容。产品F在这方面做了一些探索。它的做法是用多模态嵌入模型把图片和文本映射到同一个向量空间检索时同时检索文本和图片。生成时如果检索到图片会把图片的描述或OCR结果传给大模型。这个方向很有前景但目前的技术成熟度还不够。多模态嵌入模型的效果还不稳定图片的理解和生成也有很大挑战。我的建议是先做好文本RAG再逐步引入表格和图片。表格可以先转成结构化文本图片可以先做OCR提取文字。6.2 Agent与RAG的结合RAG和Agent的结合是另一个热门方向。所谓Agent就是让大模型不仅能回答问题还能调用工具、执行操作。在RAG场景下Agent可以调用检索工具、计算工具、API等。产品B的代码里有一个简单的Agent框架支持定义工具和调用流程。用户的问题如果涉及计算Agent会先调用计算器再调用检索最后生成回答。这个思路很实用但实现复杂度不低。我的建议是从简单的工具调用开始比如先支持计算器和日期查询再逐步扩展。不要一上来就做复杂的多步推理Agent容易失控。6.3 我在自研RAG路上踩过的坑最后分享几个我踩过的坑希望能帮你省点时间。第一个坑是过度设计。我一开始就想做一个“全能”的RAG系统支持各种文档格式、各种检索策略、各种模型。结果代码写了一万多行bug比功能还多。后来我砍掉了80%的功能只保留最核心的检索和生成系统反而稳定了。先做减法再做加法这是我最大的体会。第二个坑是忽视评估。我早期做RAG的时候只靠人工试几个问题来判断效果。结果上线后用户反馈“很多问题答不上来”我才发现检索召回率只有50%多。后来我花了一周时间构建评估数据集才发现问题出在分块策略上。没有评估就没有优化这句话在RAG场景下尤其正确。第三个坑是盲目追求新技术。我看到知识图谱RAG的论文后很兴奋花了一个月时间搭了一个图谱增强的检索系统。结果发现对于我的场景技术文档问答图谱带来的提升非常有限反而增加了维护成本。技术选型要匹配场景不要为了技术而技术。第四个坑是忽略工程细节。比如日志记录、错误处理、配置管理这些看起来不起眼的东西在实际运维中非常重要。我有一次因为没记录检索日志出了问题完全不知道从哪里排查。后来我加上了详细的日志每次检索都记录查询、召回结果、耗时排查问题就快多了。提示如果你刚开始做RAG我建议先用现成的开源方案比如Dify、FastGPT、RAGFlow快速搭一个原型验证场景可行性。等确认了需求再考虑自研。自研的成本比想象中高不要为了自研而自研。6.4 一个可复用的自研蓝图总结把上面的内容串起来我最终形成的自研RAG蓝图是这样的数据层统一的文档接口支持多种格式解析混合分块策略双写索引向量库元数据库处理进度追踪。检索层查询理解改写扩展多路召回向量关键词图谱融合排序加权粗排交叉编码器精排相关性阈值过滤。生成层多版本提示词模板上下文选择器分级模型路由流式输出引用来源标注。应用层RESTful API知识库管理界面检索日志和监控配置管理系统评估工具集。这个蓝图不是一成不变的你可以根据具体场景增减模块。但核心思想是分层解耦、可插拔、可观测。只要把握住这三点自研RAG就不会走偏。我在实际项目中发现这套蓝图落地后检索召回率能从最初的60%提升到85%以上生成质量的用户满意度也有明显改善。当然具体效果取决于你的数据质量和调优程度。希望这些经验对你有帮助。