【论文分享】反向内卷的极简主义:单机 SQLite + BM25 如何干翻重型向量库与 GraphRAG?深度拆解 SAG2 与开源 SAG-Wiki

在当前大模型检索增强生成(RAG)与智能体(Agent)知识库的工程实践中,整个技术圈几乎陷入了一种 “重型基础设施内卷”:

只要遇到复杂的知识检索或多跳推理需求,团队的第一反应往往是:上昂贵的 Embedding 向量模型、上分布式向量数据库(Milvus / Qdrant)、甚至耗费数十万次 LLM 调用提取离线知识三元组去堆砌复杂的 GraphRAG。

然而,当系统真正进入生产运维阶段,软件开发工程师很快就会遭遇残酷的工程痛点:

  1. 离线建库成本爆炸:成千上万篇文档切片向量化和图抽取极度缓慢,耗尽 GPU 算力;
  2. 增量维护是一场噩梦:业务文档改动频繁,知识图谱或稠密向量只要修改一行,全局拓扑与索引的一致性维护让人痛苦不堪;
  3. 多跳逻辑严重脱节:向量相似度本质是“表面语义匹配”,根本无法解决诸如“A 关联 B,B 关联 C,但 A 与 C 语义差异极大”的隐式多跳事实链。

近日,来自 Zleap AI(智跃深空) 的独立研究者 吴宇超(Yuchao Wu) 发表了一篇极具反叛精神与工程启发性的论文:

《SAG2: Embedding-Free Multi-Hop Retrieval with BM25 and JEV》 (arXiv:2610.22588)
论文链接:https://arxiv.org/abs/2610.22588
HTML 格式:https://arxiv.org/html/2610.22588
开源项目:https://github.com/Zleap-AI/SAG-Wiki
基准评测:https://github.com/Zleap-AI/SAG-Benchmark
核心结论:彻底抛弃向量(Embedding-Free),不做离线图抽取,仅依靠单文件 SQLite 倒排索引(BM25)与轻量判别模型 JEV 的在线扩展,在 MuSiQue、2WikiMultiHopQA 等多个多跳基准上全面超越传统向量 RAG 与 GraphRAG!

本文将站在软件开发工程师、系统架构师与后端研发的视角,深入剖析 SAG2 的问题解决范式、状态机检索循环、工程直觉,并对其闭源 JEV 依赖的局限性、自研开源替代方案,以及向视频文字多跳检索的架构泛化进行深度探讨。


01. 架构理念的反差:重离线 vs 重在线

要理解 SAG2 的突破,首先必须对比它与传统主流方案在“计算工作负载分配(Workload Allocation)”上的根本分野:

1
2
3
4
5
6
7
8
9
10
11
【传统向量 RAG / GraphRAG:重离线,轻在线】
原始文档 ──► [ 昂贵 GPU 离线抽取 / Embedding ] ──► 庞大向量库 / 静态全局图 ──► [ 单次余弦相似度匹配 ]
(耗费数小时、极度昂贵、增量更新困难) (无法解决隐式多跳)

【SAG2 范式:极轻离线,在线动态扩展】
原始文档 ──► [ SQLite FTS5 倒排索引 (纯 CPU,零模型计算) ] ──► 仅几十 MB 单文件
(1.1 万段落建库仅需 2.36 秒!增量更新就是标准 SQL 写)
│
▼
[ 在线 Query 驱动的状态机 ]
BM25 粗筛 ◄──► 决策剪枝 ◄──► 桥接词抽取 ◄──► 动态超边关联

传统方案在软件工程中的三座大山

  1. 语义碎片化(Semantic Fragmentation):GraphRAG 强行把连贯的文章切碎为 (主语, 谓语, 宾语) 三元组,丢失了完整的叙事上下文和细节事实;
  2. 图谱维护的雪崩效应:在真实企业内网中,文件每天都在频繁增删改查。维护一个静态全局知识图谱,其增量 ACID 事务和去重合并成本是指数级的;
  3. 多跳逻辑阻抗失配:单纯依赖向量空间内积计算,命中相关段落就像在盲盒中摸奖,对于有前因后果链条(Cause-and-Effect Chain)的复杂问题极其脆弱。

SAG2 提出了逆向思维:离线不做任何不可逆的语义压缩或图抽取,把一切关联计算推迟到查询时(Query-Time),通过在线循环启发式搜索动态构筑证据超边。


