
最近在把自研RAG从“能跑通demo”推向“真正敢上生产”的过程中我把LangChain、LlamaIndex、Haystack、Dify、FastGPT、RAGFlow这六款开源产品从架构到代码逻辑逐层拆了一遍。拆完最大的感受是市面上讲RAG概念的教程很多但真正能告诉你“一个可复用的RAG系统应该长什么样、模块之间怎么咬合”的恰恰是这些开源项目的源码和产品设计。本文不谈概念直接把我逆向得到的通用蓝图、检索细节、解析切分方案、翻车点整理成一套可落地的东西给同样在自研RAG的朋友做参考。这篇内容的适用对象很明确你不是来看热闹的而是已经或打算自己搭RAG却不确定该把精力花在哪。我会告诉你六款产品各自的强项和局限它们共同遵守的设计规律以及哪些模块必须自研、哪些直接拿开源组件拼就够了。看完之后你应该能画出一张属于你自己的RAG架构图并且知道第一步该写什么代码。1. 为什么选这六款产品框架与产品的分界线聊逆向工程之前先说清楚选型逻辑。市面上打着RAG旗号的项目少说上百个我最后锁定这六款是因为它们正好处在两个不同层级一类是RAG开发框架一类是开箱即用的RAG产品。没有这个区分你很容易被误导——拿Dify的功能去对标LangChain或者拿RAGFlow的解析效果去质疑Haystack结论都会偏。框架层三款LangChain、LlamaIndex、Haystack。LangChain胜在生态最大各种loader、retriever、llm封装应有尽有但它本质是一个“积木箱”帮你把组件拼起来不替你决策。LlamaIndex则更专注“索引-检索”这条主线它对文档切分、索引结构、查询转换的抽象非常完整尤其适合做知识密集型的问答场景。Haystack是三者里工程化最成熟的一个pipeline概念清晰支持并行、条件分支、评估闭环生产环境里它比前两者更“稳”。产品层三款Dify、FastGPT、RAGFlow。Dify主打工作流编排和Agent能力RAG只是它的一个节点但它把“召回结果怎么展示给用户、引用怎么溯源”做得很细。FastGPT是另一种思路它把复杂的RAG配置收敛成表单用户只要填知识库、选检索模式、调几个参数就能用老手看它的前后端交互能读出不少产品化心得。RAGFlow则把火力集中在文档解析上它的DeepDoc模块做了布局识别、表格还原、OCR在中文文档和PDF上表现非常突出。我逆向时的具体方法是不只看README而是clone源码重点读这几类文件——数据模型定义、检索器实现、文档处理流程、API路由。同时配合官方demo去点一遍观察前端传什么参数、后端返回什么结构、失败时日志打什么。这样一轮下来比看一百篇架构图都管用。下表是我整理的产品定位对比方便你按需选择参考对象产品类型核心强项适合逆向借鉴的点LangChain框架组件生态最全链式调用抽象、Retriever接口设计LlamaIndex框架索引与查询管线切分策略、Node关系建模、Query PipelineHaystack框架生产级管道Pipeline生命周期、评估组件、并行化Dify产品工作流编排知识库数据集管理、引用展示、反馈机制FastGPT产品可视化配置检索参数收敛、前置对话逻辑、权限设计RAGFlow产品文档深度解析布局识别、表格抽取、模板化切分选框架还是选产品逆向取决于你的目标。如果你想学习“一个检索管线可以有哪些变体”LangChain和LlamaIndex的源码足够了。如果你想知道“真正给终端用户用的RAG产品怎么设计前后端交互、怎么处理脏数据”那Dify和RAGFlow更有参考价值。我是全部拆完才拼出完整图形的缺一个都会少一块拼图。2. 六款产品共同遵守的五层系统骨架拆完之后我发现不管产品形态差多远只要做的是“文档问答”底层一定跑不脱五层结构。这五层不是哪家规定的而是业务倒逼出来的——每一层解决一类独立问题层与层之间用标准数据接口衔接。第一层接入层。负责知识源接入包括文件上传、URL抓取、数据库同步、Notion/Confluence等三方源。LangChain有几百个loaderLlamaIndex有ReaderRAGFlow支持上传文件夹批量解析。逆向时要注意它们都抽象了统一的Document对象不管来自哪里最终都变成“内容元数据”。元数据特别关键后面过滤、引用、权限都靠它但很多自研项目一开始就把元数据丢了。第二层解析与切分层。原始文件进来必须先转成纯文本再切成适合向量化的块。这一层最容易被低估实际上它对RAG效果的影响超过一半。RAGFlow花大力气做布局识别就是为了还原PDF里“标题-正文-表格”的真实结构。FastGPT和Dify都提供多种切分策略但默认逻辑都偏保守——先按段落分再按长度截断为的是不让语义断裂。具体的切分方案我放在第四节细说。第三层索引与存储层。包括向量索引、倒排索引、知识图谱索引三类。LlamaIndex把向量存储和文档存储彻底分开document store管原始文本vector store管embedding这样更新时可以做到只删向量不删原文。Dify和RAGFlow都支持MySQL/PG存元数据再外挂向量库。我建议自研时也采用“双存储”而不是把所有东西塞进向量库原因后面会讲。第四层检索与增强层。这是决定问答质量的核心。六款产品都实现了多路召回至少包含向量检索关键词检索有些还加了图谱检索。召回之后几乎都做重排只是有的内置reranker有的让你外挂Cohere、BGE-reranker。查询改写也是这一层的活包括多查询、HyDE、子问题分解。这些能力放一起才能保证“用户问得不准时系统也能捞得回来”。第五层生成与评估层。生成负责把召回结果送进LLM并处理提示词拼接、引用标注、流式输出。评估则负责监控检索命中率和答案准确率。Haystack有完整的evaluate组件FastGPT有简单的命中率统计Dify把“用户点赞点踩”变成了隐式反馈。但遗憾地说这一层是自研项目最常砍掉的结果就是系统上线后怎么坏的都不知道。逆向时我还注意到了一个共性每一层的输出都必须是结构化数据。比如切分层输出的是带offset和metadata的Chunk而不是一段纯文本检索层输出的是带score和source的Document列表。这样下一层才能灵活处理和扩展。自研时如果发现某层之间在靠“JSON里拼字符串”传递数据基本说明架构已经开始烂了。3. 检索链路逆向多路召回、重排与查询改写检索层是RAG的发动机也是六款产品差异最大的地方。但它们不是乱设计而是都遵循了一条主线先尽可能多地捞再集中精力排序最后让LLM来选。3.1 多路召回几乎是标配LangChain提供了EnsembleRetriever可以组合BM25和向量检索权重可配。LlamaIndex更激进默认的AutoMergingRetriever会同时考虑父子节点小节点负责精确匹配大节点负责提供上下文。RAGFlow走得更远它把知识图谱也纳入召回先通过实体识别构建图谱然后在检索时同时查向量和关系边。我抄作业后的落地配置是BM25向量检索双路召回比例取0.3:0.7。为什么不是各50%因为向量检索在语义匹配上更符合问答场景BM25只用来防止“专有名词被embedding拉偏”的情况。这个比例没有标准答案建议先跑自己的测试集再小步调。3.2 重排决定了天花板召回阶段返回Top50甚至Top100真正给LLM的往往只有Top5。中间的漏斗靠重排完成。跨编码器cross-encoder效果远好于双编码器bi-encoder这个结论六款产品基本都认可。Dify内置了Cohere Rerank和Jina Reranker可选FastGPT也支持接入BGE-rerankerRAGFlow则直接内置了bge-reranker-large。自研时我给的建议是不要自己训练重排模型直接用开源Reranker。BGE-reranker-v2-m3跑中英文混合场景够用而且部署成本可控。但要注意一个坑重排的分数分布非常集中0到1之间的区分度很小千万别用“分数大于0.5才算命中”这种一刀切策略而是取相对排名TopN。我在线上就是这么被坑的后面还会细说。3.3 查询改写被忽略的10%收益最开始我认为查询改写是花活直到我看了LlamaIndex的Query Pipeline和LangChain的MultiQueryRetriever才意识到它对真实用户query非常重要。真实用户的问题往往是口语化的、指代不明的比如“那这个怎么部署”——系统不知道“那”指什么。六款产品里FastGPT在对话过程中会做上下文压缩把历史对话和当前问题合并成一个完整query再检索。这是很聪明的一招相当于把“查询改写”做成了隐式的。我也照搬了一套每轮检索前先让LLM把“历史摘要当前问题”转成一个自包含的搜索query成本大约增加200~300ms延迟但检索命中率提升明显。3.4 多路召回后的合并策略多路召回的结果怎么合并是自研时最难写优雅的一段。直接拼接会重复加权融合又需要调参。我参考了LangChain的RRFReciprocal Rank Fusion实现它不依赖绝对分数只看排名。公式是score sum(1/(k rank))k取60。好处是不同路分数分布不一致时依然能稳定融合。实际测试中RRF比加权平均要稳得多因为它天然对“某一路分数普遍偏高”免疫。如果你希望更精细可以在RRF之后再用重排模型做一次交叉验证相当于“粗排精排”的两阶段结构。这也是Haystack推荐的pipeline模式。4. 文档解析与切分决定RAG成败的隐形战场先说一个我踩过的坑早期自研RAG时我把80%的精力花在embedding模型和向量库调参上文档解析直接用现成库按\n\n切块。结果是号称精确率80%的系统问它PDF里的表格数据时几乎全错——因为表格被切碎了向量化之后语义完全丢失。这其实也是我盯上RAGFlow的原因。它的DeepDoc模块是我见过的开源实现里最认真的解析层先做版面分析把PDF页面识别成标题、正文、表格、图片四类区域表格单独走表格结构还原输出成HTML或Markdown再喂给LLM标题区域会记录层级关系用于后续切分时保留文档大纲。这套逻辑直接改变了我的想法——解析不是“把PDF转成txt”而是“把文档还原成结构化知识”。4.1 解析层的分级策略从六款产品逆推解析至少分三级低级解析纯文本提取适合txt、markdown、代码文件。用Python的pypdf或textract就行成本最低。中级解析带版面分析和OCR适合扫描版PDF、图片。RAGFlow用PaddleOCR做中文识别效果不错。自研时可以直接搬PaddleOCR或者Tesseract但要注意扫描件的坐标信息要保留否则后面做chunk定位会很麻烦。高级解析表格还原、公式识别、阅读顺序重建。这个目前开源领域没有银弹RAGFlow的表格还原算做得好的但在复杂合并单元格场景依然会翻车。我的建议是不要追求100%还原要保留原始页码和坐标让LLM在生成时知道信息来源在哪一页比强行解析成统一结构更重要。4.2 切分方案怎么选LlamaIndex的SentenceSplitter、LangChain的RecursiveCharacterTextSplitter、以及RAGFlow的模板切分我全部试过一轮。结论是通用型文档按段落语义切分比按固定token切分好。固定512token切出来经常把一个完整观点截断检索时召回半截话生成时自然各种脑补。结构化文档按Markdown标题层级切分。比如#一级标题下的##二级标题内容作为一个chunk这样每个chunk都有明确的主题边界。大文档父子分块。父块保留上下文子块负责精确匹配。回忆时先检索子块找到后把父块一起喂给LLM。LlamaIndex对这种模式实现得非常优雅自研时可以直接照搬它的HierarchicalNodeParser思路。4.3 切分参数的经验值我的实践参数是通用文本块大小512~768 token重叠率10%~15%。为什么重叠因为句子可能在边界处被截断重叠一段能让包含关键信息的句子完整出现在至少一个块里。这个比例不是玄学RAGFlow的默认参数也在这个区间附近。但注意不要用embedding模型的max length直接当chunk size——很多模型max length是512但实际有效上下文可能只有256chunk太大超出有效范围时后面内容全是噪音。4.4 表格和图片的索引方式FastGPT的做法是表格在切分前先转成Markdown再按行/内容块切分。Dify则支持在文档里提取图片并用视觉模型生成描述再把描述写入chunk的元数据。这些思路自研时都可以低成本复制。我现在对表格数据会额外做一层“行列摘要块”把“第几行第几列是什么值”结构化存起来检索时表格原文和摘要块双路召回实测比单存原文效果好很多。5. 上下文工程与引用溯源产品化不可跳过的一环框架层的开源项目往往不怎么管“用户能不能看懂答案”但产品层必须管。Dify和FastGPT在这块给了我很大启发——它们把“引用溯源”和“用户反馈”做成了RAG产品的基本功能而不是锦上添花。5.1 引用溯源怎么做Dify在生成答案时会给每段回复附带上对应的知识库文档ID和原文片段前端渲染成可点击的引用角标。背后的实现并不复杂检索时每个chunk都保留document_id、chunk_id、source_text生成时提示词里要求LLM在回答里标注引用编号[1]、[2]然后程序把编号映射回chunk元数据。重点在于提示词里要写清楚“只根据提供的文档回答并在每个论点后面标注来源编号”否则LLM会自己编引用。我在实测中发现加引用溯源还有一个额外好处用户更愿意相信答案。同样的答案带引用角标时用户“点赞”率明显更高。这不是功能特性这是心理效应但对产品留存很重要。5.2 上下文管理窗口不是越大越好六款产品在上下文管理上有两个共识第一检索结果要按相关性截断不是全塞进提示词第二要保留一个“对话摘要”来压缩历史。FastGPT的上下文处理是把历史对话压缩成摘要再和当前问句拼接成新query同时把本次检索到的最相关TopK上下文放进最终提示词。这个策略能有效防止“上下文污染”——历史里无关信息会干扰LLM对当前问题的判断。我给自研蓝图的建议是提示词结构分三段——系统指令、检索到的文档片段、对话历史摘要与当前问题。检索片段之间用XML标签分隔并要求LLM严格按片段内容回答。这个设计的收益远超想象它能让同一个模型的答题准确率提升10%以上。5.3 用户反馈闭环Dify的“赞/踩”按钮以及FastGPT的“纠错”功能本质上都是给RAG系统装上了评估传感器。你要做的不是把反馈数据攒着不看而是每天定期抽检“被踩”的case看是召回没召回到、还是重排把对的排下去了、还是LLM没按上下文回答。我管这个叫“三层归因”每一层改一个参数第二天再来对比。没有反馈闭环的RAG系统就像没有仪表盘的汽车——能开但你不知道什么时候爆缸。6. 可复用的自研蓝图模块清单、实现顺序与技术选型前面拆了这么多最终落到自研蓝图上。我的建议是不要一上来就照搬某个开源项目而是按自己的场景做裁剪。下面是经过六款产品验证的模块清单按依赖关系排序。第一阶段最小可用闭环1~2周目标是“上传文档能问答”只做最核心四件事文档解析模块接入pypdfPaddleOCR如果需要扫描件输出带元数据的结构化文本。切分模块先按段落递归切分设置块大小512、重叠64。不要做复杂切分后面再优化。检索模块向量检索用bge-m3或text-embedding-3-small向量库用Qdrant或Milvus再加BM25关键词检索用rank_bm25用RRF融合。生成模块提示词里放检索片段引用编号接入任意LLM API输出带引用的答案。这个阶段别碰Reranker、别碰复杂索引先把链路跑通。我见过太多人在第一阶段就陷入“切分参数调优”那是永远调不完的。第二阶段效果优化3~4周在最小闭环稳定后按收益排序增加Reranker接入BGE-reranker-v2-m3把Top50重排到Top5。查询改写每轮对话前用LLM把“历史摘要当前问题”改写为自包含query。父子分块在解析时构建父子关系检索子块后带父块上下文。元数据过滤在检索API里增加source_type、doc_id、time_range过滤条件方便做细粒度权限控制和定向检索。评估模块从线上日志里随机抽取100条query人工标注正确答案所在文档每天跑一遍召回率5和命中率1。第三阶段产品化打磨持续引用溯源前端展示引用角标后端返回chunk元数据。反馈采集赞/踩按钮落库并关联当时的query、检索结果、最终答案。增量更新监听知识库文件变化只重新解析/切分/索引变更的文档而不是全量重建。多路召回可视化调试台这是我自研过程中最值的一个小工具——输入一个问题能看到向量召回Top20、BM25召回Top20、重排后Top5分别是谁。这个工具能让你从“盲调参数”变成“看着证据调参数”。技术选型关键点向量库选型自研中小规模用Qdrant足够自带payload过滤和RRF融合部署简单。大规模或高并发再上Milvus。Embedding模型中文场景优先bge-m3英文场景text-embedding-3-small性价比高。不要用同一个模型同时做检索和重排检索选bi-encoder重排选cross-encoder两者定位不同。LLM调用层不要直接裸调API封装一层带超时、重试、降级的LLMClient。FastGPT的源码里对超时降级的处理值得抄一遍。哪些必须自研我坚持认为有三个模块值得自研查询改写逻辑、引用溯源渲染、反馈归因分析。前两个直接决定产品用户体验第三个决定你能不能持续优化。其余像解析、向量库、重排模型开源生态已经成熟没必要重复造轮子。7. 逆向工程中观察到的常见翻车点与避坑建议最后这部分是把六款产品在某些场景下暴露出的问题以及我自己自研时踩过的坑合并起来给你做一个体检清单。每一条我都付过学费希望你别再付一遍。7.1 元数据过滤失效导致权限失控Dify和RAGFlow都支持数据集级的权限控制但如果你没有规范所有chunk的元数据过滤就会静默失效。常见问题是有的chunk没写入doc_id导致过滤条件匹配不到任何数据于是系统“为了不返回空结果”退化成不过滤直接把别的租户的数据暴露出来。自研时一定要在切分模块里强制校验元数据完整性宁可解析失败也不要生成一条没有doc_id的chunk。7.2 重排分数一刀切阈值我前面提到重排分数很可能集中在0.05~0.3之间如果按score 0.5过滤Top50里可能只剩两三条召回率直接崩盘。正确做法是按重排后的相对顺序取TopN同时保留一个绝对下限来做“宁可少给也不瞎给”的兜底。这个兜底值需要在你自己的数据上统计不能照搬别人的。7.3 数据更新后向量与原文不一致只改向量库、不原文案存储或者反过来都会导致检索时“索引说有一篇文档但生成时拿不到原文”。Haystack和LlamaIndex都强调document store和vector store必须同步更新且都有事务性写入的封装。自研时最简单的办法所有写操作统一走同一个Service先改数据库再改向量库失败则回滚或重试。不要允许业务代码里直接分别调两个store。7.4 盲目追求召回率而忽略精准率很多RAG教程把“召回率”捧为核心指标但这会导致一个陷阱你把TopK从5调到50召回率确实上去了但LLM的输入被大量无关片段塞满准确率反而下降。我实测过TopK从5涨到20时答案准确率是先升后降的拐点在8~10左右。评估RAG不能只看召回率要看端到端的回答准确率这也就是为什么蓝图里一定要有评估模块。7.5 把LLM的上下文窗口当检索上限有些产品为了“充分发挥大模型能力”把所有召回片段全部塞进上下文甚至超过8k窗口。结果模型开始“幻觉式整合”各种不相关内容。正确的做法是根据你的LLM实际有效窗口预留30%给系统指令、对话历史和生成空间剩下70%放检索片段。比如8k窗口检索片段最多给5k剩下的3k留给其他内容。这套蓝图我前后迭代了两个月目前已经稳定支撑内部知识库问答。回头看最大的收获不是某段代码或某个参数而是理解了RAG不是一个模型问题而是一个系统工程问题。开源项目给你的最大价值不是直接可用的代码而是它们踩过坑后沉淀下来的设计决策。如果你也正准备自研RAG我建议你按这个顺序来先跑通最小闭环再补重排和查询改写最后做评估和反馈闭环。每一步都用真实数据验证不要跳步。