做 RAG 项目这一年多,我最大的感受是:真正拖后腿的往往不是生成模型,而是检索。最近我在折腾 SAG(Search-Augmented Generation,搜索增强生成)和 OpenViking 这个开源框架,感觉这条新路线确实在回应 RAG 最痛的几个问题。这篇文章就把 SAG 的思路、OpenViking 的设计,以及我落地一个本地知识库问答的完整过程写出来,希望能给正在做 RAG 项目的朋友一些参考。
1. RAG 听着美好,真正跑起来卡在哪几个地方
先别急着聊 SAG,得先把 RAG 的痛点掰开揉碎。RAG 的标准管线大家都知道:文档解析、文本切块、向量化、召回、拼 Prompt、生成。看着简单,但每个环节都藏着变数。我自己的项目从 demo 走到生产环境,几乎每个环节都踩过坑。
1.1 不是模型不够强,是“检索回来什么”决定了答案质量
很多人以为 RAG 效果差是 LLM 的问题,换更大的模型就能解决。实际上做过几轮测试之后结论很明确:生成模型只要不是太弱,答案的上限基本由召回质量决定。recall 不到,给 LLM 再多的 CoT 也没用,它只能对着不相关的上下文硬编。
举个例子,知识库里存了一批产品销售记录,用户问“2024 年第三季度华东区卖得最好的三个 SKU 是什么”。如果向量检索召回的是“华东区销售策略”“SKU 管理规范”这类段落,那模型写得再流畅也是错的。问题不在生成,而在检索阶段没有把“2024 年 Q3、华东区、SKU、销量排名”这些精确约束变成可匹配的检索条件。
1.2 向量检索的三大失灵场景
纯向量检索在 RAG 里的失灵,我总结下来有三种高频场景:
第一,语义相似但事实不同。用户问的是 A 产品的售后政策,知识库里同时存在 A 产品和 A 产品线的售后政策,embedding 算出来相似度都挺高,排在前面的未必是用户真正要的那一版。相似度是连续值,事实却是离散的,一个相似度分数没法表达“我就是指那个特定实体”。
第二,长尾专名和代码型号。这类 token 在 embedding 模型里经常被压扁,比如“XK-3300 高压密封法兰”,语义向量可能把 XK-3300 当成普通修饰词。传统 BM25 反而能精准命中这些罕见词。
第三,跨语言、跨格式的表达差异。中文问“退货流程”,文档里写的是“Return & Refund Policy”,纯向量召回可能就漏了。向量模型能学到部分跨语言语义,但做不到稳定。
1.3 知识库管理比模型还容易翻车
再往深处看一层,RAG 项目越跑越久,真正决定天花板的是知识库本身的状态。我见过太多团队花大精力调模型,却连最基本的文本拆解都没做对。
知识库的脏乱差体现在几个地方:同一份文档被重复上传多次、PDF 表格被解析成乱码、Markdown 标题层级丢失、更新后的文档和旧版本混在一起。检索系统不在乎文档漂不漂亮,它只认切出来的块和块对应的索引。块切不好,再好的检索模型也白搭。
还有一个常见的坑是“图片和表格里的信息进不了知识库”。很多公司的核心数据就藏在 Excel 透视表、PDF 截图、扫描件里。RAG 对这些非结构化内容的支持,直接决定了知识库到底“库”了什么东西。热词里有人问“RAG 知识库能存储图片吗”,答案是能,但前提是你的链路里有 OCR、表格识别和跨模态检索,这几个能力目前很多 RAG 框架都没打包好。
2. SAG:把 RAG 的检索环节改造成一个真正的搜索系统
SAG 这个提法,本质上是在回答上面那些痛点。它没有推翻 RAG 的“检索+生成”大框架,而是把检索环节从“单一向量相似度”升级成一个完整的搜索系统。
2.1 SAG 与 RAG 的本质区别
RAG 默认了一件事:语义向量就能表达查询和文档的关系。SAG 不这么认为,它坚持的是传统信息检索那套方法论——查询需要改写、召回需要多路、排序需要独立模型、结果需要校验。用搜索引擎的思维去做 RAG,而不是用“向量数据库 + 相似度 top-k”的思维去做。
一句话概括:RAG 是“先找相似,再靠模型硬解”;SAG 是“先构造完整的检索条件,多路找回候选,用重排选出真正有用的上下文,再让模型基于引用生成”。
2.2 多路召回:词法、语义、结构一个都不能少
SAG 的第一个核心动作,是把召回从一路变成多路。
至少要有三路:
- 词法召回(BM25 / 倒排索引):擅长精确匹配专名、型号、代码、数字约束。像“XK-3300”“Q3”“SKU-807”这种词,BM25 的命中率比向量高一个量级。
- 语义召回(Dense Embedding):负责处理同义表达、口语化提问、跨语言改写。用户不记得产品全称只说了“高压法兰”,靠它兜底。
- 结构化召回(知识图谱 / 本体 / 表格):如果知识库里存在明确的实体关系、层级结构,比如“华东区包含上海、江苏、浙江”,这条检索条件就不该靠相似度去猜,而应该直接走结构化查询。
三路召回的结果合并之后,再去重、混合排序。这就是热词里“ontology rag”的价值所在——本体不是替代全文检索,而是作为一路高精度的候选来源,把实体关系先定死,再让语义召回去补充长尾表达。
2.3 重排不是可选项,而是必经之路
多路召回之后,候选集合会很大,有时 500 条甚至更多。这时候如果直接取前 5 条拼 Prompt,效果一定不稳定,因为 BM25 和向量两个体系打出来的分根本不可比。
所以 SAG 的第二个核心动作是重排(Rerank)。用独立的 cross-encoder 模型对“查询-文档块”对重新打分。这个模型会把查询和文档同时过一遍 Transformer,计算的是真正的交互语义,而不是像向量那样提前压缩成两个孤立的向量再算余弦。
实际效果差异非常明显。同一组数据,不重排时答案偶尔对、偶尔错,重排之后准确率能稳定很多。代价是多了一次推理开销,但在知识库问答这类场景完全值得。
2.4 生成侧的改变:从“填空”到“有据可依”
SAG 到了生成这一步,也不是简单地把重排后的 top-k 塞进 Prompt。
它会更关注两件事:
一是引用溯源。每个生成片段尽可能对应到具体的文档块 ID、页码、原文语句。OpenViking 的做法里,检索结果会保留原始分块元数据,Prompt 模板里强制要求模型输出时标注引用编号,这样用户点开答案就能看到来源。
二是上下文组织的顺序。不是机械地按分数从高到低排,而是按照“精确定义在前、背景补充在后、冲突信息并列展示”的方式组织。比如用户问一个政策条款,先给它条款原文,再给它解释性段落,最后放历史变更记录。模型顺着这个结构走,幻觉率明显下降。
3. OpenViking 的设计哲学:让搜索增强变成可以配置的流水线
聊完理念,说说 OpenViking 这个项目。它不是又一个“封装了向量数据库的 RAG 库”,而是把上面这套 SAG 链路做成了可配置、可插拔、可本地部署的工程化框架。以下内容基于我看到的公开设计思路和实际体验整理,框架还在快速迭代,具体接口以官方文档为准。
3.1 它解决的不是单点算法问题,而是工程化问题
很多 RAG 框架的问题在于:每个组件都有,但串联起来非常痛苦。今天要自己写文档解析、明天要调切块策略、后天发现重排模型没有插件,最后大家都在重复造轮子。
OpenViking 的思路是把整条 SAG 流水线固化成配置。数据进来到答案出去,每个环节都是标准接口,可以替换具体实现。对于一个做知识库落地的团队来说,这意味着可以花更多时间调自己的数据,而不是反复研究“怎么把组件粘起来”。
3.2 核心模块与一条可配置的链
按我的理解,OpenViking 的流水线大致是这几个模块:
- 数据接入层:支持 PDF、Word、Markdown、HTML、Excel、图片扫描件等。比较贴近生产环境的是它会把表格单独拆出结构化信息,而不是当纯文本硬啃。
- 文本拆解器:这是热词里“有没有本地的 RAG 文本拆解工具”很多人关心的点。OpenViking 提供了可配置的本地拆解策略,能按标题层级、段落边界、表格结构做切分,而不是简单按字数硬切。
- 索引层:同时建向量索引和倒排索引,这就是 SAG 多路召回的地基。
- 检索层:多路召回后做分数归一化和融合。
- 重排层:接入 cross-encoder 模型。
- 生成层:Prompt 模板、引用格式、模型接入(包括本地 Ollama 模型)。
- 评估层:针对“检索命中率、引用准确率、答案正确率”给出量化反馈。
这个设计最大的特点是:把检索系统该有的动作都变成了流水线的一等公民,而不是事后补救的补丁。
3.3 数据层:文本拆解与多模态扩展
单个模块里,我特别想聊文本拆解,因为这是 RAG 落地最容易忽略也最容易出效果的地方。
OpenViking 在这块的设计取向是“结构化优先”。拆解时先识别文档的真实结构:一级标题、二级标题、表格标题、图片说明、页眉页脚,然后再决定哪些内容合并成一个块、哪些块之间建立父子关系。
举个例子,一份产品手册里,“规格参数”是一个表格,下面紧跟着“安装注意事项”段落。如果按 500 字硬切,表格和注意事项可能被切到两个块里,LLM 检索时只能看到一半。结构优先的切法会保留表格这个独立单元,同时把它的上下文链接到“安装”这一章,检索时能按父子块一起召回。
多模态扩展方面,它能处理图片和表格的关键在于:图片先进 OCR/图像描述模型,把内容翻译成文字再参与索引;表格则走结构化解析,保留行列关系。这样知识库就能“存图片”了,存的是图片的内容,而不只是图片文件本身。
3.4 为什么说它是“开源框架里的新物种”
我觉得 OpenViking 和 LangChain 这类通用编排框架的差别在于:它不追求“什么都能接”,而是把 RAG/SAG 这条链路的默认配置做到位。你拿到手,按照它的数据格式整理文档,跑通一条标准流水线,然后再逐步替换组件。
这非常符合生产项目的实际节奏。LangChain 太自由,自由到每个环节都要自己决策;闭源平台太黑箱,出了问题没法查。OpenViking 给的是一种“约束下的灵活”——流水线标准,组件可换。对于团队协作来说,这种边界清晰的框架反而更容易推动。
4. 实操:按 SAG/OpenViking 的思路搭一个本地知识库问答
这一节我写点能直接“抄作业”的内容。就算你暂时不想用 OpenViking,按同样的思路自己搭,也能把 RAG 效果提升一个档次。
4.1 数据准备:本地文本拆解的正确姿势
我强烈建议在本地先把数据洗干净。步骤是:
- 把 PDF、Word 等统一转成 Markdown 或纯文本,检查解析后有没有乱码、表格是否错位。这一步用文档解析工具就能完成。
- 按标题层级切块。一级标题作为大章节,二级标题作为子单元,表格和列表尽量独立成块。
- 设置合理的重叠窗口。相邻块之间重叠 10% 到 20%,避免跨块语义被切断。比如每块 512 个 token,重叠 64 到 128 个 token。
- 给每个块写入元数据:来源文件名、章节路径、页码、更新时间。元数据就是 SAG 在生成阶段做引用溯源的基础。
这里值得多说一句,切块大小不是拍脑袋定的。我测试过 256、512、768 三档,发现代码类文档用 256 更准,产品介绍类 512 最均衡,规章制度类 768 更好。核心逻辑是:内容的信息密度越高,块要越小;叙述性越强,块可以越大。
4.2 索引与检索配置
数据备好后,建两套索引:
- 向量索引:用本地 embedding 模型,推荐 BGE-M3 这类对中文友好、也支持多语种的模型。维度、量化方式按模型官方建议配置。
- 倒排索引:用 BM25 算法,把原始文本分词后建索引。中文要先分词,分词器选 jieba 或盘古分词都可以,注意专有名词词典要自己维护一份。
检索时先改写查询。用户问“上季度的退款率是多少”,系统先把它改写成“2024 年第三季度 退款率 统计”,同时拆出一个子查询“退款率 统计口径”。每个查询分别走 BM25 和 Dense 两路召回,各取 top 50,合并成 100 个候选,然后去重、送进重排模型。
重排模型我习惯用 cross-encoder 类型的中文模型,速度和精度平衡。重排后取 top 5 到 top 8 进生成环节。
4.3 问答测试与引用溯源
生成阶段的 Prompt 建议写成这样:
- 系统指令:你是知识库问答助手,只允许基于给定的上下文回答。上下文每条有 ID、来源文件、页码。回答时在引用的语句后标注 [ID]。
- 用户内容:把重排后的块按“定义、扩展、补充”的顺序排好,再用一句话描述用户原始问题。
这样设置之后,模型输出基本都会带引用标注。我测试过,引用标注有两个额外好处:一是用户信任度高很多,二是当答案错误时,能快速定位是检索错了还是生成错了。
4.4 几个关键参数怎么调
调参不要盲调,按影响程度排序:
- 重排个数(Rerank Top-N):影响最大。从 5 调到 10,召回提升明显,但超过 15 之后噪音也跟着进来。我一般固定 8 到 10。
- 混合检索权重:BM25 和 Dense 的融合权重,按知识库特点调。专名多的给 BM25 更高权重,口语化内容多的给 Dense 更高权重。
- 块大小与重叠:改一次块大小就要重新建索引,所以尽量先定块再调别的。
- 生成温度:知识库问答我直接设 0.1 甚至 0,不需要创造性。
每个调整都要配同一组测试问题跑一遍,不要凭感觉判断。
5. 我踩过的几个坑和对应的解药
最后这部分是纯经验,希望能帮你少走弯路。
5.1 块大小不是越大越好
我第一次做知识库,总觉得块越大上下文越全,于是设了 1024。结果是检索经常召回一个超大块,里面只有一小段和问题相关,重排分数还很高,因为大块语义覆盖面广。但生成的答案容易被不相关信息带偏。后来改成 512 + 10% 重叠,配合父子块召回,效果立刻改善。
5.2 embedding 模型对中文的敏感度
中文 RAG 的 embedding 选型非常关键。有些英文模型对中文长尾词支持很差,专名直接跑偏。我测试下来,BGE-M3 和同类中文优化模型明显更稳。另外,量化版本会损失一点精度,本地部署如果内存紧张,可以先量化跑通,再根据效果决定要不要换回全量。
5.3 本体/知识图谱信息要用对地方
热词里“ontology rag”说明大家确实在关注结构化知识。我的教训是:不要试图用本体替换全文检索。你建了一个部门-产品-负责人三层图谱,然后检索全部走图谱查询,结果用户问一句口语化描述,图谱直接空转。正确用法是把本体验证作为一路召回,并用于查询改写时的实体链接。用户提到“华东区”,先链接到图谱里的华东节点,再把这个节点的子区域列表作为过滤条件,而不是把整个问答变成图查询。
5.4 评测数据集不建,后面全是盲调
没有评测集,调参就只是碰运气。我后来花时间建了一个 100 条问题的测试集,每条都标了期望答案和应该命中的文档块 ID。每次改配置,就批量跑一遍,对比三个指标:检索命中率、答案正确率、引用准确率。
引用准确率是最容易被忽视的。模型可能给出了一个正确率很高的答案,但你查引用发现引用的文档块根本不含这个信息,那就是模型在“脑补”。RAG 的底线应该是:答案错了不可怕,引用必须真实。有了评测集,这个问题马上暴露。
最后说点个人体会。SAG 和 OpenViking 给我最大的启发,不是某个算法有多惊艳,而是它把 RAG 从“demo 工程”往“搜索工程”拉了一大步。知识库问答本质上是一个检索问题,不是生成问题。谁先想清楚这一点,谁的项目就能在生产环境里少受折磨。如果你也在做知识库,建议先别急着换大模型,把文本拆解、混合召回、重排、引用溯源这条链跑扎实,效果可能比想象中提升得快得多。