02. 核心机制:Query-Time 动态状态机循环

SAG2 的完整流程由极速离线倒排索引与在线三轮受限扩展构成:

flowchart TD
    subgraph Offline["1. 极速离线建库 (Pure Local)"]
        D["本地文档 / 代码 / 笔记"] --> FTS["SQLite FTS5 倒排索引 (BM25)"]
    end

    subgraph Online["2. 在线有限状态机检索 (Query-Time)"]
        Q["用户原始查询 q"] --> R0["Round 0: 词法粗筛 (BM25 Top 100)"]
        R0 --> J0["JEV 证据决策打分 (空上下文)"]
        
        J0 --> S0["提取 Top 10 证据段落作为 Seeds"]
        S0 --> Term["计算扩展优先级 ➔ 提取 Top 20 桥接词"]
        
        Term --> R1["Round 1: 带着桥接词回 SQLite 交错检索"]
        R1 --> J1["JEV 再次决策打分 (携带已知上下文评估)"]
        
        J1 --> Cond{"达到最大轮次? (H=2)"}
        Cond -- 否 --> S1["提取新 Seeds 再次展开"]
        Cond -- 是 --> DPP["本地 DPP 多样性重排 (消除冗余)"]
        DPP --> Out["输出最终 Top 10 紧凑证据"]
    end

状态机三步跳

  • Round 0(初始命中):用户问题直接在 SQLite FTS5 中做 BM25 检索,拿到初步候选池(B=100B=100),并锁定绝对靠前的 5 个结果受保护(Protected Seeds);
  • Round 1(一跳穿透):从高分段落中发掘“桥接词”,拿着词回 SQLite 检索出未见过的新段落,并携带 Round 0 的证据作为 Context 重新打分;
  • Round 2(二跳收敛):进行最后一轮补充,历史高分段落与新候选合并,严格截断预算,输出最终证据链。

03. 算法与工程直觉拆解

论文第三章给出了严谨的数学表达。我们剥离学术符号,从软件工程师的实现直觉去解读每一处设计:

1. 为什么建库只要 2.36 秒?(Section 3.1)

  • 实现方案:直接利用 SQLite 原生自带的 FTS5 全文检索虚拟表。
  • 字段权重:title: 2, heading: 2, body: 1。
  • 工程收益:在包含 11,656 个段落的 MuSiQue 数据集上,建库耗时仅 2.36 秒,生成的单文件仅 25.87 MiB,离线模型调用次数为 0,成本 $0。没有向量分块对齐,没有 GPU 预热,单机多线程 CPU 即刻完成。

2. 桥接词优先级公式背后的工程直觉(Section 3.3)

从证据段落向外发掘下一个线索时,系统根据以下公式给候选词 tt 打分:

ar(t)=max⁡d∈Sr[(1+ln⁡tf(t,d))⋅I(t)⋅(1+gq(t,d))⋅wr(d)]a_r(t) = \max_{d \in S_r} \Big[ (1 + \ln \text{tf}(t, d)) \cdot I(t) \cdot (1 + g_q(t, d)) \cdot w_r(d) \Big]

工程师的直觉逻辑:

  1. tf(t,d)\text{tf}(t, d)(局部重要性):词语在当前命中断落中出现越多次,越可能是该段落讨论的核心实体;
  2. I(t)I(t)(全局罕见度 / 逆文档频率 IDF):如果该词全库到处都是(如“系统”、“公司”),直接权重归零;如果它是独特专有名词(人名、型号、地名),权重极高;
  3. gq(t,d)g_q(t, d)(句子级查询重合度,最核心精髓):若词 tt 所在的那个句子,刚好包含原问题中的词,说明这个词是原问题实体向外延展的“强力跳板”(Bridge Term);
  4. wr(d)w_r(d)(段落置信度):来自高分段落的词权重高,低分段落的词被阻尼衰减。

典型案例:
用户问:“Erik Hort 出生地的县治(County Seat)是哪里?”

  • 初始命中段落:提到“Erik Hort 出生于 Montebello, New York”;
  • 桥接词提纯:在与“Erik Hort”同一句中,罕见地名“Montebello”以极高权重入选;
  • 状态机二跳:以“Montebello”为 Key 再次查库,立刻命中第二跳段落:“Montebello 属于 Rockland County……”。原本两篇毫不相关的文档,在毫秒级内通过动态关联拼接完成。

