RAG 的新思路:SAG 和 OpenViking

最近在看 RAG 的两个新思路,其实也不是说特别新,有点新瓶装旧酒的意思。

对于 RAG 的优化,一种线路是继续优化传统 RAG:切块更细一点,召回器多堆几层,重排再优化,最后把 prompt 拼得更精致。另一条线路是开始换问题定义:如果知识检索本身的建模方式就不对,后面那一长串补丁是不是从第一步就已经输了。

SAG 和 OpenViking 属于第二种。

这两个项目看起来也不在一个层面上。SAG 讨论的是检索架构,OpenViking 讨论的是 Agent 的上下文系统。但我看下来,它们都是在解决同一类问题:RAG 之所以越来越重,往往不是因为模型不够强,而是因为我们把「知识」和「上下文」这两个对象建模得太糙。

过去一年,团队在 RAG 上花的钱和精力,主要耗在三个地方:

  • 文档切块以后,语义边界被打碎;
  • 多跳关联靠 embedding 硬撞,召回经常断链;
  • 想补结构化能力,就往 GraphRAG 走,结果离线构图、实体归一、增量更新把系统拖住。

这里的问题是这条路线会把系统带向复杂化。

SAG

SAG 的核心论文《SAG: SQL-Retrieval Augmented Generation with Query-Time Dynamic Hyperedges》提出了一种非常精妙的架构。它不是传统 RAG 与 GraphRAG 的简单拼接,而是一套用自己的数据模型和执行路径替代二者的原创检索架构。

它的核心思想可以总结为八个字:轻量离线,动态建图。

1. 数据模型

传统 GraphRAG 把文本切碎,试图把所有知识都抽象成 (Subject, Predicate, Object) 的三元组。这种做法丢失了文本原本的上下文语义,导致最后召回出来的都是一些干瘪的关系链,LLM 很难根据这些碎片生成高质量的回答。

SAG 重新设计了数据模型:

  • Chunk  Event(事件):一个语义完整的文本块(Chunk)被映射为一个 Event。Event 承载该 Chunk 的完整上下文,它是最终输出和溯源的边界,不再被拆成彼此独立的三元组。
  • Chunk  Entities(实体):从该 Chunk 中抽取多个 Entities。这些实体只负责索引和扩展,不替代事件本身所承载的完整含义。
  • Event  Entities:建立事件与实体之间的关联,共同定义了一条潜在的超边(Latent Hyperedge)。
+-------------------------------------------------------------+
|                         Original Chunk                      |
+-------------------------------------------------------------+
                               |
                               v
+-------------------------------------------------------------+
|                         Event (完整语义)                     |
+-------------------------------------------------------------+
            /                  |                  \
           v                   v                   v
+------------------+   +------------------+   +------------------+
|     Entity A     |   |     Entity B     |   |     Entity C     |
+------------------+   +------------------+   +------------------+

这个设计让事件和实体各司其职。实体只用来做连接的「钩子」,而事件负责保留完整的语义。

2. 查询时动态超边

SAG 不预先构建、也不全局维护任何静态图谱。所有的关系推理,都是在检索发生的那一瞬间,通过关系型数据库的 SQL JOIN 动态计算出来的。

当一个用户查询(Query)进来时,在线检索流程如下:

  1. 种子定位:通过语义(向量)与词法(全文检索)信号,找到与查询最相关的「种子实体」和「种子事件」。
  2. SQL 动态扩展:利用关系型数据库的 SQL JOIN 查询,沿着这些种子实体,将共享相同实体的事件连接起来。
  3. 局部超边实例化:在查询时,仅针对当前查询所需的局部结构实例化「超边」,不进行任何全局图遍历。
  4. 证据还原:将筛选出的事件映射回原始 Chunk,去重后作为最强证据提供给 LLM 生成带引用的回答。

在这个过程中,我们不需要 Neo4j,不需要复杂的图算法,底层的存储与查询完全依赖标准的数据库基础设施(如 SQLite、PostgreSQL、LanceDB 等)。

因为超边是查询时动态计算的,所以当有新文档导入时,不需要重算或重构全局图谱。新 Chunk 只需要并行抽取自身的 Event、Entities 并写入数据库即可。这让系统天然支持了高并发的增量写入。

