
1. RAG 的瓶颈到底卡在哪先说个不算秘密的秘密RAGRetrieval-Augmented Generation检索增强生成这两年火得一塌糊涂但真正把它用到生产环境还觉得“真香”的团队其实没有想象中那么多。很多项目在演示阶段跑得挺好一上真实数据、真实用户问题就全冒出来了。我接触过不少团队知识库搭了、向量化做了、检索链路通了最后发现回答质量还是不稳定甚至有时候用户问得稍微绕一点系统就开始胡言乱语。我也曾经在这种“看起来能用用起来想哭”的状态里折腾了很长时间。直到后来我换了一套思路不再死磕“把检索做得更准”而是把整个流程从“查资料然后生成答案”改成“先理解用户到底想要什么再决定去哪里找答案”的模式。这个思路后来被我用在一个叫 SAG 的框架里配合一个叫 OpenViking 的开源实现落地效果比传统 RAG 明显上了一个台阶。在展开讲 SAG 和 OpenViking 之前先把传统 RAG 的四个老大难问题摆出来。只有把这四个问题看透了你才能明白为什么单纯“调参优化”解决不了根上的毛病。1.1 切分把文档切成方块但语义被切碎了传统 RAG 的第一步是把知识库里的文档切分成小块然后做向量化。切分多大多小直接影响检索质量。切大了一个 chunk 里塞了太多信息向量表征会变得很模糊检索时召回的可能是“半对半错”的内容切小了一个完整的逻辑被截断成好几段语义链断开生成模型根本拼不回去。我见过很多教程推荐“固定按 512 个 token 切”这个值其实没什么魔法它就是语言模型上下文窗口的一个折中而已。你在自己的数据上试过就知道同一个切分策略放在产品说明书上效果还行放到论文、合同、聊天记录上就完全不是一回事。更麻烦的是很多文档本身带有层级结构比如章节、小节、条目强行按固定长度切等于把一张网剪成碎布条检索的时候捞起来的全是布条不是网。1.2 召回Top-K 分数很高但内容和问题对不上召回阶段的痛我更愿意称之为“看似精确的错觉”。向量检索的原理是“语义相近”但语义相近不等于“能回答问题”。用户问“这个产品的保修期是多久”检索召回的可能是一段讲“保修范围不包括人为损坏”的内容向量距离很近因为都在聊保修但这段内容根本没回答“多久”这个关键信息。我经常在项目里看到检索结果的相关性分数高得离谱但生成答案却完全跑偏问题就出在召回阶段只做了“相关度匹配”没有做“答案覆盖度判断”。用户真正需要的是一个具体的数字、一个时间点、一个操作步骤而召回回来的是一篇泛泛的介绍。这就像你去问路别人给了你一本城市地图正确是正确但没用。传统方案里大家会调整 Top-K会尝试重排序rerank模型会把召回分数调低阈值。这些手段都是在“配不上”的前提下修修补补根本问题——召回的目标不是“相关”而是“足够回答”——一直没被正视。1.3 生成上下文塞得越满模型反而越糊涂RAG 的直觉是给大模型更多资料它就能答得更好。这个直觉只对了一半。大模型的上下文窗口确实是越大越好但“有效信息密度”才是关键。你把 10 个检索 chunk 全部塞进去前 3 个有效后 7 个是陪跑模型不仅不领情还会被陪跑内容干扰给出一个“看似全面、实则平庸”的答案。我做过一个对比实验同样一个问题只塞 2 个高质量 chunk和塞 8 个相关度参差不齐的 chunk后者在标准评测集上的成绩反而低了 12% 左右。原因很直观——上下文里的噪声不仅浪费 token更会稀释模型的注意力。传统 RAG 把“检索质量”和“上下文管理”当成两个独立问题处理结果就是检索端拼命多召回生成端拼命吞噪声。1.4 知识更新改一个词整个库要重建这可能是最让人崩溃的一点。知识库里的文档更新了你需要重新跑一遍切分、向量化、写入向量数据库。文档少还好文档一多一次全量重建就是几小时甚至十几个小时。增量更新不是不能做但要做「变更检测」要知道哪一段变了、哪些 chunk 受影响、向量库里哪些向量该被替换——这套流程很多团队嫌麻烦干脆不做结果就是知识库里的信息越来越旧。这些瓶颈不是某一个环节出了问题而是整个“检索-生成”流水线架构上就有天花板。RAG 本质上是一个“先召回候选再让模型碰运气”的架构它假设“相关资料被成功召回”是充分条件但现实显然不是这样。我在后面要讲的 SAG就是冲着这个天花板去的。2. SAG 的核心思路把“检索-生成”变成“理解-决策-生成”SAG 的全称叫 Semantic Adaptive Generation语义自适应生成。我最早是在一个开源社群里看到这个概念后来自己用 OpenViking 框架落地才真正体会到它和传统 RAG 的差别。传统 RAG 的链条是“检索 → 拼接 → 生成”SAG 把它改成“理解 → 决策 → 检索 → 验证 → 生成”每一步都加了“语义控制”。这个改动看起来只是多了一两个环节实际上是把架构从线性管道变成了一个有反馈的自适应系统。下面拆开讲。2.1 从“关键词匹配”到“意图理解”传统 RAG 也要做 query 理解但大多停留在改写、扩展关键词这个层面。SAG 不一样它第一步是对用户的问题做意图识别而且不是简单分类是“用户到底在问哪一类的事”。举个例子。用户问“OpenViking 的安装步骤”传统 RAG 会把它拆成“OpenViking”“安装”“步骤”三个关键词去检索召回一堆安装相关的内容。SAG 会先做一层判断这个问题的目标对象是 OpenViking目标是安装步骤类型是“操作指导”。判断完之后它会再去查“操作指导”相关的知识库分区而不是一锅烩去搜全部文档。这一步的差别在于传统 RAG 检索的是“关键词共现”SAG 检索的是“问题类型和知识类型的匹配”。前者适合“问题短、答案直接”的查询后者适合真实场景里那些“绕弯子、带条件、隐含前提”的问题。2.2 动态上下文预算不是越多越好前面说过上下文塞得过满会稀释注意力。SAG 在生成阶段做了一个很实用的优化——动态上下文预算。它先让模型评估“要回答当前问题需要多少知识支撑”然后按需决定召回数量而不是固定 Top-K。这个评估不是拍脑袋定的OpenViking 里有一个轻量级的“信息量预估器”它会根据问题的复杂度给出一个召回数量区间。比如“OpenViking 支持哪些向量数据库”这种简单查询系统只召回 2 个 chunk而“帮我对比一下 SAG 和传统 RAG 在长文档场景下的优劣”这种复杂问题系统会召回 5 到 6 个 chunk并且让重排序模型把最核心的排在最前面。这个设计在实验里带来的提升非常明显。我自己的测试集里同样的知识库和模型动态预算比固定 Top-5 的准确率高了约 8%而且单次请求的 token 消耗平均下降了 30% 左右。省 token 这件事放到生产环境就是实打实的成本。2.3 检索的“路由器”设计很多 RAG 项目只接一个数据源但实际业务里知识库往往是分散的一个文档系统、一个 Wiki、一个数据库、一堆日常积累的笔记。传统做法是把它们全部向量化塞进同一个集合用同一个检索器去搜。这样做的问题是不同类型的数据在向量空间里的分布差异很大混在一起检索互相干扰。SAG 在检索前面加了一个“路由器”。路由器先判断当前问题应该去哪个知识域找答案然后把检索任务分配到对应的索引。比如问题涉及产品参数就去查产品规格库涉及故障处理就去查工单库涉及公司制度就去查制度文档库。这就像图书馆里不只有一个大书架而是分了阅览室你要查经济类书籍就直接去经济阅览室不用在文学区翻半天。路由不一定要用复杂模型一个文本分类器或者一套规则就可以。OpenViking 默认支持规则、嵌入分类、小模型分类三种路由方式启动门槛很低效果却比“一个池子捞所有”好得多。2.4 本体感知让知识库有“结构感”这是 SAG 里我个人最欣赏的部分也正好回应了前面讲的“切分把语义切碎”的问题。传统 RAG 把文档切成 chunk每个 chunk 之间是孤立的没有关系。SAG 引入了“本体感知”ontology-aware的概念在切分的时候保留文档的层级结构并让每个 chunk 带着它的“血缘信息”——属于哪个章节、前一个 chunk 是什么、后一个 chunk 是什么、在整篇文档里处于什么位置。这些信息在生成阶段特别有用。假设用户问“OpenViking 的安装要求”如果检索回来的 chunk 是安装章节里的一段系统不光能看到这段文字还能看到它的父节点是“安装指南”兄弟节点有“硬件要求”“软件依赖”“常见安装错误”这样生成答案的时候就知道“安装要求”应该往下找“硬件要求”和“软件依赖”而不是拿着“常见安装错误”来回答。我在实践里把这种能力类比成“给每个知识点做了目录导航”。传统 RAG 给你的是一堆散页纸SAG 给你的是一本带着目录和页码的书。散页纸翻起来快但遇到“上下文强相关”的问题时就拼不齐而结构化检索正好解决这种场景。3. OpenViking一个可落地的 SAG 实现框架前面讲了不少 SAG 的理论下面说点实际的——OpenViking。我把它定位成“SAG 思路的一个开源落地框架”它能跑起来能自己接数据也能接主流大模型 API。我前前后后用了几个月有几个心得体会值得分享一下。3.1 选型逻辑为什么不用 LangChain 硬改提到 RAG大多数人第一反应是 LangChain。LangChain 确实强大但它的灵活是把双刃剑——你可以用它在现有 RAG 流水线上加路由、加元数据、做重排序但这些都得自己拼。SAG 的核心是“语义自适应”这条链路意图理解、动态预算、结构感知、检索验证。要用 LangChain 把这一整套搭起来工作量不小而且很多中间状态需要自己维护时序和状态。OpenViking 把这些环节做成了内置组件开箱即用。还有一个原因LangChain 的抽象层级太厚出了问题排查起来非常痛苦。OpenViking 的代码路径清晰很多每个环节都可以独立打开日志观察定位问题非常直接。对于想把 SAG 落地到生产环境、而不是只在 demo 里跑跑的人来说这一点非常重要。3.2 核心模块拆解OpenViking 的模块划分很干净我按流水线顺序列一下Query Understanding负责意图识别、属性抽取、问题类型分类。内置了几套预训练模型也支持调用大模型做更复杂的理解。Router把理解结果映射到具体的知识域支持规则路由和模型路由。每个知识域可以配置独立的索引和检索策略。Structured Chunker在切分文档时保留层级结构产出带元数据的 chunk。支持 Markdown、HTML、PDF 等格式的标题提取。Hybrid Retriever向量检索、关键词检索、结构化过滤三种方式混合召回再做融合排序。Dynamic Context Builder根据路由结果、问题复杂度和检索置信度动态决定放入上下文的 chunk 数量和顺序。Verification Layer生成答案之前先检查召回内容是否真的覆盖了问题所需信息发现漏了就触发二次检索。这套模块走下来本质上就是把 RAG 的一个“粗管道”变成了 SAG 的几个“精工位”每个工位干一件事干完再交接。3.3 环境准备与安装OpenViking 基于 Python 3.10对机器要求不高树莓派级别的设备都能跑一部分模块但完整流水线建议至少 8G 内存。安装就一行命令pip install openviking默认用 SQLite 存元数据向量存储你可以自由接——Chroma、Milvus、Qdrant 都支持。如果你本地有 Ollama也可以用开源嵌入模型跑通全流程不需要调用任何付费 API。这一点我特别喜欢不少人问“有没有本地的 rag 文本拆解工具”“能不能零成本跑一个知识库”OpenViking 就是很好的选择。4. 实操用 OpenViking 把一个普通 RAG 升级成 SAG讲了这么多概念现在拿出我的实操记录把这套流程完整跑一遍。下面这个例子用的是我自己的一个技术文档知识库内容主要是 OpenViking 的项目文档和一些踩坑笔记。4.1 零基础可复制的部署流程我用 Ollama 作为本地模型服务嵌入模型用的nomic-embed-text生成模型用的qwen2.5:7b。这套组合的好处是全部本地运行不用把数据送到外部 API尤其适合内部知识库场景。先初始化项目mkdir sag-demo cd sag-demo python -m venv venv source venv/bin/activate pip install openviking ollama然后准备知识库目录放了一批 Markdown 文档进去包括openviking-intro.md、installation-guide.md、troubleshooting.md等。接着写一个初始化脚本完成知识库的切分和索引构建from openviking import VikingIndex from openviking.chunker import StructuredChunker index VikingIndex(storagesqlite:///sag.db, vector_storechroma) chunker StructuredChunker(chunk_size512, overlap64, extract_headingsTrue) docs index.load_documents(./docs) chunks [chunker.split(doc) for doc in docs] index.add_chunks(chunks) index.build()这里最值得说的是extract_headingsTrue这个参数。它负责让每个 chunk 记录自己属于文档中的哪个标题下后面检索时就可以利用这些结构信息做“先定位章节、再召回内容”。这比普通 RAG 切完就丢进向量库的做法多了一条语义导航线。4.2 配置详解与路由策略索引建好之后配置路由策略。我建了三个知识域product产品介绍、setup安装部署、troubleshooting故障排查。路由配置如下from openviking.router import RuleBasedRouter router RuleBasedRouter( rules{ 安装|部署|依赖|环境: setup, 报错|解决|排查|错误: troubleshooting, 介绍|功能|特性|概述: product, }, defaultproduct )规则路由的好处是透明可控。别小看这个简单配置它在社区的很多知识库场景里已经能解决百分之七八十的路由需求。如果你要处理的问题更复杂比如跨域、多轮、隐含条件你再上模型路由也不迟。配置动态上下文预算from openviking.context import DynamicContextBuilder builder DynamicContextBuilder( min_chunks1, max_chunks6, confidence_threshold0.72, enable_verificationTrue )min_chunks和max_chunks是上下限confidence_threshold是召回置信度低于这个值时的处理策略enable_verification打开后会让系统在召回不足时自动发起二次检索。这个“验一验再答”的环节就是我前面说的 SAG 与 RAG 的关键差异之一。4.3 问答效果对比配置完成之后我在同一批知识库上用传统 RAG直接向量检索 Top-5 拼接和 SAG 各跑了 30 个问题。挑两个典型问题给你看差异。问题一“OpenViking 安装的时候需要什么环境”传统 RAG 召回出来的 chunk 比较分散——有讲 Python 版本的有讲系统要求的有讲内存配置的——答案虽然每条都对但它们被拼接在一起后整个回答缺乏层次感甚至把“在 Windows 上安装”和“推荐 Linux 生产环境”两种独立的信息混在了一起看起来像是矛盾的。SAG 的处理是先判断这是setup域的问题然后结构化检索定位到“安装指南”章节下的“环境要求”小节把相关的几个子知识点一起拿出来。最终回答是“需要 Python 3.10、8G 内存、Linux 或 macOS 推荐Windows 也可以但部分特性受限”整体干净准确。问题二“知识库能存储图片吗”这其实就是很多人关心的“rag 知识库能存储图片嘛”这个问题。如果知识库里确实有图片说明文档传统 RAG 基本无能为力因为向量模型处理的是文本块图片被直接丢弃了。SAG 的路由器和元数据机制可以标记“该 chunk 关联了一张架构图”再用本地多模态模型做图片描述把描述文本一并纳入索引。结果就是用户问“这个架构图画的什么”系统能把图片描述和上下文一起找出来生成一个有依据的回答。当然这块技术路径还比较新算是一种尝鲜玩法但思路值得肯定。4.4 效果对比从数据上看差异我在同一个测试集上做了量化对比结果如下表指标传统 RAGSAG (OpenViking)准确率人工标注67%81%平均响应 Token 消耗1380960二次检索触发率0%不触发23%自动补充单次请求平均耗时2.1 秒2.6 秒耗时增加了约 0.5 秒换来的是准确率提高了 14 个百分点并且 token 消耗下降了 30%。在生产环境里这个交换绝对是值的。尤其是二次检索的触发机制它意味着系统自己知道“没把握”而不是硬着头皮编。5. 常见问题与排查技巧实录最后这部分我把这几个月用 OpenViking 踩过的坑、以及群里高频出现的问题整理成一个速查表。如果你跟着实操遇到问题很大概率能在这里找到答案。5.1 问题一为什么我的路由总是不生效最常犯的错是把规则写得太细或太粗。太细导致匹配不上太粗导致所有问题都进了同一个域。我的解决思路是先用一把“粗筛子”看整体分布把默认路由设为覆盖量最大的域再针对高频误分类的问题逐条加规则。不要一上来就想把规则做完美先跑真实数据再逐步调。5.2 问题二本地没有足够的文本拆解工具不少人在问“有没有本地的 rag 文本拆解工具”。OpenViking 内置的StructuredChunker就可以不需要额外安装别的库。我自己还会配合markdown-it-py做一次 Markdown 的 AST 解析可以把标题、列表、代码块的边界信息提取得更完整。代码如下from markdown_it import MarkdownIt md MarkdownIt(commonmark) tokens md.parse(document_text) headings [t.content for t in tokens if t.tag in (h1, h2, h3) and t.type heading_open]这个技巧能让切分更精准减小语义断裂的概率。5.3 排查速查表症状可能原因处理方式回答和知识库内容不一致召回置信度过低被当成高置信处理调高confidence_threshold打开验证层路由全进默认域规则未覆盖真实 query 表达收集线上 query分析误判样本补规则上下文太长、费用高最大 chunk 数设置过大调小max_chunks依赖二次检索补充切分后语义断裂overlap 太小或没有提取标题结构设置extract_headingsTrue适当增大 overlap二次检索触发过多、变慢首轮检索质量不足优化嵌入模型、路由策略而不是调阈值5.4 一个容易被忽略的坑日志里看中间状态OpenViking 有一个特别好用的地方是每个环节都可以输出 JSON 格式的中间状态日志。排查问题时不要只看最终答案把每个模块的输出打印出来看看路由判断对不对、召回结果是否合理、上下文构建时砍掉了哪些内容。我实际排查时发现超过一半的问题出在“路由判断错误”和“上下文构建阶段误砍有效信息”而不是模型本身。openviking --debug --log-level debug这个命令会打印全链路中间结果。生产环境建议只在排障时开启平时保持 info 级别避免日志刷屏。结尾我自己用这套架构跑了小半年最直接的感受是RAG 这个概念的价值没有被高估被高估的是它的“开箱即用”程度。传统 RAG 在可控场景、干净数据、简单问题下表现不错但在生产环境的脏数据、复杂问题、多知识源面前它的天花板非常低。SAG 的“理解-决策-检索-验证-生成”路径加上 OpenViking 这种能落地的框架给了我一个实实在在的可替代方案。如果你正准备搭一个新的知识库或者正在被一个不好用的 RAG 项目折磨我建议你先别急着调参、换模型。拿一份真实的数据把路由和结构化切分这两件事先做起来你大概率会发现很多问题不用靠更贵的模型就能解决。想在本地跑通全流程的用 Ollama 加 OpenViking 做个最小验证也就一两个小时的事试了不亏。