3. 边界约束设计(Bounded Retrieval,Section 3.5)

工程系统最忌讳无休止递归与雪崩。SAG2 在边界控制上极其克制:

  • 最大跳数限制:写死最多扩展两轮(H=2H=2),即至多 3 轮打分;
  • 打分预算硬截断:单次用户查询的全局模型调用次数被硬限制在 340 次以内,杜绝并发风暴。

4. 本地多样性重排(DPP 算法,Section 3.4)

  • 搜出来的 Top 10 如果全在讲同一件事怎么办?
  • 系统利用行列式点过程(DPP)在本地用稀疏 TF-IDF 向量投影消除语义重叠,强制选出能互补拼图的跨领域证据。
  • 纯本地 CPU 矩阵运算,耗时不到 1ms,完全不调用模型。

04. 源码实战:开源 SAG-Wiki 的架构实现

SAG2 对应的开源项目是 Zleap-AI/SAG-Wiki。其代码库展现了极高水平的系统级 Rust 与前端工程化素养:

1
2
3
4
5
6
7
8
9
SAG-Wiki/
├── core/ # 核心检索引擎 (Rust)
│ ├── src/retrieval.rs # 论文中的有限状态机、扩展词提取与调度循环
│ ├── src/sag2_v10.rs # 内存安全词表构建、别名修复、DPP 重排
│ ├── src/store.rs # SQLite FTS5 封装、ACID 监听与段落拆分
│ └── src/models.rs # Jev / OpenAI 接口封装、批处理与取消控制
├── cli/ # 独立的 Node / Rust CLI 工具与 Agent Skill
├── src-tauri/ # 跨平台桌面外壳 (Tauri 2)
└── src/ # 现代化用户界面 (Svelte 5)