从跑分数据来看,在相同的 BGE-Large-EN-v1.5 Embedding 与 Qwen3.6-Flash LLM 配置下,SAG 在 HotpotQA、2WikiMultiHopQA 和 MuSiQue 的 9 项 Recall@1/2/5 指标中取得了 8 项最佳成绩。其平均 Recall@2/Recall@5 达到了 79.30%/88.18%,而 HippoRAG 2 仅为 68.14%/83.28%。

一些问题

SAG 不是银弹,还存在一些问题。

抽取质量依然依赖 LLM。Event 的切分和 Entity 的抽取用的还是大模型,模型抽错了,后面的动态连接就是在错误的实体上做 JOIN。SAG 降低了实体的语义负担,但没消除抽取本身的不确定性。这块的鲁棒性得拿自己的语料实测。

查询时的实体提取是另一个隐患。动态超边的起点是从 Query 里提出核心实体,如果用户的提问很口语、实体表达很模糊,种子定位偏了,后面整条链就歪了。这个环节的召回率我认为是整个系统最脆弱的地方,值得单独压测。

还有并发下的延迟分布。JOIN 在数据量大的时候,SQL 的执行计划、索引命中情况会直接影响尾延迟。其底层继承了数据库的并发能力,在海量数据情况下需要观察其延迟分布。

OpenViking

聊完 SAG,看下 OpenViking。它跟 SAG 不是同一个层面的东西,我把它俩放一篇文章里,是因为它们代表了同一个方向上两种不同深度的探索。

SAG 解决的是「怎么把知识检索得更准」。OpenViking 解决的是「Agent 的上下文该怎么组织」。前者是检索问题,后者是上下文工程问题。

火山引擎把它定位成一款面向 AI Agent 的上下文数据库,基于开源的 OpenViking 内核构建的全托管云服务。核心思想它自己概括成四句:虚拟文件系统、分层上下文、目录递归检索、可观测与自迭代。

万物皆文件

OpenViking 把 Memory、Resource、Skill 三样东西统一抽象成文件,全部映射到 viking:// 协议下的虚拟目录,每个条目有唯一的 URI。

这个抽象是 Agent 发展到现在的一种沉淀,这里最早是 Manus 在实践的逻辑。Agent 领域现在最乱的就是上下文管理。你有用户的长期记忆、有外部知识资源、有工具和技能定义,这三样东西各自用各自的存储、各自的检索方式,代码里到处是特判。OpenViking 把它们收敛成同一套文件语义之后,Agent 就能用 listfind 这种统一指令去操作所有上下文。

从模糊的语义匹配变成确定性的文件操作,这个转变对调试很有帮助。向量检索是个黑盒,你不知道为什么这次召回了这几条、漏了那几条。文件系统是白盒,路径、目录结构摆在那,Agent 走了哪条路一目了然。

分层加载

OpenViking 在上下文写入的时候,就自动把它处理成三层。

L0 是一句话摘要,用来快速判断。L1 是概述,包含核心信息和使用场景,供 Agent 在规划阶段决策。L2 是完整原始数据,Agent 确有必要时才深入读取。

这个设计针对的是一个很现实的成本问题。把海量上下文一次性塞进提示词,token 烧钱、容易爆窗口、还引入噪声。分层之后,Agent 在规划阶段只看 L0 和 L1 就能做大部分决策,只有真正要用某份资料的时候才去拉 L2。

我算过一笔账。一个复杂的多步 Agent 任务,如果每一步都把候选上下文的全文塞进去,token 消耗是指数级的。分层供给相当于给 Agent 一个「先看目录再翻正文」的能力,规划阶段的 token 成本能压下去一大截。具体压多少取决于你的任务形态,但这个方向省的钱是实打实的。

目录递归检索

OpenViking 的检索不是单次向量召回。流程是这样:先通过意图分析生成多个检索条件,用向量检索快速定位初始切片所在的高分目录,然后在这个目录下做二次检索,把高分结果更新到候选集合;如果目录下还有子目录,就逐层递归重复二次检索;最后拿到最相关的上下文返回。

