1. 为什么我要花两周时间逆向拆解六款开源RAG产品
去年下半年,我所在的团队接到一个内部知识库问答系统的需求。当时摆在面前的有两条路:一是直接调用市面上成熟的商业RAG服务,二是基于开源方案自研一套。商业方案看起来省事,但数据要出内网,而且按调用量计费的模式在长期使用下成本不可控。于是我们决定自研。
自研RAG这件事,听起来门槛不高——无非就是文档切块、向量化、检索、拼Prompt、调模型这几步。但真正动手之后才发现,每一个环节都有大量的工程细节需要决策:切块粒度怎么定?向量模型选哪个?检索用纯向量还是混合检索?重排序要不要加?多轮对话的上下文怎么管理?这些问题如果没有参考答案,光靠拍脑袋,很容易做出一个"能跑但不好用"的系统。
我的做法是:先把市面上口碑不错的六款开源RAG产品拉下来,逐一部署、使用、读源码,从它们的架构设计中提炼共性,再结合我们自己的业务场景做取舍。这个过程大概花了两周时间,收获远超预期。这篇博文就是把这套逆向工程的思路和结论完整地分享出来,适合正在考虑自研RAG的开发者、需要做技术选型的架构师,以及想了解RAG工程化落地细节的读者。
2. 六款开源RAG产品的整体架构对比
2.1 我选择的六款产品及选择理由
在挑选逆向对象时,我定了几个标准:第一,必须是真正能跑起来的完整系统,而不是只提供几个函数的工具库;第二,要覆盖不同的技术路线,比如有的偏轻量、有的偏企业级;第三,社区活跃度要够,说明这些设计经过了真实用户的检验。
最终选定的六款产品,按定位大致可以分为三类:
- 轻量级框架型:这类产品的特点是代码量小、依赖少、上手快,适合快速搭建原型。它们的RAG流程通常是线性的,从文档加载到答案生成一条路走到底,没有太多分支逻辑。
- 平台级系统型:这类产品提供了完整的Web界面、用户管理、知识库管理等功能,RAG只是其中的一个模块。它们的架构更复杂,但工程化程度更高,值得借鉴的地方也更多。
- 检索增强型:这类产品在检索环节做了大量优化,比如混合检索、多路召回、重排序等,适合对检索质量要求高的场景。
注意:我这里刻意没有提具体产品名称,一方面是为了避免广告嫌疑,另一方面是因为这篇博文的核心价值在于提炼设计思路,而不是推荐某个具体工具。你在选型时可以根据自己的需求去对应这些类别。
2.2 架构共性与差异的提炼
把这六款产品的架构图铺在一起看,我发现了一个很有意思的现象:所有产品的核心流程都是一致的,区别只在于每个环节的实现深度和扩展方式。
核心流程可以概括为五个阶段:
- 文档摄入:加载原始文档,解析成纯文本,然后切分成块。
- 向量化与索引:把文本块通过Embedding模型转成向量,存入向量数据库。
- 检索:根据用户问题,从向量数据库中召回相关文本块。
- 增强:把召回结果和用户问题拼成一个完整的Prompt。
- 生成:调用大语言模型生成最终答案。
差异主要体现在三个方面:
| 对比维度 | 轻量级框架 | 平台级系统 | 检索增强型 |
|---|---|---|---|
| 文档解析 | 支持纯文本和Markdown为主 | 支持PDF、Word、HTML等多种格式 | 通常支持多种格式,且解析质量较高 |
| 切块策略 | 固定长度切块 | 支持按语义、按标题层级切块 | 支持自定义切块规则,部分支持重叠切块 |
| 检索方式 | 纯向量检索 | 向量检索为主,部分支持关键词检索 | 混合检索+重排序,多路召回 |
| 上下文管理 | 无多轮对话支持 | 支持多轮对话,有会话历史管理 | 支持多轮对话,部分支持查询改写 |
| 扩展性 | 低,修改需要改源码 | 中,提供插件机制 | 高,提供丰富的配置项和API |
这张表是我在逆向过程中逐步整理出来的,每一列都对应着我实际部署和测试后的观察。比如"切块策略"这一行,轻量级框架通常就是一个RecursiveCharacterTextSplitter搞定,而平台级系统会提供多种切块器的组合,甚至允许用户自定义切块规则。
2.3 从对比中得到的第一个关键结论
对比完这六款产品之后,我得到了第一个关键结论:自研RAG的难点不在"能不能跑通",而在"每个环节做到什么程度"。
如果只是做一个Demo,用轻量级框架的思路,半天就能跑通。但要做一个真正能在业务中使用的系统,就必须在每个环节都做深入的工程优化。比如文档解析,如果只支持纯文本,那用户上传PDF就没法用;如果切块粒度太粗,检索出来的内容就会包含大量无关信息,影响答案质量;如果没有重排序,检索结果的相关性就会差很多。
这个结论直接影响了我们后续的自研方案设计:不追求大而全,但每个核心环节都要做到"够用且好用"。
3. 核心模块的逆向分析与自研取舍
3.1 文档解析与切块:最容易被低估的环节
在逆向之前,我以为文档解析和切块是最简单的环节,无非就是读文件、按固定长度切分。但实际拆解下来,这个环节的复杂度远超我的预期。
先说文档解析。六款产品中,有三款只支持纯文本和Markdown,另外三款支持PDF、Word、HTML等格式。支持多格式的产品,在解析质量上也有明显差异。有的产品解析PDF时只能提取纯文本,表格和图片全部丢失;有的产品则能保留表格结构,甚至能提取图片中的文字。
我在测试中遇到过一个典型案例:一份产品需求文档,里面有一个关键的功能对比表格。用纯文本提取的方式,表格内容变成了一堆乱序的文字,检索时完全无法定位到正确的信息。而用支持表格结构提取的解析器,表格被转成了Markdown格式,检索效果好了很多。
再说切块。六款产品中,有四款用的是固定长度切块,只是块大小和重叠长度不同。另外两款用了更复杂的策略:
- 按语义切块:先用Embedding模型计算相邻句子的相似度,在相似度骤降的地方切分。这种方式的优点是每个块内部的语义更完整,缺点是计算成本高,而且切块结果不稳定。
- 按标题层级切块:利用Markdown或HTML的标题结构,把同一标题下的内容作为一个块。这种方式适合结构化的文档,但对非结构化文档无能为力。
我们最终的取舍是:以固定长度切块为主,辅以标题层级切块。具体来说,如果文档有明确的标题结构,就按标题切块;如果没有,就用固定长度切块,块大小设为512个token,重叠长度设为50个token。
实操心得:块大小和重叠长度这两个参数,我试过很多组合。块太小(比如256),检索出来的内容太碎,模型很难拼出完整答案;块太大(比如1024),检索出来的内容包含太多无关信息,反而干扰模型。512是我实测下来比较平衡的值。重叠长度设为块大小的10%左右,能有效避免关键信息被切断。
3.2 向量化与索引:模型选型与索引策略
向量化环节的核心决策是Embedding模型的选择。六款产品中,有三款用的是OpenAI的Embedding接口,两款用的是开源的BGE系列模型,还有一款同时支持多种模型,让用户自己选。
我在测试中对比了不同模型在中文场景下的检索效果。结论是:在中文场景下,开源模型和商业接口的差距已经很小了。BGE系列模型在中文语义相似度任务上的表现,和商业接口相比差距在可接受范围内,但成本优势非常明显。
索引策略方面,六款产品都用了向量数据库,但选择不同。有的用FAISS这种轻量级的本地索引,有的用Milvus、Qdrant这种独立的向量数据库服务。我们的取舍是:开发阶段用FAISS,生产环境用Qdrant。FAISS的好处是零依赖、启动快,适合快速迭代;Qdrant的好处是支持分布式部署、有完善的过滤和标量索引功能,适合生产环境。
这里有一个容易被忽略的细节:向量维度和距离度量方式的选择。不同的Embedding模型输出的向量维度不同,常见的有768维、1024维、1536维。距离度量方式有余弦相似度、欧氏距离、内积等。这些参数在创建索引时就要确定,后期修改需要重建整个索引。
注意:如果你用的是归一化后的向量,余弦相似度和内积是等价的。大部分Embedding模型输出的向量都已经归一化,所以直接用内积即可,计算速度更快。
3.3 检索策略:从纯向量到混合检索的演进
检索环节是六款产品差异最大的地方,也是我花时间最多的部分。
轻量级框架通常只做纯向量检索:把用户问题向量化,然后在向量数据库中找最相似的Top-K个块。这种方式实现简单,但在实际使用中问题不少。比如用户问"XX功能的参数是什么",纯向量检索可能会召回一堆提到"XX功能"但没提参数的块,因为向量相似度主要看语义整体,对关键词的精确匹配不敏感。
平台级系统通常会加入关键词检索,形成混合检索。具体做法是:同时做向量检索和BM25关键词检索,然后把两路结果融合。融合算法有RRF和加权求和两种,RRF更常用,因为它不需要调权重。
检索增强型产品还会加入重排序环节:先用混合检索召回较多的候选块(比如20个),然后用一个重排序模型对候选块进行精排,选出最相关的几个(比如5个)送给大模型。重排序模型通常比Embedding模型更大、更准,但计算成本也更高,所以只用在候选集上。
我们最终的方案是:混合检索+重排序。具体流程是:
- 向量检索召回Top-20。
- BM25检索召回Top-20。
- 用RRF融合两路结果,得到Top-20。
- 用重排序模型对Top-20精排,选出Top-5。
- 把Top-5拼入Prompt。
这套流程比纯向量检索复杂不少,但检索质量提升明显。我做过一个简单的对比测试:同样的问题集,纯向量检索的答案准确率大概在60%左右,加入混合检索后提升到75%,再加入重排序后提升到85%左右。
3.4 上下文管理与多轮对话
多轮对话是很多RAG系统的标配,但实现方式差异很大。
最简单的做法是:把历史对话直接拼到当前问题前面,一起做检索和生成。这种方式的问题是,历史对话中的无关信息会干扰检索,而且随着对话轮次增加,Prompt会越来越长,最终超出模型的上下文窗口。
更成熟的做法是查询改写:先用大模型把当前问题和历史对话结合,改写成一个个独立的、不依赖上下文的查询,然后再用这个查询去做检索。比如用户先问"XX功能怎么用",再问"它的参数有哪些",查询改写会把第二个问题改写成"XX功能的参数有哪些",这样检索时就不会丢失关键信息。
六款产品中,有三款支持查询改写,但实现方式不同。有的用规则模板,有的用大模型。我们最终选择了用大模型做查询改写,因为效果更好,而且成本可控——毕竟改写只是生成一个短查询,token消耗不大。
实操心得:查询改写的Prompt设计很关键。我试过几种写法,最终用的是:"根据以下对话历史,将用户的最新问题改写成一个独立的、不依赖上下文的问题。如果最新问题本身已经独立,则原样返回。"这个Prompt简单直接,效果稳定。
4. 从逆向到自研:一套可复用的蓝图
4.1 整体架构设计
基于前面的逆向分析,我设计了一套自研RAG的架构。整体分为四层:
- 接入层:负责接收用户请求,管理会话状态,做基础的参数校验和限流。
- 检索层:包含文档解析、切块、向量化、索引、检索、重排序等模块。
- 生成层:负责Prompt组装、大模型调用、答案后处理。
- 存储层:包括向量数据库、文档存储、会话存储。
这四层之间通过明确的接口通信,每一层都可以独立替换或升级。比如检索层,你可以先用FAISS做向量索引,后期换成Qdrant,只要接口不变,上层代码不用改。
4.2 关键模块的实现要点
文档解析模块:我用了两个解析器,一个处理纯文本和Markdown,一个处理PDF。PDF解析器选的是能保留表格结构的方案,虽然速度慢一点,但检索质量提升明显。解析后的文本统一转成Markdown格式,方便后续按标题切块。
切块模块:实现了两种切块器,按标题切块和按固定长度切块。切块器根据文档是否有标题结构自动选择。固定长度切块时,块大小512token,重叠50token。
向量化模块:封装了一个统一的Embedding接口,底层可以切换不同的模型。开发阶段用开源的BGE模型,生产环境根据成本和质量要求选择。
检索模块:实现了向量检索、BM25检索、RRF融合、重排序四个子模块。每个子模块都可以独立开关,方便做A/B测试。
生成模块:Prompt模板可配置,支持不同的模型和参数。答案后处理包括引用标注、格式清理等。
4.3 参数配置与调优经验
这套系统跑起来之后,我花了不少时间做参数调优。以下是我认为最关键的几个参数及其调优经验:
| 参数 | 推荐值 | 调优经验 |
|---|---|---|
| 块大小 | 512 token | 太小则信息碎片化,太大则噪声多 |
| 重叠长度 | 50 token | 约为块大小的10%,避免关键信息被切断 |
| 向量检索Top-K | 20 | 太小则召回不足,太大则重排序压力大 |
| BM25检索Top-K | 20 | 与向量检索保持一致,方便融合 |
| 重排序后Top-K | 5 | 太多则Prompt过长,太少则信息不足 |
| 温度参数 | 0.1 | RAG场景需要确定性答案,温度不宜高 |
这些参数不是一成不变的,需要根据你的业务场景和数据特点做调整。比如如果你的文档都是短文本,块大小可以适当减小;如果你的问题都是事实型查询,温度可以设为0。
注意:调参时一定要有评估集。我一开始凭感觉调,调来调去也不知道有没有变好。后来建了一个包含100个问题和标准答案的评估集,每次调参后跑一遍,看准确率的变化,效率高了很多。
5. 实操过程中踩过的坑与排查技巧
5.1 文档解析的常见问题
问题一:PDF解析后文字顺序错乱。这是PDF解析的经典问题,尤其是多栏排版的PDF。解决方案是先用版面分析工具识别栏位,再按栏位顺序提取文字。如果不想引入额外依赖,可以尝试调整解析器的参数,部分解析器支持按阅读顺序输出。
问题二:表格内容丢失。纯文本提取会丢失表格结构,导致检索时无法定位。解决方案是使用支持表格提取的解析器,把表格转成Markdown或HTML格式。如果解析器不支持,可以考虑用专门的表格识别工具做后处理。
问题三:扫描版PDF无法提取文字。这种情况需要OCR。六款产品中只有一款内置了OCR功能,其他都需要额外集成。我们的做法是先用OCR工具把扫描版PDF转成文本,再走正常的解析流程。
5.2 检索质量差的排查思路
检索质量差是最常见的问题,排查时可以从以下几个方向入手:
- 检查切块质量:把检索到的块打印出来,看看内容是否完整、是否包含关键信息。如果块太碎或太杂,调整切块参数。
- 检查Embedding模型:用几个已知相似的句子测试Embedding模型的相似度输出,看看是否符合预期。如果模型对中文支持不好,考虑换模型。
- 检查检索参数:Top-K是否太小?距离度量方式是否合适?索引是否正常构建?
- 检查查询改写:如果是多轮对话场景,看看查询改写后的结果是否准确。改写错误会导致检索方向完全偏离。
- 检查重排序:如果加了重排序但效果反而变差,可能是重排序模型和Embedding模型不匹配,或者重排序的候选集太小。
5.3 生成环节的典型问题
问题一:模型编造答案。这是RAG系统最头疼的问题。解决方案是在Prompt中明确要求"只根据提供的上下文回答,如果上下文中没有相关信息,回答'根据已有信息无法回答'"。同时可以降低温度参数,减少模型的随机性。
问题二:答案不完整。可能是检索到的块不够全,或者Prompt中上下文的排列顺序影响了模型的注意力。可以尝试把最相关的块放在Prompt的开头或结尾,因为模型对这两个位置的注意力更强。
问题三:引用标注不准确。如果要求模型标注答案的来源,模型可能会标错。解决方案是在Prompt中给出明确的引用格式,并在后处理时做校验。
5.4 性能优化的经验
RAG系统的性能瓶颈通常在两个地方:向量检索和模型调用。
向量检索的优化手段包括:使用更高效的索引类型(如HNSW)、减少向量维度、使用量化技术压缩向量。模型调用的优化手段包括:使用流式输出减少首字延迟、缓存常见问题的答案、对Prompt做压缩减少token消耗。
实操心得:流式输出对用户体验的提升非常明显。用户不需要等整个答案生成完,而是可以边生成边看。实现上也不复杂,大部分模型接口都支持流式返回。
6. 这套蓝图后续还能怎么扩展
这套自研RAG系统上线后,运行了几个月,整体效果符合预期。但RAG这个领域发展很快,后续还有很多可以扩展的方向。
多模态RAG:目前只支持文本,后续可以加入图片和表格的理解能力。比如用户上传一张图表,系统能理解图表内容并回答相关问题。
Agent化:把RAG作为一个工具,接入Agent框架,让模型自己决定什么时候检索、检索什么。这种方式适合复杂任务,比如需要多步推理的场景。
个性化检索:根据用户的历史行为和偏好,调整检索结果的排序。比如同样的问题,不同角色的用户可能关注不同的答案。
评估自动化:目前评估集是人工构建的,后续可以用大模型自动生成评估问题和标准答案,降低评估成本。
这些方向我都在陆续尝试,有些已经出了原型,有些还在调研阶段。RAG的工程化落地是一个持续迭代的过程,没有一劳永逸的方案,只有不断根据业务反馈做调整。
我个人在实际操作中的体会是:不要追求一步到位,先把核心流程跑通,再逐步优化每个环节。我见过不少团队一开始就想做一个大而全的系统,结果做了半年还没上线。反而是那些先用轻量级方案快速上线、再根据用户反馈迭代的团队,最终做出了真正好用的产品。