聊一个我最近反复在做的功课:当你已经决定不直接用开源RAG产品,而是准备自研一套RAG服务时,第一步应该做什么?我的做法是,先把当前一线开源RAG产品拆开看一遍。这个“拆”不是看热闹,也不是为了改个皮肤抄代码,而是带着问题去逆向工程它们的架构决策。我前后对比了Dify、FastGPT、RAGFlow、QAnything、Haystack、LlamaIndex六款产品,每一款在RAG链路上都有自己的明确取舍。今天就把这份逆向工程结果整理成一套可以复用的自研RAG蓝图,给正在自研知识库问答的团队省点弯路。
这篇内容不打算讲“RAG是什么”这种基础概念,重点放在:产品之间到底在哪些地方花了大力气、哪些设计是公认的必选项、哪些花哨功能其实可以砍掉。如果你正处在“要不要自研”的犹豫期,或者已经拍板自研但还没想清楚架构,这篇文章应该能给你一张比较清晰的地图。
1. 自研之前,先看清开源格局
1.1 六款产品可以分成三派
市面上的开源RAG项目数量早就过百了,但真正值得逆向研究的并不多。我选这六款,不是因为它们名气大,而是因为它们恰好覆盖了三种完全不同的实现路线。
| 产品 | 产品形态 | 核心定位 | RAG链路里最值得看的设计 |
|---|---|---|---|
| Dify | LLMOps应用平台 | 偏向搭建AI应用和Agent工作流 | 知识库配置化、检索模式可选、可视化编排 |
| FastGPT | 知识库问答平台 | 开箱即用,适合快速搭建问答应用 | 索引与检索规则分离、流程节点可编排 |
| RAGFlow | 深度文档理解型RAG引擎 | 面向真实业务文档,强调解析质量 | 版面感知解析、模板化提示词、Rerank链路 |
| QAnything | 端到端知识问答框架 | 强调本地模型配合知识库问答 | 从OCR到语义向量、重排一体,多轮处理成熟 |
| Haystack | 开发者框架 | 可组装的生产级管道 | 索引管道和查询管道彻底解耦,组件化极强 |
| LlamaIndex | 数据框架 | 面向开发者的数据连接与检索抽象 | 各类索引结构、查询引擎、可编程接口丰富 |
第一派是应用平台,代表是Dify和FastGPT。它们解决的核心问题是“让企业快速搭出一个带知识库的问答应用”,所以把大量精力花在流程编排、知识库管理界面、API输出规范上。如果你自研的目标是给内部做一个可维护的知识库,这一派的设计思路参考价值最大。
第二派是RAG引擎,代表是RAGFlow和QAnything。它们不搞花哨的应用搭建,而是死磕“文档进来之后到检索之前这段路”。RAGFlow对PDF、图片、表格做了版面级解析,QAnything在OCR和重排模型上做得非常扎实。如果你自研时遇到“资料一堆但检索效果就是不行”的困境,问题大概率出在这一层。
第三派是开发者框架,代表是Haystack和LlamaIndex。它们不绑定任何具体实现,而是提供抽象的管道、索引、检索器、查询引擎,让你自己拼装。这一派的价值在于“设计模式”,比如组件接口怎么定义、数据流怎么流转、插件怎么扩展。我自研时大量设计习惯其实是从Haystack的管道思想里借鉴来的。
1.2 逆向工程的重心不是代码,是决策
很多人一听到“逆向工程”,第一反应是把项目源代码拉下来读。但面对这些动辄几万行、十几万行的开源项目,逐行读代码是最低效的做法。我做逆向工程时,心里其实只带三个问题:
第一,它为什么选择了这个方案?比如RAGFlow为什么要自研文档解析模型,而不是直接调现成的PDF解析库?因为通用解析库在复杂版面、表格、扫描件上表现差,而知识库场景里这些又是高频需求。第二,它砍掉了什么?对比一下会发现,很多产品并没有一上来就上Agent、图谱这些概念,而是把基础检索链路做得非常扎实。第三,它有哪些共性?六款产品虽然路线不同,但都存在“文档解析层—切分索引层—召回层—重排层—生成层”这条主链路,说明这是RAG绕不开的骨架。
把这三个问题想清楚,比记住某个开源项目的某个函数要重要得多。后面几节我会沿着这些决策点逐一展开。
2. 解析与切分:召回质量的第一道闸门
2.1 文档解析的深度决定了产品水位
我见过太多自研RAG翻车的案例,最后排查来排查去,问题都出在文档解析上,而不是检索模型上。尤其是PDF,表面上看起来是文本,实际上内部结构复杂得离谱:有双栏排版、有表格、有图片注释、有页眉页脚。如果用最简单的文本抽取方式去解析,这些信息全会被打乱。
RAGFlow让我印象最深的一点,就是它把文档解析当成核心工程在做。它的DeepDoc模型能做版面分析,识别出标题、正文、表格、图片区域,再按阅读顺序重组内容。一个双栏PDF,不做版面识别就按文本流硬切,结果往往是左栏一行接右栏一行,全被揉碎了。这种数据喂给再好的向量模型也没用,因为源头的语义单元就是错的。
QAnything在这块同样花了很大力气。它对扫描件、图片类PDF做OCR识别,对表格做结构还原。我在实测中传过一些带复杂表格的技术规格书,QAnything解析后能把表格转成相对规整的Markdown格式,而某些直接抽文本的方案会把表格内容全部丢掉。所以自研蓝图里,解析层不能只做个文件格式转换,必须具备基础版面分析能力。早期可以接入现成的OCR服务或者文档解析服务,但架构上必须为“解析算法可替换”留好接口。
2.2 分块策略与块大小:不是越大越好,也不是越小越好
解析完之后就是切分。最基础的做法是按固定字符数硬切,省事但副作用明显:可能把一句话从中间劈开,也可能把一个完整的知识点切成两半。自研时我强烈建议至少用递归字符切分,优先按段落、句子、标点这些自然边界来切,而不是按字节数硬切。
更进一步的方案是结构感知切分。如果文档本身带标题层级,比如Markdown、HTML,那就按标题把内容聚成树状区块,子标题下的内容跟随父标题。这样切出来的块天然自带上下文,检索命中一个子块时,可以把父标题一起带上,回答时模型就知道这个知识点属于哪个章节,回答的条理性会好很多。
再精细一点是语义切分,比如检测文本中语义向量的突变点来决定边界。这种方案效果好,但计算成本和延迟都高,短时间内不适合作为自研第一版的选择。我见过不少团队一上来就上语义切分,结果分块环节比整个检索环节还慢,这是典型的过度设计。
块大小方面,业界常见的经验值是单块控制在256到512个token之间,重叠区设置32到64个token。但这不是死的,得看文档类型。法律条款类适合按条款编号切块,一条一议;产品手册类适合按功能模块聚合,衔接上下文更重要。判断的标准很朴素:把检索召回的那一段单独拿出来给一个人看,他能不能不借助其他信息就理解这段在说什么。如果不行,说明块切细了或者切碎了。
2.3 元信息设计:检索召回之后的最后一根稻草
解析和切分之后,每个块不能只存文本和向量,必须带上丰富的元信息。我在自研时至少会给每个块保存:文档ID、文档名称、章节路径、页码、块序号、字符数、token数。这些字段看着不起眼,实际作用非常大。
第一是引用溯源。回答里要标注“这段话来自《某某手册》第几节”,没有元信息就只能靠文本里猜,极不准确。第二是权限过滤。企业知识库经常有部门隔离需求,可能一个文档只允许部分人检索到,那就要在召回时按元信息里的权限标签做过滤。第三是召回后的上下文重组。检索到的多个块来自同一份文档时,可以按页码和章节顺序重新排列,而不是按相似度分数硬排,这样组装出来的上下文更像一篇连贯的文章,而不是一堆孤立的碎片。
有个容易踩的坑:切分时如果改了文本内容,比如去掉了换行符、把多个空格压缩成一个,那么块和原文之间的映射关系就断了,后面要做引用精确到原文段落时非常痛苦。我建议每个块都保存它对应原文的起始偏移和结束偏移,哪怕第一版用不上,后面做高亮显示和精确定位时也会感谢这个设计。
3. 混合检索与重排序:六款产品共同的胜负手
3.1 为什么单靠向量检索远远不够
很多人以为RAG的核心就是Embedding加向量数据库,但真做了会发现,单纯向量检索在知识库场景里表现并不稳定。向量模型对语义相近的内容很敏感,但对专有名词、型号编码、工单编号这类精确信息经常“视而不见”。
举个实际例子:文档里写着“型号XYZ-2000的设备需要定期校准”,用户提问是“XYZ2000怎么校准”,如果Embedding模型把型号编码当噪声处理了,这个文档块可能根本不会被召回到。这时候用关键词匹配反而能一击命中。所以Dify的知识库提供向量检索、全文检索、混合检索三种模式,FastGPT和QAnything的底层也都有类似的关键词检索通道。
我的建议很直接:自研RAG的第一版就要上混合检索,不要省这一步。向量召回负责语义相近但用词不同的情况,全文检索负责精确匹配和专有名词命中,两者取并集再统一排序。全文检索可以直接用数据库自带的全文索引,比如PostgreSQL的tsvector,也可以单独挂一个Elasticsearch。小规模场景用数据库自带功能就够,别为了关键词检索专门引入一套ES,徒增运维成本。
3.2 Rerank是投入产出比最高的一个环节
召回阶段为了召回率,通常会把候选块放宽到几十甚至上百个,但真正塞进大模型上下文里的只能有三五个。如果不做重排,只按向量相似度取TopK,会出现一种常见病:召回的块看着都相关,但最该用的那个排在第十位,前三个都是似是而非的内容。
解决这个问题要靠Rerank模型。Rerank用交叉编码器把用户查询和候选块一起输入模型,算出更精准的相关性分数。通俗点说,向量检索是“先把可能相关的先捞上来”,Rerank是“在捞上来的里面仔细打分排序”。QAnything和RAGFlow都把Rerank放在链路的核心位置,FastGPT也把重排选项开放给了使用者,这说明行业共识已经非常一致。
Rerank模型的选择上,中文场景优先考虑BGE系列的重排模型,英文场景通用模型选择也很多。部署时注意Rerank的推理延迟,实测下来单次推理在几十到几百毫秒不等,需要控制并发或者做缓存。还有一个经验:Rerank不要对全部候选块做,先通过粗召回把候选压缩到50到100个,再做精细排序。这能省下不少延迟,效果差别不大。
3.3 两路召回结果怎么合并才不打架
混合检索听起来简单,做起来最头疼的是合并排序。向量相似度分数和关键词匹配分数根本不在一个量纲上,不能直接相加比较。我测试过两种主流合并方案,都有各自的适用场景。
第一种是加权线性融合。把向量分数和全文检索分数各自归一化到0到1区间,再按权重相加。好处是可以精细调参,坏处是权重对数据分布敏感,换一批文档可能就要重新调。第二种是RRF(Reciprocal Rank Fusion),不直接用分数,而是按排名位置算倒数,两路结果按排名加权融合。RRF的好处是无需归一化,对异常值不敏感,实现起来也简单,实测在大多数场景下效果都很稳。
我个人更倾向于第一版用RRF,参数少、稳定,等积累了足够的线上检索日志之后,再根据实际效果换加权融合。切记不要在还没上线的时候花太多时间去调融合权重,因为你没有真实数据,调出来的参数大概率是自欺欺人。
4. 从检索到生成:上下文组装与链路协同
4.1 上下文组装不是简单把结果拼在一起
检索完成之后,有了几个候选块,很多人就直接把它们拼成一大段文本丢给大模型。这么做能用,但效果通常不如预期。原因在于,模型需要的不是“相关片段的大杂烩”,而是“逻辑上连贯的素材”。
我在自研时采纳了Haystack管道思想里的一个习惯:组装上下文之前,先对候选块做一轮清洗和排布。首先是去重,同一个来源的块可能被两路检索各召回一次,合并前必须按块ID去掉重复。其次是按文档逻辑重排,如果候选块来自同一份文档,尽量按原始章节顺序组织,而不是按相似度分数降序组织。最后是精简,如果检索到的块里混入了纯导航内容或页眉页脚,要能识别并剔除。
还有一类比较容易忽略的情况:一个知识点可能跨越多个相邻块。比如某个方案说明在块7只讲了一半,关键参数在块8里。如果组装上下文时只选了检索分最高的块7,模型就缺了另一半信息,回答自然不完整。所以组装策略里最好带一个“邻居扩展”能力:当选中某个块时,允许按原始顺序带上它前后相邻的一到两个块,作为上下文补充。这个能力放在知识库问答场景下,效果提升非常明显。
4.2 引用溯源不是加分项,是企业落地的底线
我在帮团队做RAG自研时发现,凡是面向真实业务使用的知识库,引用溯源都是硬需求,不是可选需求。用户问到的每一个回答,系统必须能说清楚“这句话是从哪份文档、哪个章节里来的”。这里面有两个层面的问题要处理。
第一是提示词约束。要在System Prompt里明确要求模型在作答时,只参考给定材料,并且回答中标注来源编号,比如“根据材料[1]和材料[2]”。第二是结果后处理。模型输出的“材料[1]”要能映射回真实文档ID和块ID,API返回结果里要有结构化的引用列表,而不只是文本里的标注。
这块容易踩的坑是:模型经常自作主张把不存在的来源编出来。我的应对方式是在提示词里强制要求“如果答案无法从给定材料生成,直接回复无法回答,不要编造来源”,并且在后处理时校验引用编号是否超出实际提供的材料范围。简单说,就是把引用当结构化数据来校验,而不是只当文本来看。
4.3 从固定链路走向Agent化编排
传统RAG的链路是“查一次、拼一段、答一次”,简单直接。但真实问题往往没那么乖巧:用户问“帮我对比一下A和B两款设备的参数差异”,这种问题可能需要在多个知识库板块分别检索,然后汇总比较。再比如多轮对话中,用户说“那它的维护周期呢”,这个“它”指代的是上一轮的某个设备,孤立检索当前问题根本查不到。
六款产品里,QAnything在多轮对话上做了不少工作,它对历史对话的压缩和指代消解做得比较成熟。Dify和FastGPT也都开始在流程编排上加入条件分支、工具调用,本质上就是向Agent方向演进。
我自研蓝图里特意把编排层设计成可插拔的:第一版可以是一条简单的“检索—组装—生成”固定管道,但接口上留出对话改写、意图识别、工具调用这些扩展点。这样后面要升级成Agent形态时,不需要推翻重来,只需要在管道里插入新的节点。切记不要在自研第一天就想着做Agent,那是给自己挖坑,先把固定链路跑通、业务验证有价值了,再一步步扩展。
5. 逆向工程后的自研蓝图:服务边界、数据模型与演进路径
5.1 模块划分与数据流设计
综合六款产品的共性,我整理了一套适合中小团队自研的服务划分方案,总共六个模块:
- 接入与解析服务:负责文件上传、格式识别、文档解析、版面分析、切分。
- 向量化服务:负责调用Embedding模型,把文本块转成向量。
- 存储层:向量数据库加关系型数据库配合,前者存向量,后者存文档、块和任务状态。
- 检索服务:负责混合召回、Rerank、结果合并与过滤。
- 编排服务:负责对话管理、上下文组装、引用映射,未来扩展Agent节点。
- API与应用层:对外提供统一接口,面向Web端、IM端或其他业务系统。
数据流走的是单向管道:文件进入接入解析服务后,产出结构化的块数据;块数据交给向量化服务生成向量;解析结果和向量分别写入关系库和向量库;用户提问时,检索服务从两路存储中召回候选块,经过Rerank后交给编排服务;编排服务组装上下文,调用大模型生成答案,最后把答案和引用返回给应用层。
这个设计里最忌讳的是模块之间互相调内部表。比如检索服务直接去查解析服务的临时表,短期看着方便,后面一改动就全乱套。哪怕第一版简单点,也建议各服务之间只通过接口通信,数据通过明确的DTO传递。
5.2 核心表结构与状态管理
存储层建议至少设计四张核心表:数据集表、文档表、分块表、解析任务表。
数据集表记录每个知识库的配置,包括使用的Embedding模型、检索模式、Rerank开关等。文档表记录文件本身的信息,包括文件路径、解析状态、切分参数、来源URL。分块表是核心,字段上除了内容、向量、元信息之外,建议加上唯一的块哈希,用于识别内容变更。解析任务表是容易被忽略但非常重要的表,因为文档解析不是瞬时完成的,大文件可能耗时几十秒甚至几分钟,异步任务的状态必须被记录。
解析任务的状态机建议这样设计:pending(等待解析)→ parsing(解析中)→ indexing(向量化中)→ ready(完成)或 failed(失败)。失败的任务要记录失败原因,并且支持重试。这个状态机的价值在于,当用户在界面上传一批几十个文件时,系统可以展示每个文件到了哪一步,哪一个失败了、为什么失败。没有这套治理机制,批量导入基本就是噩梦。
有一个经验值得强调:解析和向量化要设计成可断点续跑的。假设你第一批导入了200个文档,向量化跑到一半服务崩了,重启后应该能从断点继续,而不是全部重来。实现上很简单,只要向量化任务也走状态表,每次启动时捞取未完成的任务继续处理即可。
5.3 模型选型与向量数据库的选择策略
模型选型是自研RAG里最需要务实权衡的部分。嵌入式模型的路线,中英双语场景首选开源的BGE-M3或BGE系列中文模型,都能本地部署,避免数据出域。如果预算充足、数据允许走云端API,也可以选商业Embedding服务,但自研知识库通常都有数据合规要求,我见过的多数团队最后还是选择了本地模型。
维度是一个关键参数。BGE-M3输出1024维向量,相对早期的768维模型在细粒度语义上表现更好,但占用存储空间也更大。一个小规模知识库可能几万条块记录,感觉不到差别,但到了百万级向量规模,存储和检索延迟就会有明显压力。第一版不用过度纠结,选择主流模型,但要保证向量维度这个参数可配置,后面换模型不用改代码。
向量数据库的选择,我按规模给三个档位的建议。十万级向量以下,直接用PostgreSQL加pgvector插件,省掉一个独立中间件;十万到百万级,可以考虑Milvus或者Qdrant,支持标量过滤和混合检索;百万级以上,才会需要认真设计分片和集群。绝大多数企业内部知识库连十万级都到不了,直接上独立向量数据库属于给自己加戏。
5.4 MVP演进三阶段的路径规划
蓝图给得再完整,也不能第一天就全量落地。我建议按三个阶段推进。
第一阶段目标是跑通闭环:文件上传、解析切分、向量化、单路检索、生成答案、引用溯源。哪怕检索只用向量方式,哪怕切分只用递归字符切分,都没关系,关键是先把端到端链路打通。这个阶段的产出是“能用的最小系统”。
第二阶段目标是提升命中率:加入全文检索通道,实现混合召回;加入Rerank节点;把切分从固定长度升级为结构感知切分;开始积累评测集。这个阶段会有比较明显的效果跃迁。
第三阶段目标是精细化:引入Agent化编排,支持多轮改写、多路检索、工具调用;建立完整的评测和监控体系,线上检索日志回灌评测集。这个阶段系统才算真正达到生产可用标准。
切记不要跳阶段。我在实际项目里见过一支团队第一版就上了Agent加知识图谱,结果光排查链路问题就花了几周,核心的检索质量反而没人关注。地基都没打牢,楼层盖得再花哨也立不住。
6. 避坑实录:常见故障与我的实操心得
6.1 常见故障速查表
| 现象 | 可能原因 | 优先排查方法 |
|---|---|---|
| 检索结果经常为空 | 向量索引未创建、切分粒度太小、关键词完全缺失 | 检查索引任务日志,用单条文本直接查向量库确认是否有写入 |
| 回答张冠李戴 | 切分粒度过大导致块内混入不相关内容,或Rerank候选范围太小 | 查看命中的几个块原文,判断是不是切分把不同主题揉在了一起 |
| 知识更新后检索不到新内容 | 增量任务失败或未触发,向量库存在旧版本缓存 | 检查解析任务状态表,确认向量化节点是否处理了新文档 |
| PDF表格内容丢失或乱码 | 解析方案对表格支持不足,表格被当纯文本抽取 | 换用带表格识别能力的解析方案,或对表格类文档单独走OCR流程 |
| 回答时快时慢、不稳定 | Rerank推理耗时高,或大模型并发受限 | 给Rerank加缓存和并发控制,分开统计每段耗时定位瓶颈 |
| 引用来源指向不准确 | 块和原文偏移没有记录,或提示词未约束来源 | 后处理校验引用编号,确保块ID和文档ID映射一致 |
6.2 几条写在最后的实操心得
第一个心得,评测集必须在第一天就开始攒。不要等系统上线了才想“效果到底好不好”。我自己的做法是准备一份百条左右的人工标注评测集,覆盖常见问题、模糊问题、专有名词问题、多轮问题,每次改动模型或检索策略,都拿这份评测集跑一遍,用命中率和人工打分对比改动前后效果。没有评测集,你做的任何优化都是盲人摸象。
第二个心得,多轮对话的检索一定要做问题改写。用户说“那它的维护周期呢”,你要先把“它”还原成设备名,再去做检索。实现方案可以用大模型做改写,也可以用规则匹配上下文实体。第一版用规则就够,后面再上模型改写,成本低而且效果稳定。
第三个心得,不要试图一次把检索命中率做到100%。RAG系统的效果上限不仅取决于算法,还取决于源文档的质量。如果源文档本身就没有那个信息,任何检索和生成技巧都无法无中生有。所以与其死磕模型参数,不如花时间整理知识库的源文档,把过时的、重复的、残缺的内容清理掉。
第四个心得,生产环境一定要留检索日志。把每次用户提问、命中的块、排序分数、最终答案都记录下来。这些日志有三个用途:一是排查线上问题,二是积累真实数据优化Rerank和融合权重,三是反哺评测集。我见过很多团队把精力全花在预处理和模型调优上,却忽略了这份最便宜、最真实的资产。
最后一个感受,自研RAG最大的风险不是技术做不到,而是低估了链路之长、问题之多。文档解析、切分、召回、重排、上下文组装、引用溯源、评测迭代,每一环都需要持续打磨。开源产品的存在不代表你不需要自研,但如果没有先把这些开源产品拆透、想清楚它们为什么这么做,自研大概率会踩着它们已经填平的坑再走一遍。这份蓝图只是一个起点,真正的功夫在后期的数据治理和评测循环里。