「先锁定高分目录,再精细探索内容」,这套策略把向量检索的语义定位能力和文件系统的层次结构结合起来了。单纯的向量检索找到的是孤立的片段,它不理解片段所在的完整语境。目录递归能顺着文件的组织结构,把一个片段周围的语境一起带出来。

这个思路和 SAG 的动态超边其实有相通之处。两者都意识到孤立的语义片段不够用,都想在检索时把「关联」这层信息补回来。SAG 用的是共享实体的 SQL JOIN,OpenViking 用的是目录层级的递归遍历。一个走关系代数,一个走树形结构。解决的底层痛点是同一个。

可观测与自迭代

OpenViking 的检索过程,每次的目录浏览、文件定位轨迹都被完整留存,能看清问题根源、指导检索逻辑优化。

对做过 RAG 调优的人来说,这个能力的分量不用多解释。传统向量检索出了问题,你几乎无从下手,只能瞎调 embedding、瞎调 chunk size。有了完整的检索轨迹,你能精确复盘 Agent 每一次信息获取走了哪条路径,哪一步偏了。

自迭代那部分是通过 session.commit() 主动触发的。会话结束时,系统异步分析任务执行结果和用户反馈,自动更新到 User 和 Agent 的 /memory 目录下。它既更新用户偏好,也从任务执行经验里提炼操作技巧和工具使用经验。

这个闭环我持谨慎乐观。自动提炼记忆听着很美,但提炼的质量、更新的边界、错误记忆的污染问题,都是需要长期跑才能暴露的。我不会一上来就把它当成生产依赖,会先在低风险场景里观察它更新出来的记忆到底靠不靠谱。

SAG 和 OpenViking

写到这,我把 SAG 和 OpenViking 放到一起,讲讲我看到的相同点。

传统 RAG 的世界观是扁平的。所有知识被切成等长的 Chunk,拍平进一个向量空间,检索就是在这个空间里找最近邻。这套世界观简单、好扩展,但它主动丢弃了知识之间的结构信息。Chunk 和 Chunk 之间是什么关系,谁包含谁,谁引用谁,谁是谁的上下文,全都被拍平的过程碾掉了。

SAG 和 OpenViking 从两个不同的方向,试图把这层被碾掉的结构信息找回来。

SAG 找回的是「实体关联」这层结构。它承认知识点之间存在通过共享实体建立的隐式关联,并且用查询时的 SQL JOIN 把这层关联即时重建出来。它的贡献在于证明了这层结构不需要离线物化成一张昂贵的全局图,用关系型数据库现成的关联能力就能在查询时高效算出来。

OpenViking 找回的是「层次组织」这层结构。它承认知识天然有目录、有层级、有从属关系,并且用虚拟文件系统把这层组织显式地保留下来,检索的时候顺着这个层级递归下去。它的贡献在于把 Agent 的上下文从一堆散落的切片,重新组织成了一个可导航、可追溯的结构。

两者都在做同一件事:扁平化的向量检索丢失了太多结构,而这些结构恰恰是复杂查询和长程任务真正需要的东西。它们的分歧只在于用什么载体去承载这层结构。SAG 选了关系表和 SQL,OpenViking 选了文件系统和目录树。

怎么选

落到实际决策,可以分两个场景。

如果核心诉求是知识库的多跳检索精度,语料相对静态、增量频繁、又要给用户看得见的原文引用,建议评估 SAG。它的动态超边在多跳上的数据摆在那,增量友好和溯源能力恰好解决了之前方案的问题。部署门槛低,本地就能验,评估成本几乎为零。

如果做的是 Agent 产品,痛点在长程任务的上下文管理、记忆沉淀、多 Agent 协作,建议去评估 OpenViking。它解决的是比检索更上一层的问题,文件系统抽象和分层加载对控制 Agent 的 token 成本、提升可调试性比较有用。

这两个东西不冲突。理论上一个成熟的 Agent 系统,底层完全可以用 SAG 这类引擎做精准的知识检索,上层用 OpenViking 这类框架做上下文的组织和调度。一个管「检索得准」,一个管「组织得清」,各司其职。

检索这条路走到今天,值得关注的方向已经从「怎么把向量算得更快」转到了「怎么把结构找回来」。SAG 和 OpenViking 是这个转向上两个具体的答案,一个在检索层,一个在上下文层。

以上。