核心工程亮点

  1. 轻巧极致的发行体积:
    • 传统带向量数据库的 RAG 桌面工具动辄 500MB 到数 GB;
    • SAG-Wiki 凭借 Rust + SQLite + Tauri 2,整个桌面端安装包仅约 14 MB,启动即搜。
  2. 防御性内存设计(VocabularyBuilder):
    • 在 [core/src/sag2_v10.rs](file:///tmp/sag2_v10.rs) 中,为了防止大文件导致内存 OOM,专门实现了流式词表构建器,设定了严格的字符预算保护,单文件超出限额即熔断。
  3. Agent 优先(Agent-Native)协议:
    • 随仓库发布了 [cli/skills/sag/SKILL.md](file:///tmp/README-CN.md),支持直接把 CLI 作为独立 Skill 挂载到 Claude Code、Codex 等 AI 智能体中,人机共用同一个单机 SQLite 知识库。

05. 客观反思与技术债:JEV 闭源依赖的死穴与自研平替

任何客观的系统分析都必须正视架构的短板。在这篇论文中,最受争议也最具商业风险的设计就是 JEV 模型。

1. 致命软肋:强依赖第三方托管 API

论文作者在 5.5 节(Discussion and limitations) 明确坦承:

JEV 目前是 TypeSafe AI 的云端闭源模型,模型结构与权重并未公开。这导致虽然本地检索索引是轻量的 SQLite,但整套系统目前无法完全离线在企业私有化内网独立部署。

2. JEV 究竟在干什么?(撕下玄学外衣)

看穿 JEV 的本质,它并不是一个深奥的黑盒,而是一个带上下文的二分类判别器(Binary Classification Verifier):

  • 输入:[原始问题, 已知上下文证据, 当前候选段落];
  • 输出:直接通过单一 Forward Pass 输出 0.0 到 1.0 的概率浮点数(type: "noul"),判断该段落是否是必要证据。
  • 优点:不需要生成多余文字,解码开销几乎为零,吞吐极高。

3. 工程师如何破局?3 条完全自主可控的开源替代路线

若要在私有化环境真正独立落地 SAG2 架构,我们可以用以下三种方案平替 JEV:

1
2
3
4
5
6
7
8
【路线 A:开源小模型 Logits 抽取 (推荐,极致兼容)】
部署 Qwen2.5-3B/7B ──► 开启 logprobs ──► 单 Step 推理 P("Yes") ──► 0.0~1.0 概率输出

【路线 B:Cross-Encoder 交叉重排器微调】
采用 bge-reranker-large ──► 拼接 [Context + Query, Candidate] ──► Sigmoid 输出得分

【路线 C:回退至 SAG 1.0 (纯单机无卡环境)】
采用关系表 (事件表+实体表) ──► 纯 SQL JOIN 关联 ──► 零大模型调用

在 SAG-Wiki 源码 [core/src/service.rs](file:///tmp/service.rs) 中,系统原生支持挂载自定义端点(sag config --url http://127.0.0.1:8000/v1 --auth none)。用 Python FastAPI + vLLM 封装几十行代码,就能实现 100% 独立内网闭环。


06. 架构泛化:能否将 SAG2 迁移至“视频文字检索”?

当前视频检索领域对长视频通常采用:时间切片 ➔ 提取 ASR 对白 + OCR 画面字 + VLM 密集动作描述(Visual Caption)。既然切片已经被降维成了文字事实描述,SAG2 的算法是否适用?

答案是:不仅完全适用,而且天生契合视频长时序因果检索的痛点!

1. 痛点降维:解决视频中的“时序多跳因果”

  • 典型查询:“那个在走廊摔倒的白衣护士,后来把病历交给了谁?”
  • 纯向量搜索:摔倒片段没有提病历,交付片段没有提摔倒,两头落空;
  • SAG2 范式:
    1. 初始检索定位“摔倒”切片;
    2. 提取局部特征桥接词“白衣护士”;
    3. 状态机在时序片段中扩展检索,无缝串联起病历交付的后续切片。

2. 视频落地的工程改造要点

  1. 引入时间衰减先验(Temporal Locality Boost):文本没有物理距离,但视频切片有明确的时间戳。在计算扩展词优先级时,对时间临近(如前后 5 分钟内)的候选切片赋予更高的关联权重;
  2. 防御 VLM 同义词漂移:不同镜头的大模型描述可能出现词汇不一致(“红衣服” vs “朱红色夹克”)。可在预处理切片时打上规范化的目标检测实体标签(Tags),弥补纯词法分词的不足;
  3. 两阶段架构终审:前 99% 的时序切片依靠 SQLite 毫秒级锁定到 Top 5,最后仅将这 5 个关键片段的关键帧喂给小型多模态大模型做单次视觉确认(Visual Grounding)。

07. 评测基准与实测数据

在经典多跳问答基准与企业级数据集上的评测充分证明了这套架构的强悍生命力:

检索方案 索引准备耗时 MuSiQue (困难多跳) R@10 2WikiMultiHopQA R@10 HotpotQA R@10
纯 BM25 极快(秒级) 54.41% 71.50% 86.90%
BGE-Large-EN (顶流向量模型) 慢(全量向量化) 64.08% 73.75% 93.35%
NV-Embed-v2 (7B 顶级向量模型) 极慢(需大显存 GPU) 77.27% 80.75% 97.70%
HippoRAG 2 (知识图谱方案) 极慢(庞大离线抽取) 73.76% 93.40% 97.15%
SAG2 (本文) 极快(2.36 秒,纯 SQLite) 86.62% 97.43% 99.00%

在 MuSiQue 上,完整证据链覆盖率(All-Evidence Coverage)从传统 BM25 的 17.4% 飙升到了 SAG2 的 69.5%;跑完 1000 个复杂多跳问题的总线上模型账单仅为 $4.32(单次查询仅约合人民币 3 分钱)。


08. 软件工程师的总结与启示

从软件工程实现与系统设计的维度来看,SAG2 与 SAG-Wiki 带来了极其宝贵的方法论沉淀:

  1. 破除对新技术的盲信:向量数据库和图数据库并非解决知识检索的唯一解,甚至是很多场景下的“过度工程化”。老当益壮的 SQLite FTS5 / BM25 倒排索引,在灵活性、稳定性和极致成本上依然无可替代;
  2. 将模型从“生成器”降级为“判别剪枝器”:不要让大模型去写长篇大论来做中间推理,把它作为带上下文的二分类校验器,配合状态机剪枝,系统具备确定性、高吞吐和强可观测性;
  3. 极简主义是软件交付的最高准则:单文件存储、无守护进程依赖、14MB 极速安装,能用经典算法解决的问题绝不堆砌庞大的基础设施。这才是真正经得起生产环境考验的工程艺术。