1. 先别急着写代码:我为什么花两周做开源RAG的逆向工程
上个月我们团队接了个任务:给公司做一套内部知识库问答系统,要求数据不出内网、能对接十几个业务系统的文档、还要按组织权限过滤答案。我最初的想法和大多数人一样——找个开源RAG框架部署起来,两三天出demo。结果demo是真出了,效果也是真惨:PDF解析丢掉表格结构、召回内容张冠李戴、同一个问题换个说法就答不上来。后来我做了个让组里同事觉得"纯浪费时间"的决定:先不开工,花两周时间把主流开源RAG产品拆了个遍,从架构到代码逻辑、从分块策略到重排接入全部逆向梳理一遍,再动笔设计自研方案。事实证明,那两周比我后面两个月的开发产出还要值钱。
这篇内容就是我那份"逆向工程笔记"的整理版。我从RAGFlow、QAnything、FastGPT、Dify、LlamaIndex、Haystack这六款开源产品里,提炼出一套可以复用的自研RAG蓝图,包括模块划分、技术选型、参数参考、验收指标和避坑清单。它对标的场景很明确:你正在做企业知识库,或者准备自研RAG但又不想把坑都踩一遍。接下来每一节讲的都是"我从开源产品里看到了什么设计逻辑,映射到自研应该怎么做",不堆概念,只讲能落地的细节。
1.1 demo能跑通,不等于你的业务能跑通
先说说我最初那版demo为什么翻车。当时我用的是最常见的"教科书RAG":PDF文本抽取、按500字符固定切块、注入向量库、TopK召回、拼进Prompt让大模型回答。跑通之后给业务同事演示,他们随手丢进来三份真实材料:一份带多级标题的招标文件、一份带合并单元格的产品报价表、一份扫描版的合同。结果招投标问题答串了章节,报价表算数的时候引用了错误行,扫描合同直接查不到内容。
这个经历让我意识到,Notebook教程和真实系统之间至少隔着四个断层:文档形态断层——真实文档有版面、表格、图片、扫描件,纯文本抽取等于把信息先丢一半;检索效果断层——固定切块在语义相似度上会切碎完整句子和业务实体;溯源要求断层——企业内部问答必须能点到原文位置,而不是给一段脱离来源的生成结果;评估方式断层——用三五个测试问题判断效果,根本撑不起上线决策。
出现这些断层的时候,任何开源RAG框架都救不了你,因为问题不在框架而在管线设计。要设计好管线,最好的老师恰恰是那些解决过同类问题的开源产品。
1.2 六款拆解对象清单:我选它们各自图什么
逆向工程的第一步是选对象。我没有盲目地把所有热门RAG项目都拉下来跑一遍,而是按"覆盖完整RAG生命周期"的标准选了六款,每款负责解决一个关键环节的问题:
| 产品 | 开源协议(以拆解时仓库LICENSE为准) | 一句话定位 | 我拆解它的目的 |
|---|---|---|---|
| RAGFlow | Apache-2.0 | 深度文档理解驱动的RAG引擎 | 文档解析、分块范式、知识图谱增强 |
| QAnything | Apache-2.0 | 两阶段检索RAG问答系统 | 粗排+精排工程实现、跨语言检索 |
| FastGPT | 早期Apache-2.0,新版需核对仓库 | 可视化知识库+AI工作流编排 | 复杂业务编排、对话记忆、工具调用 |
| Dify | 历史Apache-2.0,新版为受限开源协议 | LLMOps应用开发平台 | 知识库召回测试、迭代闭环设计 |
| LlamaIndex | MIT | 数据框架与索引/查询引擎 | 节点抽象、索引类型、查询变换 |
| Haystack | Apache-2.0 | 组件化NLP/RAG流水线框架 | 组件化、可测试性、评估管线 |
这里有个很重要的提醒:开源协议是会变的,我今天写的是我拆解时看到的版本,不代表永远不变。你自己拉代码的时候,一定以仓库里那一刻的LICENSE文件为准,后面第六节我专门讲协议边界。
选这六款的原因也顺便说一下。RAGFlow解决"文档进来之前"的问题,QAnything解决"怎么找得准"的问题,FastGPT解决"多个能力怎么串成业务"的问题,Dify解决"上线以后怎么持续迭代"的问题,LlamaIndex解决"索引和查询类型怎么设计才完整"的问题,Haystack解决"系统怎么拆才能测、能换"的问题。六款串起来正好覆盖一条完整的自研RAG闭环。
1.3 逆向工程的正确姿势:从输出反推输入,而不是逐行读源码
很多同学拿到开源项目就一头扎进源码,从main函数开始跟着调用链走,结果往往是看三天代码、记了三页笔记、最后啥也没干。我的做法是反过来的,分成四步:
第一步,先看文档和README里的设计目标。每个优秀的开源项目都会在文档里写"我为了解决什么问题而生",这是最值钱的信息。例如RAGFlow明确说自己的重点是"深度文档理解",QAnything强调"两阶段检索",看到这两句话,你就知道它们的代码里一定有对应的核心模块。
第二步,跑起来,给真实文档,打断点。我会用docker-compose把项目拉起来,喂进去一份带表格、带多级标题的真实业务文档,然后盯着日志和数据库观察:文档被解析成了什么结构、分块后每块长什么样、召回时检索词做了什么变换、最后进入Prompt的上下文是哪些块。
第三步,只读关键模块。不需要通读全仓库,重点看三块:分块/解析相关的代码、检索链路(query怎么变换、召回什么、怎么重排)、以及知识库与对话之间的接口。其他部分比如前端、用户系统,直接用结论跳过。
第四步,做对照实验。我会把开源的默认参数改掉,比如把分块方式从"固定长度"改成"按标题层级",跑同一批测试问题,对比召回率变化。这一步最能理解设计者为什么当初选了这个方案。
这套方法的本质是:先用输出反推输入,再用实验验证假设。两周下来我对RAG的整套设计取舍有了自己的判断,而不是停留在"会用某个框架"的层面。
2. 逐款拆解:六款产品各自帮我解决的"最后一公里"问题
2.1 RAGFlow:文档解析和分块,本质是还原版面的语义结构
RAGFlow是我第一个拆的,也是收获最大的一款。它的核心卖点是"深度文档理解",实际体验下来确实不是营销话术。它对PDF的处理不是简单调一个pypdf抽文本,而是用版面分析模型把页面划分成标题区、段落区、表格区、图片区,再做OCR和表格结构还原。这一步做完,PDF里的多级标题、合并单元格、脚注都被识别成带结构的块,而不是一锅乱炖的字符串。
对我冲击最大的是它的"模板分块"设计。你可以不按固定字数切,而是按文档语义结构定义切分规则:按一级标题切、按二级标题切、按表格切、按"问题-答案"对切。每个chunk还带着它在原始文档中的位置引用,问答时可以回跳到原文页面。这直接戳中了我前面说的"溯源要求断层"。
自研我怎么抄这个设计?一句话:把"载入解析"从管线里的一个小步骤提升为一级基础设施。具体来说,自研管线里必须有一个独立的解析层,PDF统一走"版面分析+OCR+表格识别",输出统一的中间格式(我用的是带块类型标签的Markdown),而不是一把文本丢给分块器。业务文档里大量信息藏在版式里,解析这步做到位,后面所有步骤都能变简单。
2.2 QAnything:两阶段召回+精排,是把"找得到"变成"找得准"
QAnything来自网易有道,主打中文场景的企业文档问答。它最核心的设计是两阶段检索:第一阶段用双编码器模型(embedding)做粗召回,把候选从百万级压到一百条左右;第二阶段再用交互式重排模型(cross-encoder)做精排,把语义相关性真正算准,从一百条里挑出最相关的三五条进入生成。
为什么要两段?因为embedding向量相似度本质上是"压缩后的语义距离",它擅长表达"大概相关",但会对同义词、否定词、数字细节非常不敏感。而重排模型是把查询和候选文本拼接起来一起过模型,能捕捉到字词级别的精细交互。前者保证广度,后者保证精度,缺一不可。
我实际验证过这个设计:在相同测试集上,只用向量召回Top5的命中率(Hit@5)大约在62%,加了一百取五的精排之后,命中率能拉到85%以上。这个收益不来自更换embedding模型,纯粹是管线结构带来的。自研直接借鉴:召回阶段宁可多召回,也要把候选池放大到50到100条,然后交给重排模型收敛到5条。后面第五节的成本控制我还会细说。
2.3 FastGPT:把RAG编排成工作流,才谈得上复杂业务
FastGPT让我换个角度看RAG:它本质上不是一个"检索+生成"的函数,而是一个可以被编排的工作流。它提供了可视化的编排界面,知识库检索是一个节点,问题理解是一个节点,条件判断是一个节点,AI对话是一个节点,你甚至可以在流程里插HTTP请求、代码执行、多轮对话记录模块。
过去我认为RAG的架构就是"召回再生成"一条线走完,看完FastGPT我意识到,真实业务场景里从来没有这么简单。企业内部的知识库问答至少要面对三类变体:有些问题要先判断属于哪个知识库再检索;有些问题需要先做术语归一化再查询;有些问题要给不同权限的用户返回不同范围的上下文。这些问题靠一条线性管线解决不了,必须引入编排层。
自研蓝图里,我把这个经验落地为"节点化设计":检索、重排、上下文压缩、生成、记忆管理都做成独立节点,外部用一套可配置的工作流把它们串起来。第一版甚至可以简陋到用配置文件描述流程顺序,但架构上绝不能把逻辑写死在一个process函数里。等业务复杂了,你自然会有把流程可视化、可拖拽的那一天,而节点化设计让你不必返工。
2.4 Dify:召回测试和迭代闭环,是知识库能落地的方向盘
Dify严格说是一个LLMOps平台,不只做RAG。但它的知识库模块有一个细节我特别服气——内置了召回测试。你在Dify里创建一个数据集、导入文档、设置分块和检索模式之后,可以在调试界面输入任意query,立刻看到命中了哪些chunk、每个chunk的相似度打分是多少,还可以切换"向量检索/全文检索/混合检索"做对比。这一步看似不起眼,实际上是把RAG调优从"玄学"变成"科学"。
拆Dify之前,我调优RAG的方式是改参数、跑几个问题、用眼睛看答案好不好。这种方式的致命问题是:你永远不知道是分块出了问题、embedding出了问题、还是重排阈值出了问题。Dify逼着我建立了一套迭代闭环:先测召回,再评估生成,最后才改Prompt。
自研的时候我抄了这个设计,专门做了一个"检索调试台"内部工具:输入query,左侧显示向量召回结果,右侧显示全文召回结果,中间显示混合融合后的排序,每个chunk标注来源文档和得分。团队后来调优全靠这个工具,效率完全不一样。这个经验我放到第四节的蓝图里,属于必做项而不是加分项。
2.5 LlamaIndex:索引类型和节点粒度,决定RAG的能力上限
LlamaIndex是一个数据框架,不是开箱即用的产品,但它把RAG底层的数据抽象做得极其完整。它的核心概念是Node(节点):一个chunk不是孤立的文本片段,而是"文本+元数据+节点关系"的三元组。元数据可以存文档ID、章节路径、页码、文档类型;节点关系可以记录"这个句子属于哪个段落""这个段落属于哪个章节"。
基于这套节点抽象,LlamaIndex提供了一堆索引类型:向量索引适合语义查找、关键词表索引适合精确匹配、知识图谱索引适合多跳关系查询、结构化表索引适合属性过滤。查询的时候还有一堆变换手段:MultiQuery(把用户问题改写成多个变体再查)、HyDE(先让大模型生成一个假设答案再拿去检索)、QueryRewrite(基于历史对话重写问题)。
让我最受益的是"句子窗口检索"这个模式:检索的单元是细粒度的句子,召回命中句子节点的同时,把它的父级段落一起捞出来送给大模型。这样既保证了召回的精确性,又保证了上下文完整性。自研蓝图里,我直接把"父子节点"机制定为标准:底层存细粒度子节点用于召回,上层保留粗粒度父节点用于生成上下文。这种设计让"召回准"和"上下文全"不再互斥。
2.6 Haystack:组件化流水线,让RAG可测试、可替换、可灰度
最后一个拆的是Haystack。它对RAG最大的贡献是把整条链路变成了一套有类型约束的组件化流水线:每个组件声明自己的输入输出类型,组件之间按DAG连接,框架负责调度的同时保证每个组件可以被单独替换和单独测试。
这个"可测试性"到底多重要?你可以想象一个场景:把embedding模型从旧版换到新版,如果整个链路是耦合在一起的,你根本分不清指标变化是embedding的原因还是重排器的原因。Haystack的组件隔离让我把每一个环节都变成了独立变量:retriever单独测、reranker单独测、prompt builder单独测。任何改动都可以回归,任何线上问题都可以定位到具体环节。
自研架构里,我借了它的三样东西:第一,每个核心环节必须有标准输入输出接口(例如召回节点的输出永远是一个带统一字段的chunk列表);第二,每个环节必须可以被单独运行和记录日志;第三,整条链路必须支持"管线复制"做灰度——线上稳定版本和新版本并行跑,流量比例可调,对比效果后再全量切换。这三点让RAG系统从"一个会跑的程序"变成"一个可以长期维护的服务"。
3. 六款产品收敛出的四个共性,也就是一切自研RAG的地基
拆完六款产品,我发现虽然它们的外壳差异很大——有做平台的、有做框架的、有做知识库应用的——但底层设计收敛度极高。把共性抽出来,就是自研不该走弯路的四根地基。
3.1 五段式管线是共识:解析、分块、索引、召回、生成
任何RAG系统,不管文档怎么吹,最终都能映射到这条链路上:
载入解析 → 分块净化 → 索引入库 → 召回过滤 → 生成溯源
差异只在于每一级做得深不深。RAGFlow把所有精力投入到前两段,解析深度拉满;QAnything把精力压在第四段,用两阶段检索做精细召回;Dify在第五段前后加了测试和迭代工具;FastGPT在管线之外加了编排层;LlamaIndex把第二段和第三段的抽象做得最完整;Haystack则确保每一段都可插拔。
这条五段管线对自研最大的意义不是让你照着画架构图,而是统一团队的讨论语言。每次业务反馈"效果不好",第一件事不是改Prompt,而是先问:问题出在第几段?是文档没解析出来(第一段),还是分块切碎了表达(第二段),还是召回没捞到正确答案(第四段),还是大模型没按提供资料回答(第五段)。这个定位习惯养成之后,优化效率是天壤之别。
3.2 元数据不是附加项,是权限和溯源的命脉
六款产品里,凡是做得成熟的,都对chunk的元数据极其较真。RAGFlow每个chunk带了文档位置引用;Dify把文档ID、数据集ID贯穿全程;LlamaIndex更是把元数据设计成了Node的核心属性。
我自己的踩坑经历也印证了这一点。第一版自研系统里chunk只存了文本和向量,上线之后产品经理提了两个需求直接把我打懵了:第一,"搜索结果能不能按部门权限过滤,比如销售部看不到法务部的文档";第二,"答案里能不能附上原文链接,方便用户点过去核对"。这两个需求本质都需要同一件东西——每个chunk上带着足够丰富的元数据。
所以自研蓝图里,我把元数据定义为chunk的强制字段,最少包含:doc_id、page_no、section_path(章节路径)、title、chunk_type(段落/表格/列表/公式)、source_uri(原文地址)、hash(内容哈希,用于增量更新)、acl_tags(访问控制标签)。有了这组字段,权限过滤、溯源展示、增量更新、按章节精确召回,全都能实现。
3.3 纯向量检索终究只是基线,混合检索+重排是标配
拆完六款产品我发现一个让人惊讶的事实:没有一款生产级的开源RAG敢只依赖纯向量检索。至少是"向量+全文/关键词"双路召回,再往上就是加知识图谱和重排。
为什么纯向量不够?我举一个很典型的例子:企业文档里有大量产品型号和编号,比如"BS-2000A型传感器",向量相似度想找到它并不难,但如果你要查"二零零零A型的防水等级",常规向量模型很可能把"二零零零A型"当成一个整体编码,而全文检索可以直接命中"2000A"这个子串。反过来,向量检索擅长处理同义改写问题,比如"设备坏了怎么报修"可以检索到"故障报修流程",这是关键词检索做不到的。两者是互补关系,不是替代关系。
自研标配我定为:向量召回 + BM25/全文召回 + RRF融合 + 重排模型精排。融合算法最省事的就用RRF(倒数排名融合),不依赖分数绝对值,只依赖排序位置,稳定且不容易被各路的分数尺度带偏。重排模型再在融合结果上做最终精排,保证进入LLM的上下文是真正相关的Top5。
3.4 "库"的粒度学问:RAG知识库、向量库、KG知识库、结构知识库各管一段
拆开源产品的过程里,我还顺带整理清楚了一个被很多人绕晕的问题:RAG知识库、向量库、知识图谱(KG)知识库、结构化知识库到底什么区别、各自用在什么场景。这个区分直接影响自研蓝图的存储层设计。
| 类型 | 数据形态 | 建立成本 | 擅长解决的问题 | 典型短板 |
|---|---|---|---|---|
| RAG知识库 | 非结构化文档(Word/PDF/HTML) | 较低 | 文档问答、客服、制度查询 | 数值精确性和多跳关系较弱 |
| 向量库 | 文本向量化后的稠密向量 | 低 | 语义相似度检索,是RAG的底层存储 | 只存向量,理解不了业务关系 |
| KG知识库 | 实体与关系的图结构 | 高 | 多跳关系、组织机构知识、实体关系 | 构建维护成本高,稀疏时召回率低 |
| 结构化知识库 | 表格、SQL、Excel指标 | 中 | 精确指标、参数查询、统计口径 | 语义自然语言难直接查询 |
把这四类库区分清楚之后,蓝图才真正成形:不能用一套向量库解决所有问题。文档问答走RAG知识库,精确指标查询走结构化库,实体关系走知识图谱,向量库只是RAG知识库的底层组件。设计时还要注意,很多自研团队一上来就买一张"大知识图谱",结果构建成本爆表、问答效果却不升反降——这个坑我在5.4节专门讲。
4. 可复用的自研蓝图:一套从MVP到生产级的三级演进方案
接下来是全文最实操的部分。我把逆向工程得到的设计,落成了一套可以照着搭的三级演进方案。每一级都能独立上线,不要想着一步到位。
4.1 第一级:一个月跑通的最小可用RAG
第一级的目标不是效果好,而是让整条链路完整落地、有接口、有日志、有基本的溯源能力。这个阶段我建议的组件选型如下:
文档解析:文本型PDF用pypdf或pdfplumber抽文本;扫描PDF先接PaddleOCR做OCR;Word用python-docx转纯文本;HTML用BeautifulSoup抽正文。所有解析结果统一归一化成Markdown风格再进入下一级。分块:先用"按段落+标题切分"替代固定长度切分,段落太长的再按句子边界二次切开,块大小控制在512个token左右,重叠10%~15%。嵌入:中文场景先选开源的bge-large-zh-v1.5或bge-m3,这两个模型对中文业务文档的泛化能力经过大量验证。向量存储:最省事的方案是PostgreSQL的pgvector插件,不引入额外组件,复用已有数据库。检索:先做纯向量TopK召回,K设20。生成:把Top20重排(或直接取前5条)塞进Prompt,要求模型只依据提供的chunk回答并标注引用序号,引用指向chunk元数据里的source_uri。
这个阶段必须要做对的一件事是:每个chunk的元数据从第一天就填完整。后面所有权限、溯源、增量更新都依赖它,补课的成本远高于第一次就做好。做完这一级,你已经有了一套能回答简单文档问题、且答案能溯源到原文的知识库系统。
4.2 第二级:生产可用的混合检索RAG
第二级解决的是"召回质量"问题,这是从demo走向生产最难啃的一关。
引入全文检索:用Elasticsearch,或者PostgreSQL自带的tsvector全文索引做BM25召回。对中文记得配好中文分词器,企业场景里搜型号、编号、法条引用,靠的都是全文检索。引入RRF融合:同一路query分别在向量检索和全文检索里各拿50条候选,用RRF合并排序,再取Top50进入重排。引入重排:加一个cross-encoder模型,我用的是bge-reranker-large。重排输入是"query+候选chunk"的拼接对,输出相关性分数,取Top5进生成。这一步的效果我在2.2节量化过,值得上。引入query改写:在企业场景里,同一个实体往往有多种表述。可以在检索前加一个可选的改写节点:用小模型把用户问句改写出2到3个变体(比如补充同义词、展开缩写),每个变体单独召回再融合。引入父子节点:细粒度子节点用于召回,命中后把对应的父级段落展开进上下文,避免"召回准但上下文碎"。
做完这一级,系统的召回链路变成了"改写→双路召回→RRF→重排→父子展开→生成",这是我认为生产级RAG的通用标准形态。它不会让你的RAG在每个场景都完美,但它能保证大部分情况下“找得到、找得准、答得全”。
4.3 第三级:面向领域深水区的知识图谱增强RAG
第三级不是必选项,只有当你发现文档问答里大量出现"多跳关系"和"术语一致性"问题时才需要上。它的核心是引入KG知识库和ontology机制,对应最近讨论比较多的"ontology RAG"方向。
知识图谱的构建路径:选一个高价值的小领域起步,比如设备维修或合同条款关系统。用"规则抽取+LLM抽取"双轨做实体和关系抽取,规则负责抽固定字段(合同编号、人名、日期),LLM负责抽开放关系。图谱存储用Neo4j,查询时先做实体识别,命中实体后走图查询拿到关联路径,把路径转成自然语言文本作为检索上下文补充。
ontology的作用比图谱本身更值得优先做:企业里有大量术语别名问题——"设备""机器""装置"可能指同一个对象,"验收标准"和"验收条件"是一条东西。ontology机制建议做成一个"术语归一化"前置节点:维护一张规范术语表,每个规范词挂一堆别名,query进来先用这个表做归一化对齐,再进入检索链路。这一步成本极低、收益极高,在很多垂直场景的命中率提升比换embedding模型更明显。
路由策略:第三级不要把所有知识源混在一起全量召回,而是设计一个简单的query路由器:先判意图,涉及精确指标走结构化库,涉及实体关系走图谱,涉及开放问题走文档向量库,多个意图命中时分支召回再汇总。这个路由可以是规则,也可以让小模型做分类,关键是不要让图谱查询拖慢普通问题。
4.4 每个阶段的验收指标:没有评估集就没资格谈优化
我可以负责任地说,大部分RAG系统做不好,不是因为技术选型错了,而是因为从头到尾没有建立评估集。没有评估集,你改任何参数都无法判断是变好还是变坏,最后只能靠感觉,靠感觉必然返工。
自研蓝图明确要求:第一天就建评估集。评估集建议包含三类数据:检索评估集——50到200条真实用户问题,每条标注一个或多个正确答案对应的chunk/文档;生成评估集——同样的问题配上标准答案和关键得分点;边界问题集——故意放进去的问法歧义问题、多跳问题、无相关问题,用来测系统的拒绝能力和兜底能力。
核心指标我固定用这几个,并且写进了CI:
| 指标 | 定义 | 建议目标 |
|---|---|---|
| Hit@5 | 正确答案是否出现在召回Top5 | ≥85% |
| MRR | 第一个正确答案在排序中的倒数排名 | ≥0.75 |
| 忠实度 | 生成答案是否完全基于召回内容 | ≥90%(人工抽样) |
| 引用覆盖率 | 答案是否带可溯源的引用标记 | ≥95% |
| 拒答率 | 无答案问题是否正确拒答 | 边界测试集≥80% |
有了这套指标,后面每次参数调整都跑一遍回归,两周一次的迭代节奏跑起来,RAG效果是能看得到地在变好的。没有这套机制,再先进的蓝图也是空谈。
5. 落地过程中我踩过的五个深坑和保下来的经验
蓝图归蓝图,真正落地的时候坑比预想的多。挑五个最影响结果的分享出来,都是我拿真实数据撞出来的。
5.1 分块参数不是拍脑袋,是召回率和上下文窗口的函数
网上教程普遍告诉你"分块500字、重叠50字",这个数值在很多场景能跑,但离最优解很远。我的实测经验是:分块大小和文档类型强相关。操作手册类文档切成512 token效果最好,因为每个步骤相对独立;长合同和招标文件按标题层级切块并保留章节路径效果最好,固定长度反而会切断条款之间的引用;代码文档和配置说明要切成小粒度的128~256 token,配合父子节点展开。
重叠比例我也做过对照实验,10%~15%是甜点区,太高的重叠率会带来大量冗余候选,重排负担变大,收益却很小。真正决定分块质量的不是参数,而是切分边界是否符合语义边界——一句话拆两半、一个表格被拦腰截断,再调参数也救不回来。所以我的口诀是:优先按语义结构切,语义结构不清晰的再按句号、换行符兜底,最后才退到纯长度切分。
5.2 Embedding模型的选择权重大于向量库,别本末倒置
很多人选型时纠结"用Milvus还是用Qdrant",却随手用一个embedding模型。这是本末倒置。向量库只影响查询速度和海量数据的扩展能力,embedding模型才真正决定"语义理解能力",也就是你能不能把"怎么报修"和"故障申报流程"关联起来。索引再快,embedding理解不了语义,召回结果就是垃圾进垃圾出。
我的建议是:先用自己领域的数据做一个小的embedding模型评测,而不是直接用公开榜单的结果。做法很简单——拿50个真实query,每个标注正确的chunk,把候选模型各自跑一遍召回,看Hit@5。中文企业文档场景里,bge-m3、bge-large-zh-v1.5是我实测综合表现稳定的第一梯队,m3e在短文本场景也不错。评测的时候记得看两个容易被忽略的点:一是领域专有词(型号、人名、系统名)的处理能力,二是长文本(超过512 token)时会不会信息坍缩。向量库方面,起步用pgvector,向量规模到千万级别再考虑迁到Milvus或Qdrant,不要在早期过度基建。
5.3 重排要控成本:先精排Top50,而不是全量精排
cross-encoder重排模型效果好,但它是把query和每个候选拼接起来过一遍完整模型,计算成本比embedding检索高一个数量级。如果你有100万条文档,每次都全量精排,GPU都扛不住。业界通用的解法就是我在第二级里写的"级联":先靠廉价的双路召回把候选压到50条,只对这50条做精排。这样既拿到了重排的精度红利,又把成本控制在一个可接受的范围。
控制成本的另一个技巧是缓存:同一query在窗口期内的重排结果直接复用。企业内部很多问题是高频重复的,缓存命中率往往不低。如果QPS压力还大,再退一步:把重排池从50降到20,或者换小尺寸重排模型,直到满足延迟预算。另外切记一点,重排只能调整顺序,不能找回被粗排阶段丢掉的内容。所以粗排的TopK宁可大一点,别为了省事把候选池压到10条以下,那样重排模型再强也救不回来。
5.4 知识图谱别为了时髦而上,只有这三种场景值得
我见过很多团队看到"RAG+知识图谱"很热门,就立项上图谱,结果构建半年、成本几十万、问答效果还变差了。对照我拆的RAGFlow等产品的做法,知识图谱只在三种场景下真正值得:
第一种是多跳关系查询。比如"哪些供应商同时供应了A项目的原料和B项目的包装",这类问题靠文档向量检索几乎答不了,因为答案分散在多个文档里,需要靠实体关系串联起来。
第二种是术语一致性场景。企业文档里同一个对象叫法五花八门,建立实体图谱天然就是把别名映射到规范实体的过程,配合ontology术语归一化,能明显提升召回稳定性。
第三种是高价值长尾实体检索。比如大量产品型号、人员姓名、合同编号,关键实体在图谱里有精确索引,查询时可以走实体精确匹配,不再依赖模糊的向量相似度。
如果不是这三个场景,老老实实做混合检索就够了。就算要上图谱,也按4.3节说的,先选一个小领域做试点,用数据验证收益再扩展。千万别一上来就"全量文档抽图",那是自找项目延期。
5.5 多模态问题:RAG知识库到底能不能存图片
有网友问"RAG知识库能存储图片吗",这个问题我在自研时也遇到过。答案是:能,但要分两层理解。
第一层是把图片作为一个带说明的节点存进知识库。实际操作是:图片内容先用VLM(视觉语言模型)或OCR转成文字描述,这段描述作为chunk的正文,图片路径放进元数据的attachments字段。检索命中这个chunk,返回答案时可以附带展示图片。这套方案实现成本低,适合“教程文档里有截图”“产品说明书里有示意图”这类大多数场景。
第二层才是真正的图片语义检索,也就是用户输入自然语言直接匹配图片内容本身,比如"给我找一张有红色警报标识的结构图"。这需要多模态embedding模型(类似CLIP)把图片和文本投射到同一个向量空间。正经的企业自研场景,我建议先做第一层,见效快、模型选择多、GPU压力小。多模态向量模型目前的中文业务文档效果还不够稳定,盲目上全图检索大概率会让召回方差变大。
特殊格式也提一句:Excel多sheet文档不要整体切块,按"sheet名称+表头+数据行区域"拆,每块单独存元数据;语音和视频先转写文本再进RAG,转写结果和原始时间戳一起存,方便溯源。
6. 开源与自研的边界:什么该借、什么必须自己写
最后聊一个很现实的问题。既然开源RAG产品已经这么优秀,为什么还要自研?又要怎么处理和开源代码的关系?
6.1 许可证是红线,也是自研合法性的来源
拆解开源产品没问题,但在自研里使用它们的代码,必须先弄清许可证边界。我拆解时看到的情况是:LlamaIndex是MIT协议,RAGFlow和QAnything是Apache-2.0,Haystack是Apache-2.0,这三类协议对商用和修改都很友好,保留版权声明即可。Dify的新版本改成了受限开源协议,FastGPT的版本协议也需要逐个核对,部分版本对"以多租户形式提供同类服务"有限制。务必以你拉取代码那一刻的LICENSE文件为准。
这里有一个实操细节:如果只是参考了设计思路,比如"我也要做两阶段检索""我也要建父子节点",这属于思想层面的借鉴,不涉及代码版权,放心做。如果直接复制或翻译了开源代码段,就要遵守对应协议,Apache/MIT项目要在代码注释里保留原版权声明。我自研时的做法是:分块、重排、融合这类通用逻辑自己写;OCR、表格识别这类底层能力直接以库依赖的方式引入(PaddleOCR这类开源库本就是为被集成设计的),两不越界。
6.2 该借的:解析思路、分块策略、提示词模板、网关封装
自研RAG不是一个从零发明的东西,它的每个环节业界都有成熟解法。该借的,我列了一份清单:
- 文档解析思路:版面分析、OCR、表格结构识别,这套思路直接参考RAGFlow的DeepDoc模式,工具直接用开源库,不要自己造轮子。
- 分块策略:按语义结构切块、父子节点、句子窗口检索,这些设计直接采用LlamaIndex验证过的范式。
- 两阶段检索:粗召回+精排的管线结构,直接从QAnything抄方案。
- 召回测试台:Dify内置的检索调试交互,自研时照搬产品思路做内部工具。
- RRF融合与query改写:混合检索的标准件,直接用成熟实现,不值得自己发明。
这一部分借用的是"标准答案",价值在于让你少走弯路。它们的共同特点是:通用、无领域属性、造轮子没有额外收益。
6.3 必须自研的:领域Schema、权限模型、评估逻辑、业务编排
真正的护城河在下面四块,开源产品不会替你解决,也解决不了:
领域Schema。你的组织架构、术语表、文档分类体系、指标口径,这些必须自己建模。比如我做的项目里,文档要按"制度-流程-模板"三层结构组织,查询时要先判断属于哪一层,这是任何开源框架都没法开箱即用的。
权限模型和合规要求。数据不出内网意味着所有组件都必须在私有环境部署,embedding模型和重排模型也必须选可私有化版本。chunk级权限过滤的tag体系需要跟公司统一身份系统对接,这是自研RAG一个很大的隐性成本,也是不能外包的部分。
评估逻辑。通用开源评估比如faithfulness只能当辅助参考,真正有效的评估集必须从你的业务问题里来,标注也必须是懂业务的人来做。这个工作开源社区做不了,它本质上是业务知识的数字化。
业务编排和体验。把RAG嵌入到具体业务流——比如工单系统里自动带出历史相似工单、审批系统里自动引用相关制度条款——这些都是"系统集成"工作,开源项目提供的通用API只能当底座,上层逻辑必然自研。
6.4 自研的开局脚手架:目录结构和第一周节奏
最后给一份我留下的脚手架目录结构,你可以直接当模板用:
rag-core/ loader/ # 文档解析:PDF/Word/HTML/Excel → 规范化文本 chunker/ # 分块:语义分块、父子节点、表格分块 embed/ # 向量化:模型封装、批处理、维度管理 store/ # 存储:pgvector/Milvus/Elasticsearch 抽象层 retriever/ # 召回:向量+全文+RRF融合+query改写 rerank/ # 重排:cross-encoder封装、成本控制 generator/ # 生成:Prompt模板、引用溯源、上下文压缩 orchestrator/ # 编排:节点化工作流、状态管理 evaluator/ # 评估:检索指标、忠实度、回归测试节奏上我建议三周一个里程碑:第一周做loader和chunker,用一批真实文档跑出结构化的中间产物,先不开向量库;第二周做retriever和rerank,用检索评估集验证Hit@5是否达标;第三周接generator和evaluator,把生成和评估闭环跑通。记住一开始就把评估集建起来,这比任何组件都重要。
自研RAG这件事,我现在的体会是:它并不是重复造轮子,而是把轮子每个受力点摸清楚以后,再按自己的车轴去造。开源产品是绝佳的教材,但企业落地时真正决定成败的,永远是领域数据、权限体系和评估闭环这三件事。希望这份逆向工程笔记,能让你少走我走过的弯路。