半年多前,我接手了一个自研RAG的项目。第一次给业务方演示时,效果非常尴尬:用户问"2024年Q3的营收数据",系统从一份两百多页的财报里召回的却是"风险提示"章节的原文,生成答案自然也跑偏了。那次翻车之后我做了一个决定——把市面上能跑起来的开源RAG产品全部拉下来,逐一做逆向工程,看它们到底用什么手段把"检索+生成"这件事做到可用。
我挑了六款最有代表性的产品:RAGFlow、QAnything、Dify、FastGPT、LangChain、LlamaIndex。它们一个比一个卷,架构理念各有侧重,但拆完之后我发现,真正决定RAG效果上限的,翻来覆去就是那么几个环节。这篇文章是我那次逆向工程的完整记录,也会直接给出提炼后的自研RAG蓝图,适合正在搭建RAG知识库、被召回率折磨、或者准备从零自研RAG框架的团队参考。
1. 为什么我要对开源RAG做逆向工程?——自研的起点不是写代码,而是拆产品
1.1 自研RAG的第一次翻车
我先说那次翻车的细节。当时团队选型时图省事,直接用了"向量数据库+通用Embedding模型+大模型"的三件套组合。文档处理也很粗糙,就是把PDF按页切块,每块512个字符直接灌进向量库。演示那天,业务方问了一个很正常的经营分析问题,结果系统从财报的附录里捞出一段关于法律风险的原文。检索出来的内容跟问题在语义上不能说完全无关,但就属于"答非所问"级别的错位。
复盘时我们列了几个怀疑点:是Embedding模型不够强?是切块大小不对?还是向量检索本身就有天花板?后来我发现,这些问题单独拎出来都不是根因,根因是整个流水线缺了太多环节——没有版面分析、没有标题层级利用、没有混合检索、没有重排。说白了,我们做的不是RAG,只是一个带检索外壳的文本拼接器。
那次之后我给自己定了一条规则:别一上来就写代码,先把成熟的开源产品拆明白。RAG这个领域已经卷了好几年,像RAGFlow、Dify这些项目几千个Star不是白来的,它们背后的架构决策,全是拿真实业务数据试错试出来的。逆向工程开源产品,本质上就是花别人的时间成本,把别人踩过的坑提前踩一遍。
1.2 逆向工程的方法论:拆什么,怎么拆
我当时的拆解思路分四步。第一步是跑通官方Demo,用它们的默认配置跑同一份测试文档,记录效果基线。这一步很关键,默认配置本身就是产品作者认为"最不容易出错"的参数组合,相当于是白送的调参经验。
第二步是读架构文档和代码目录结构,确认每个模块的职责边界。我关心的不是某个函数怎么写,而是数据流在哪几个环节发生了形态变化——原始文档变成文本块、文本块变成向量、向量召回结果如何拼装成Prompt。
第三步是改参数做对照实验。比如把分块大小从256改到1024,看召回结果怎么变;把TopK从3改成10,看生成质量是否崩。这一步能很快暴露产品的容错边界,也能反推出它内部用了什么兜底机制。
第四步是看Issue区和社区讨论。很多产品都把已知瓶颈写在Issue里,比如"表格解析效果差""长文档丢失段落顺序",这些信息比源码本身还值钱,因为它们是作者亲口承认的短板。这套方法我建议自研团队直接抄,核心原则就一条:不要试图理解每一行代码,要理解每一个设计决策背后的取舍。
2. 六款开源产品的横向解剖:架构共性里的答案
2.1 RAGFlow和QAnything:文档解析层是RAG的地基
RAGFlow最让我惊艳的不是它的问答效果,而是它的DeepDoc解析引擎。它把PDF当成一张版面图来处理,先做版面分析,区分标题、正文、表格、图片、页眉页脚,然后再对每个区域走不同的处理管线。表格会走专门的表格结构识别,图片会走OCR,标题会保留在切块元数据里。这看起来像"笨办法",但对知识库问答的提升是决定性的。
我做过一组对照实验:同一份含表格的财报PDF,用普通PDF文本抽取,表格数据会乱成一团,按行切出来全是碎片;用RAGFlow式的版面分析,表格先被识别为结构化数据,再进行语义切分,用户问"华东区营收"时能精准定位到表格对应行列。这就是解析层带来的差距。很多人觉得RAG瓶颈在模型,其实模型能做的早做了,真正卡住效果的往往是文档进来第一步就没处理好。
QAnything的亮点则在本地部署和混合检索。它在默认配置里同时跑了向量检索和BM25关键词检索,再用Rerank模型把两路结果合并排序。我当时很疑惑,为什么一个本地优先的产品要搞得这么复杂?后来想明白了:向量检索擅长语义相似,但精确的词面匹配还得靠BM25。产品型号、工单编号、合同条款号这类内容,向量很容易在语义空间里跑偏,关键词检索反而是最稳的。
2.2 Dify和FastGPT:应用编排与知识库运营
Dify给我最大的启发是"RAG流水线可视化"。它把文档导入、切分、向量化、检索、重排、模型调用统统做成了可视化节点,每个节点都能单独调参数、单独看日志。自研时最痛苦的事情就是排查链路问题——用户说答案不对,到底是检索错了还是生成错了?Dify这种设计相当于强迫开发者把每个环节的输入输出都暴露出来,排查效率直接提升一个量级。
FastGPT则是把知识库当成一个运营产品来做。它有很完整的"知识库→应用→反馈"闭环:每一次问答都能标记点赞或点踩,点踩的样本可以回流到知识库里做人工修正,比如补充同义问题、修正切块边界。这个设计看起来不起眼,但对长线效果维护特别重要,因为RAG冷启动容易,越跑越准很难。
我当时还专门研究过Dify的检索逻辑。它在检索节点里同时支持向量检索、全文检索、混合检索三种模式,混合模式下还能配权重。这个设计解决了一个很现实的问题:不同知识库对检索方式的需求不一样。合同库需要精确词面匹配,FAQ库需要语义泛化,单一检索策略根本覆盖不了。
2.3 LangChain和LlamaIndex:框架思维的价值与陷阱
LangChain和LlamaIndex可以放在一起说。它们不是完整可用的RAG产品,而是RAG开发框架,价值在于提前定义了RAG的通用抽象层。LangChain把"检索器""文档加载器""输出解析器"这些概念标准化,LlamaIndex更极端,直接围绕"索引"做了大量优化,比如树状索引、关键词索引、文档关系图索引。
逆向这两个框架最大的收获是:RAG的架构设计必须有清晰的层次划分。数据加载、切分、索引、检索、合成这五层必须解耦,每一层都能单独替换实现。很多自研项目把代码写成一坨,切分逻辑和检索逻辑搅在一起,后面想优化一个点就得动全身。框架类产品用抽象层换来了极大的灵活性,代价是学习成本和性能损耗,这个取舍自研时要想清楚。
但我也要说,直接用LangChain搭RAG会有明显的天花板。框架给的是积木,不是房子,效果好坏完全取决于使用者的工程能力。而且框架为了通用性,默认实现普遍偏薄,比如它的默认切分器就是按字符数硬切,不会主动保留语义边界。这也是为什么我最终建议自研团队参考框架的分层思想,但具体实现要往RAGFlow这些产品的水准靠。
2.4 共性架构:从六款产品里抽出的八层最小组件
拆完六款产品之后,我发现它们的核心流水线惊人地一致,都是"解析→切分→向量化→索引→检索→重排→生成",区别只在于每个环节的工程深度。我整理了一张共性架构表,这也是自研RAG的最小必要组件清单:
| 层 | 核心组件 | 常见实现 | 关键参数 |
|---|---|---|---|
| 数据接入 | 文档加载器 | PDF/Word/HTML/Markdown 解析 | 格式覆盖范围 |
| 文档解析 | 版面分析+OCR | DeepDoc、PaddleOCR、Tesseract | 是否保留标题层级/表格结构 |
| 文本切分 | Chunker | 固定大小、递归字符、语义切分 | chunk_size、overlap |
| 向量化 | Embedding模型 | BGE、M3E、OpenAI Embedding | 维度、batch_size |
| 索引 | 向量库+倒排索引 | Milvus、Qdrant、Elasticsearch、pgvector | 索引类型、距离度量 |
| 检索 | 混合检索 | 向量+BM25/稀疏检索 | top_k、权重配比 |
| 重排 | Rerank模型 | BGE-Reranker、Cohere Rerank | 候选集大小 |
| 生成 | LLM调用 | 各类大模型 | prompt模板、max_tokens、温度 |
这八层就是RAG的最小必要组件。自研时每一层都可以先用简单实现跑通,再逐步替换成更重的方案。我见过太多团队一上来就上重型组件,结果链路太复杂,排查问题时根本不知道从哪里下手。
3. 从逆向工程到自研蓝图:一套可复用的RAG架构
3.1 数据接入与文档解析:别在第一步偷懒
自研RAG的第一个决策点是数据接入层。很多团队直接拿现成的PDF文本抽取库干活,遇到扫描件就抓瞎,遇到复杂表格就乱码。我在拆完RAGFlow之后,把数据接入拆成了三个子步骤:格式识别、版面分析、内容提取。
格式识别决定走哪条处理管线。文字型PDF直接走文本抽取,扫描型PDF走OCR,Word和Markdown走结构化解析,HTML要先去标签。版面分析是把页面拆成标题、正文、表格、图片、页脚几个区域,这一步决定了后续切块是否保留结构信息。
内容提取是真正的分水岭。表格要用表格结构识别,提取后转为Markdown表格或HTML表格再入库;图片里的文字要走OCR;多列排版的文档要先还原阅读顺序。我实测下来,不做版面分析的RAG,表格类问题的召回准确率会掉30%以上,这不是危言耸听,是拿真实数据跑出来的结果。
实操上我建议自研团队第一版就直接集成PaddleOCR或类似工具,成本不高,但能覆盖扫描件和图片型PDF。RAGFlow的DeepDoc不开源核心权重,但它的版面分析思路完全可以复刻——把页面图像输入目标检测模型,输出各区域的坐标和类别,再按坐标顺序重组文本流。这一步做扎实,后面所有环节都会轻松很多。
3.2 分块策略:chunking是一道数学题,也是一道语义题
分块是RAG里讨论最多、玄学味最重的一个环节。我拆完六款产品后发现,大家的分块策略本质上都是在解决同一个矛盾:块太大,向量化后语义被稀释,检索精度下降;块太小,上下文不完整,生成阶段信息不足。
我先给一个可用的起点参数:chunk_size设为300到500个汉字,overlap设为50到80个字符。为什么是这个区间?因为中文Embedding模型大多数按token计算,一个汉字大概对应1到1.5个token,300到500字对应400到700个token,在主流Embedding模型的512token上限附近,既不会截断,又能保留完整语义。
但固定大小切块只是基线,真正的提升来自语义切分。我推荐一种工程上很实用的做法:先把文档按标题层级拆成章节,再把每个章节按段落边界切块,最后把过长的段落按句子边界二次切分。这样切出来的块,天然带有章节归属信息,检索时可以把"章节标题"作为元数据一起存入向量库,召回后还能用标题做上下文补全。
这里有个细节很多人忽略:overlap不是越大越好。overlap的作用是防止切块正好截断一个完整语义单元,但过大的overlap会让相邻块高度相似,检索时大量返回重复内容。我实测下来,overlap控制在chunk_size的10%到20%之间最稳。另外要记得,切块时保留原始文档的页码和章节路径,这两个字段在生成引用溯源时是救命稻草。
3.3 混合检索与重排:召回率提升的两个隐藏杠杆
召回率不高,很多团队第一反应是换更强的Embedding模型。但拆完QAnything和Dify之后,我得说句实话:换模型带来的提升,远不如把检索策略从"单路向量"改成"混合检索+重排"来得明显。
混合检索就是同时跑向量检索和BM25关键词检索,然后把两路结果合并。向量检索擅长语义泛化,BM25擅长精确匹配,两者互补。工程上最简做法是两路各取TopK,K设置为最终期望候选数的2到3倍,然后合并去重,交给重排模型打分。如果向量库用的是Elasticsearch或OpenSearch,本身自带BM25能力,实现成本很低。
重排是整个流水线里性价比最高的组件。Rerank模型会把候选集逐条与用户问题做深度语义匹配,打出一个精确的相关性分数。我强烈建议自研团队把这个环节加上,BGE-Reranker这类开源模型已经足够好用。我实测的一个案例:只加重排不加其他优化,Top1命中率从62%提升到81%,这个提升幅度远超换一个更大的Embedding模型。
重排还有一个额外好处:它可以天然解决"向量召回结果排序不合理"的问题。向量检索返回的分数是语义空间里的余弦相似度,这个分数在不同查询下的分布不稳定,直接按它排序经常出错。重排模型把候选集重新排序后,取TopN传给生成层,整个链路的稳定性会好很多。
3.4 KG知识库与Ontology RAG:结构化知识怎么融入
拆完开源产品之后有一个问题始终绕不开:纯向量的RAG知识库处理不了结构化知识和复杂关系推理。比如问"A供应商和B供应商之间是什么股权关系",这种多跳查询靠向量检索几乎不可能答好。解决思路就是引入知识图谱,也就是热词里常说的KG知识库。
先厘清概念。RAG知识库(向量知识库)存的是非结构化文本块,适合"语义相似"类查询,比如"这个产品的售后政策是什么"。结构化知识库存的是数据库表、Excel、JSON这类规整数据,适合精确查询和聚合计算。KG知识库存的是实体和关系,适合多跳推理和关系链查询,比如"哪些员工同时参与了这两个项目"。
这三种知识库不是互斥的,而是可以分层的。我在自研蓝图里建议的做法是:把非结构化文档作为主知识库,抽取其中的实体和关系构建KG辅助层;结构化数据继续留在关系数据库里,通过一个工具调用层接入RAG流程。查询进来时,先做意图分类,判断该走向量检索、走KG查询还是走数据库查询,或者组合使用。
Ontology RAG是KG知识库的进阶形态。普通知识图谱只有实体和关系,Ontology则额外定义了概念层级和属性约束,相当于给知识图谱加了一层"Schema"。在垂直领域里,Ontology能大幅提升图谱构建的准确率。比如医疗领域预先定义"疾病、症状、药物、不良反应"这些概念及关系类型,抽取时就不会把"发热"既当成症状又当成疾病。工程实现上,可以用LLM配合Schema约束做三元组抽取,再用Neo4j或NebulaGraph存图谱。
3.5 生成与溯源:答案不是终点,引用才是
检索做得再漂亮,最后生成环节如果处理不好,前面的努力全部白费。我拆完六款产品后发现,成熟产品在生成层的设计上有两个共性:一是Prompt模板里明确要求模型只能基于检索内容回答,二是强制输出引用来源。
Prompt模板的写法有讲究。我推荐的结构是:先给系统角色定义,再放检索到的参考内容(带编号),然后放用户问题,最后加一条硬性约束——"如果参考内容不足以回答问题,请直接说明信息不足,不要编造"。这条约束是防幻觉的最后一道防线,实测能显著减少无中生有的回答。
引用溯源是决定RAG系统能否落地的关键。业务方用RAG不是要一个"看起来对"的答案,而是要能追溯到原始文档的答案。我的做法是:检索时保留每个切块的文档路径、页码和章节标题,生成时要求模型在答案末尾标注引用编号,后端再把编号映射成具体来源。这一步让RAG从"聊胜于无的玩具"变成了"敢给业务方用的生产工具"。
4. 自研RAG的常见瓶颈与排查技巧实录
4.1 召回质量差的排查路线图
RAG效果不好,90%的情况是召回环节出了问题,不是模型不够强。我整理了一条排查路线,按顺序走一遍基本能定位根因。
第一步:检查解析结果。随机抽几份文档,看原始文本有没有乱码、表格有没有错位、扫描件有没有漏字。这一步的问题最隐蔽也最致命,因为解析是静默失败的,你很难发现某页内容其实根本没进知识库。
第二步:检查召回测试。用同一套Query分别测向量检索、关键词检索、重排后的结果。如果向量检索结果差,大概率是Embedding模型与领域不匹配;如果关键词检索结果差,大概率是文档分词有问题;如果两路都好但重排后变差,那就是重排模型或候选集大小没调对。
第三步:检查切块策略。单独抽一个召回失败的案例,人工看切出来的块是否语义完整。如果块里混入了表格残留、页眉页脚,或者把一个完整的案例拆成了两半,问题就出在切分策略上。
第四步:检查Prompt。召回没问题但答案差,那就是生成环节的问题。把Prompt里塞入的检索内容打印出来人工读一遍,如果内容本身是对的,那就是Prompt约束不够,或者模型对引用格式理解有误。
4.2 分块参数怎么调:一份可抄作业的配置表
我在拆解产品时记录了一批可用参数,整理成表格供直接参考。注意这是起点值不是最优值,实际场景要在基线值上做对照实验调整。
| 场景 | chunk_size | overlap | 切分策略 | 检索方式 |
|---|---|---|---|---|
| 财报/研报等长文档 | 500字 | 80字 | 按标题层级+段落 | 混合检索+重排 |
| 合同/法务条款 | 300字 | 50字 | 按条款编号切分 | 关键词优先+重排 |
| FAQ问答对 | 整条不切 | 0 | 按问答对独立存储 | 向量检索 |
| 产品手册 | 400字 | 60字 | 按章节+表格保留 | 混合检索+重排 |
| 代码文档 | 200行 | 20行 | 按函数/类切分 | 关键词优先 |
调参的通用经验是:先固定其他变量,只调一个参数,用一组标准Query跑两遍对比效果。我习惯准备20到30条真实业务Query,其中包含精确匹配、语义泛化、多跳推理三种类型,作为回归测试集。每次改完参数都跑一遍测试集,记录Top1命中率和答案人工评分,比凭感觉调靠谱得多。
4.3 在Mac上本地搭建RAG知识库的注意事项
不少读者问过我在Mac上怎么搭建RAG知识库。我踩过一轮坑,说几个重点。硬件上,Apple Silicon Mac建议内存16G起步,8G能跑但很勉强。模型方面推荐用Ollama跑本地Embedding和LLM,Embedding用BGE-M3的中文优化版本,生成模型用Qwen2.5系列70亿参数版,效果和资源占用比较平衡。
向量库的选择上,轻量场景直接上ChromaDB或LanceDB,Python装个包就能跑,零运维成本。数据量超过十万级再考虑Docker跑Qdrant或Milvus。很多教程一上来就让装Docker Desktop,其实小规模验证完全没必要,纯Python方案够用且省内存。
Mac上最麻烦的是PDF解析。macOS自带的文本抽取对扫描件无能为力,建议直接集成PaddleOCR。注意PaddlePaddle在Mac上的安装依赖较多,建议单独建虚拟环境。还有一个坑是中文路径问题,文档名和目录别用中文和空格,部分库在解析中文路径时会有编码问题,排查起来非常费劲。
4.4 多模态问题:RAG知识库能存储图片吗?
这是被问得很多的问题,答案是能,但要看你要哪种"能"。目前的实现思路分三种。第一种是图片先转文本:OCR提取图片里的文字,图像描述模型(比如CogVLM、Qwen-VL)生成图片内容描述,再把文本描述作为切块存入向量库。这种方案最简单,适用于截图、扫描件、含文字的产品图。
第二种是图像Embedding:用CLIP类的多模态模型把图片本身编码成向量,检索时用户文本也编码进同一语义空间。这种适合"按语义找图"的场景,比如找一张"疑似设备故障的现场照片",但实际效果对中文场景仍不稳定,需要挑选合适的多模态模型。
第三种是混合方案:图片既存原始文件路径,又存OCR和描述文本,同时用多模态模型做一个Image Embedding存进单独索引。检索时两路同时查,用Rerank统一排序。RAGFlow就是这么做的,它把图片抽取后单独建了图片索引。自研时建议按需选择,纯文档场景用第一种就够了,涉及大量工程图纸或产品图时再上多模态向量。
最后再说一个我踩过的坑:图片存储别把二进制直接塞进向量数据库,大部分向量库对BLOB支持很差。正确做法是图片存在对象存储或本地文件系统,向量库里只存文件路径和Embedding向量。
我个人在拆解这六款产品之后最大的体会是:RAG的难度不在任何一个单点技术上,而在把所有环节咬合起来的系统工程能力。文档解析、切分、检索、重排、生成,每一层都有现成的开源方案,但把它们拼成一个稳定可用的系统,需要的是对细节的敬畏和对数据流的清晰认知。如果你正在自研RAG,我建议按这篇文章的八层架构先把最小闭环跑通,再一层一层做深。别一上来就追求炫技,先把地基打牢,效果自然会上来。