zvec-grep:把 ripgrep、BM25 和向量检索拧成一个入口——人和 Agent 共用同一份索引

阿里 zvec 团队的新项目 zg:一个本地优先的统一检索层,语义发现 + 词法排名(RRF 融合)+ 精确正则走同一个入口,默认 MCP 只给 Agent 一个搜索工具。配对 A/B 评测里输入 token 少 47%、工具调用少 59%,而回答质量没掉——顺便拆解它为什么这么设计,以及那五条少见的诚实评测方法论。

  • AI
  • Agent
  • 检索
  • MCP
  • RAG
  • 代码理解
  • 开源项目

先说一个我这段时间反复遇到的场景:让编码代理去陌生仓库里回答一句"这个项目的插件生命周期是在哪儿注册的"。 它接下来做的事很朴素——grep 一轮、换关键词再 grep 一轮、 读三个"看起来相关"的文件、发现自己走错了目录、再来一轮。 二十几次工具调用、几十万的输入 token,烧的全是找东西,不是想东西。

问题不在代理笨,在它手上只有两类工具,而这两类各瞎一半:grep 精确但不懂语义——你不知道那个概念叫什么名字它就救不了你;向量检索 懂语义但丢了精确性和结构—— 它能把"语义相近的文本块"捞上来,却说不清这个函数是谁调用的、这个标识符到底出现在哪一行。

这篇写的 zvec-grep 就是冲着这个裂缝来的。 它的官方口号我很喜欢:

Know the words—or don't. Just zg.(知道关键词,或者不知道,都行——用 zg。)

面向人与 Agent 的本地优先统一检索层。

项目很新:2026 年 7 月 10 日建的库,TypeScript 写的,Apache-2.0,到 9 月初约 2100 star,版本 0.2.1。 但背景不算新手——它是 zvec(阿里开源的进程内向量数据库,1.5 万+ star)团队做的上层产品, 命令名叫 zg(zvec-grep)。这也是为什么它的文档写得异常完整:八篇指南、两套可复现 benchmark、CI 上 80% 的覆盖率门槛。

一、它是什么:一个入口,三条路

一句话:把 ripgrep、BM25、向量检索接到同一个本地入口后面,人和 Agent 用同一份工作区索引。 你不需要先决定"这次该用语义搜索还是 grep"——三条路都在,选哪条由查询本身的性质决定。

zvec-grep · 一个入口,两条路,一份紧凑结果文件、索引、模型默认都留在本机人 · 终端 / 脚本zg query / zg indexAgentMCP · 默认只暴露 1 个工具auto / server / direct127.0.0.1:7999/mcpzg 引擎 · 同一个工作区,两种取证据的方式索引检索BM25 / FTS + 向量RRF 把两路排名融到一起托管 ripgrep精确文本 · 正则 · 穷尽扫描不需要索引,也不需要 embedding紧凑结果 · 为 token 预算设计按查询分组 · file:line · fresh / possibly_stale · 预览默认不给工作区索引 <root>/.zvec-grep/扫描 → 结构感知抽取(tree-sitter 符号 / Markdown 标题段) → 本地 embedding托管 rg 那条路由 zg 统一输出格式,命令被解析成参数、从不交给 shell 执行
图 1 · zg 的结构:两个入口、一个引擎、两条检索路、一份紧凑结果;索引层默认全部留在本机

几个关键设计,从这张图里能读出来:

  • 两条路,不是一个模型。索引检索那条路把 BM25/FTS 的词法排名和向量的语义排名用RRF(reciprocal rank fusion) 融到一起;托管 ripgrep 那条路直接扫文件,默认穷尽,不需要索引也不需要 embedding 模型。它没有假装向量能替代 grep。
  • 同一份索引,人和 Agent 共用。索引落在 <root>/.zvec-grep/ 里, 三个主要文件:manifest.json、files.zvec、index.zvec。 你在终端 zg query 命中的,和代理通过 MCP 命中的,是同一个东西——不会出现"人搜到的和 Agent 搜到的不一样"。
  • 执行模式三选一:auto(默认,有现成 daemon 就用,没有就在当前进程里跑)、server(要求 daemon,共享状态、后台刷新)、direct(一次性、CI、不留后台进程)。 差别只在进程生命周期,搜索行为完全一样。
  • 默认本地。扫描、rg、索引存储、本地 embedding 推理都在这台机器上, Server 只监听回环。zg install 之后不需要任何云服务。

二、真正的分界线不是"代码还是文档",而是"你有没有精确锚点"

官方文档里我认为最值得抄下来的一句话在 Agent 集成指南里:

代码还是非代码,不是分界线;一个工作区里可能同时混着代码、文档、配置和数据。分界线是:这个答案该不该落在当前这个被索引的工作区里,以及你手上有精确锚点还是没有。

它给 Agent 写的路由规则是这样的四条:

意图该用的工具
要找的就是精确的词、引文、名字、日期、配置键、文件名、路径或正则代理自己的 grep / rg
措辞或位置不知道,或者要的是语义、模糊、关系、时间线、因果、对比、跨文件综合zvec_grep_search
精确锚点已知,但答案需要更大上下文或综合先 zvec_grep_search,再原生 grep 收尾
跟本地无关的开放世界知识、当下外部事实、网页内容外部来源,不要用 zg

注意第一行和第四行:zg 明确写着"这种情况别用我"。文档里还有一条更细的约束—— 当语义检索只是因为"找不到足够好的精确锚点"才被选中、且用户问的是"本地有没有相关材料"时, Agent 最多做一次聚焦探测,结果不相关就停。

三条可选路由摆在你面前:--hybrid(语义 + 词法)、--fts(索引内排名)、--vector(纯语义)、--rg(穷尽字面/正则),默认那个位置参数就是 hybrid, 多组查询用 --fuse 融成一个排名计划。用法上不需要"先选引擎",只需要"这次要不要精确"。

三、索引这一侧:结构感知、模型可选、边界诚实

索引不是"把文件切碎扔进向量库"。zg 的扫描器给每个文件挑一条抽取路径,能拿到结构就拿结构,拿不到才退回纯文本分块:

  • 有语法树的代码——C/C++、Go、Java、JavaScript/JSX、TypeScript/TSX、Python、Rust 走 CodeExtractor(依赖 web-tree-sitter),索引里存的是符号、签名、breadcrumb 层级路径加周边源码。所以能按module / class / interface / function / value / alias这六种符号类型过滤,也能 --prefer-symbol 偏好命中一个真符号。
  • 组件文件——.vue、.svelte 抽 <script> 块里的 JS/TS。
  • Markdown——按标题段切,带 breadcrumb。这解释了 README 里那句 "同一套检索循环跨领域可用:代码按符号索引,散文按聚焦的 section 返回"。
  • 纯文本 / CSV / JSON / TOML / YAML / HTML / XML——普通分块。
  • 位图图片——只有你显式纳入、且选的模型支持图像时才进向量索引。

尺寸上限是按类型给的:代码 1 MiB、文本与 Markdown 256 MiB、结构化数据 16 MiB、图片 10 MiB。 这里有个必须知道的硬边界:PDF、Word、PPT、Excel、压缩包、可执行文件、音视频、sqlite 目前全部在抽取前就被跳过。 Roadmap 里排第一的就是把 PDF/PPT/HTML/图像做成一等公民,但现在没有就是没有。

Embedding 模型给了十四个选项,思路是"从最小的能用的开始":

需求模型形态
代码库,想快local/potion-code-16m-v2Model2Vec 静态向量 · 1024 token · 256 维
英文文档检索local/potion-retrieval-32mModel2Vec · 512 维
多语言文档(101 种语言)local/potion-multilingual-128mModel2Vec · 256 维
代码专用 Transformerlocal/jina-embeddings-v2-base-codeONNX Q8 · 8192 token · 768 维
多语言代码 + 文档local/embeddinggemma-300mGGUF Q8_0 · 2048 token
不想在本机跑模型qwen/qwen3.7-text-embedding远程 API · 128K token

Potion 那三个是 Model2Vec 静态模型:查表得到向量,几乎零成本零延迟,代价是语义能力弱于 Transformer (官方也直说选设备对这类模型没用,GPU 帮不上忙)。Transformer/GGUF 那批支持auto/cpu/metal/vulkan/cuda。两个坑要记住:换 embedding 模型必须 --rebuild(向量空间不兼容,即使维度相同),远程 endpoint 变更同样算不兼容。

隐私这条线画得比多数同类项目清楚:唯一可能把内容送出机器的路径是远程 embedding, 而它需要单独授权。--allow-remote 只对当条命令生效,想长期用要显式签一个工作区级许可:

zg auth grant "/absolute/workspace" --capability embedding --scope workspace
zg auth status
zg auth revoke

文档里反复强调一句:MCP 的工具批准不等于数据外传授权,两件事分开。 本地 Server 的 Bearer token 保护的是本地端点,也不是远程授权的凭据。

四、给 Agent 用的检索层,最有意思的是它的克制

zg install --target claude 之后,Agent 拿到的是 MCP 端点http://127.0.0.1:7999/mcp。默认的 agent 工具集里——只有一个工具:

工具集暴露什么
agent(默认)只有 zvec_grep_search。精确查找留给 Agent 原生的 grep/rg。
full(兼容)6 个:search、rg、index、index_drop、index_status、server_status。

这个选择挺反直觉的。多数 MCP server 拼命往工具列表里塞东西,因为"能力"是最好的卖点; zg 反过来把索引管理(index/--drop)留在 CLI 手里,文档里的理由是"索引生命周期和诊断归 CLI 所有,Agent 通常不需要管理类工具",并且明文写着 "Agent 绝不能悄悄创建、重建或删除一个持久索引"。工具越少,代理选错的概率越低——这条在实践里被验证过太多次。

输出格式也是按"要进模型上下文"来设计的,这是我觉得最值得抄的部分:

  • 默认不返回源码预览。索引命中的预览被省略,除非你要(--preview short/full); 给人看的富文本要显式 --human。同一条命令的两种输出,一种省 token,一种省眼睛。
  • 按查询分组、按组内排名排序,一次调用可以带多组语义 + 词法约束:query、queries、fts、vector 加 fuse。
  • 自带新鲜度标记:结果头部写 freshness: fresh 或 possibly_stale, 代理不用先跑一次 status 预检。
  • 托管 rg 不接受改输出格式的选项:--json、--count、-l、-o、--replace、--vimgrep 一律拒绝,因为"输出格式是 zg 的"。 而且你传给它的 rg 命令是被解析成参数的,从不交给 shell 执行。

Agent 集成的支持面也够宽:Codex、Claude Code、Qwen Code、Qoder(CLI + IDE)、OpenCode、Cursor 六个,zg install 会写对应的 MCP 条目、追加检索指引、配好工具批准并顺手拉起本地 Server。 写进配置文件的部分用 ZVEC_GREP_START / ZVEC_GREP_END 标记圈住,块外内容原样保留,uninstall 只删自己加的东西(包括 Qoder 那几条精确的 permissions.allow 规则)。

五、效果:配对 A/B,以及一份少见的诚实方法论

官方给的两组数,协议都是配对 A/B——同任务、同 Agent、同模型、同 prompt、同环境和资源限制,唯一差别是有没有 zg;索引构建时间单独计时,不混进 Agent 执行耗时。

配对 A/B:唯一差别是「能不能用 zg」灰 = Baseline · 红 = 加 zg · 索引构建单独计时SWE-QA-Bench · 代码仓库问答(Coding)20 个跨文件任务 · 11 个仓库 · 每个 profile 独立跑 3 次 · Claude Code + Claude Opus 5回答质量 Judge80.4281.92+1.50 pp ↑输入 Token559K294K−47.3%工具调用23.429.70−58.6%Agent 耗时127.5s79.7s−37.5%BrowseComp-Plus · 深度研究问答(通用文本)100 个 case · 300 组配对 trial · 约 10 万篇固定语料 · Codex + gpt-5.6-sol回答准确率98.67%99.00%+0.33 pp · 持平输入 Token1.68M1.05M−37.56%工具调用25.4214.36−43.52%Agent 耗时259.4s159.3s−38.58%Token 平均降 37.6%,case 级中位降幅 25.8%,100 个 case 里 67 个更低——收益不全靠几个极端长尾轨迹
图 2 · zg 在代码问答与通用文本检索上的配对 A/B 结果(灰为 Baseline,红为加 zg)

两组数字合起来讲的是同一件事:回答质量基本没动,资源几乎腰斩。 代码那组(20 个跨文件任务、11 个仓库、Claude Opus 5)Judge 从 80.42 到 81.92, 输入 token 少了 47.3%、工具调用少了 58.6%、Agent 耗时少了 37.5%。 通用检索那组(约 10 万篇固定语料、100 case / 300 组配对 trial)准确率 98.67% → 99.00%(已经顶到天花板,没多少上升空间), token −37.56%、工具调用 −43.52%、耗时 −38.58%。

更该看的是他们怎么处理"平均数会骗人":token 平均降 37.6%,但 case 级中位降幅只有 25.8%,100 个 case 里 67 个更低。 平均值高于中位数说明有几个 Baseline 高消耗的长尾轨迹被大幅缩短——收益确实存在,但不是靠几个极端值撑出来的。 这类自曝式细节在自己的 benchmark 里相当少见。

benchmark 文档末尾那节「评测注意事项」我读得比正文还认真,五条基本是可以直接抄走的 Agent 评测规范:

  1. 条件必须一致——他们直说:强模型的 Baseline 可能赢过弱模型的 Treatment, 因为更强的推理能力和先验知识能补上检索的差距,这种比较根本量不出工具本身的作用;
  2. 指引要保持通用——如果 prompt 要求 Agent"永远用 zg 别用 grep", 就等于人为夸大收益;反过来强制禁用也一样偏。用哪个工具、何时用,交给 Agent 判断;
  3. 随机波动不均——他们观察到回答质量在多次运行间相对稳定,而 token 消耗和工具调用次数波动明显更大, 因为不同探索路径直接改变搜索和阅读步数;结论是要分别跑相同次数的独立试验取均值;
  4. 先确认工具可用——他们发现一个很实用的现象:Agent 首次调用外部工具失败后,往往直接改用别的办法、不再重试, 一次失败就能改写整条执行轨迹,所以正式评测前必须单独冒烟;
  5. 防信息泄漏——参考答案绝不能进工作区或索引(否则 Agent 直接检索到现成答案), Judge 要盲评。还提醒了更早发生的泄漏:公开 benchmark 的题目可能早已进了模型训练数据, 模型靠参数里的记忆作答,检索工具的价值自然量不出来。

至于"什么时候最能帮上忙",官方给的边界也很具体:目标位置未知、证据跨文件或模块分布的调用链、数据流和架构问题。 三个案例研究正好对上:Pylint 要搞清 AST 节点处理怎么区分带不带类型标注的属性初始化(入口事先未知); Matplotlib 要沿数学文本渲染的多个阶段追 FontInfo 和字体选择(跨文件数据流); Django 要关联 username 唯一约束、ORM 事务和 formset 批量操作(设计依据散落多处)。 反过来,如果精确关键词本来就够了,zg 的增量收益很小,表现通常就是接近 Baseline。

六、三十秒上手:官方那个"神秘脚印"演示

上手流程一共三步。README 用的是两本公版书——爱丽丝梦游仙境 和 福尔摩斯归来记—— 当"书架"演示,这个选择本身就很有意思:它演示的不是代码搜索,而是一个纯粹的语义检索问题。

# 需要 Node.js 22+
npm install -g @zvec/zvec-grep

mkdir zg-mystery && cd zg-mystery
# ...(下载爱丽丝 + 福尔摩斯两个 txt)...
zg index --embedding local/potion-retrieval-32m

然后接上 Agent 并问一句话。注意 prompt 里没有提任何工具名:

zg install --target opencode --yes

opencode run --model opencode/nemotron-3-ultra-free \
  "An unseen creature left a few marks. What did the detective infer? Cite local evidence."

OpenCode 自己选了 zg,一次调用就拿到证据:

⚙ zvec_grep_search {"root":".../zg-mystery",
   "query":"An unseen creature left a few marks. What did the detective infer?",
   "fts":["marks","unseen creature","inferred","detective"],
   "fuse":true}
→ Read sherlock-holmes.txt [offset=5440, limit=120]

回答是带行号引用的:五趾肉垫、长爪、印子接近甜点勺大小 ⇒ 不是猫狗猴(sherlock-holmes.txt:5479-5486); 身长近两英尺、步幅只有约三英寸 ⇒ 长身短腿(:5497-5502);朝金丝雀笼爬 ⇒ 肉食(:5509-5510); 大概属于鼬或白鼬一族,只是异常大(:5514-5516)。一次语义 + 词法联合检索,换到 grep 路线大概是要摸好几轮的—— 因为你根本不知道原文用的是 foot-pads 还是 marks 还是 impression。

不通过 Agent,人直接搜同一个书架:

zg query --human "An unseen creature left a few marks. What did the detective infer?" --limit 3

# 显式选路
zg query --fts "AuthService"
zg query --vector "where credentials are validated"
zg query --hybrid "authentication flow" --fts "ForbiddenError" --fuse --limit 10
zg query --rg -n -F "AuthService" -g "*.ts" src   # 不需要索引

大仓库建议一开始就收窄范围,别把 dist 和依赖目录一起嵌了:

zg index \
  --embedding local/potion-code-16m-v2 \
  -g "src/**" -g "docs/**" -g "!dist/**" -t ts

七、和 graphify、RAG、原生 grep 怎么摆位置

这篇很容易让人想起我写过的graphify——同样是"给代理换个找东西的方式", 但路线差别很实在:

路线擅长
原生 grep / rg穷尽字面匹配锚点已知、要一个不漏
典型 RAG切块 + 向量语义相似,但丢结构、丢精确
graphifyAST → 知识图谱关系类问题:谁调用谁、最短路径、枢纽节点
zvec-grepBM25 + 向量 + rg 三路统一,RRF 融合位置未知、证据跨文件,且同时要语义广度和精确锚点

zg 的位置不是"取代 grep",而是把 grep 保留为自己的第二条路——这跟 graphify 明确反对向量检索正好相反。 有意思的是 Roadmap 里两边正在靠拢:zg 明确把"加知识图谱构建与图检索,补充 BM25、向量和托管 rg"列为产品方向之一, 还要做多路混合(词法、向量、图、结构、元数据)和更好的查询规划。也就是说,"图谱派"和"混合检索派"最终会在同一层里见面,差别只是谁先长出来。

另外一个容易搞混的点:zg 是检索层,不是 harness。 它不参与编排、不写计划、不做 memory,只管"你要的证据在哪几行"。也正因为它管得窄,它可以直接塞进任何一套 AI-native 工作流里,和 Superpowers、ECC 那类东西不冲突。

八、诚实的提醒

  • 这是公开预览版。0.2.1、建库不到两个月、28 个 open issue, Roadmap 现在的目标就叫"make the public preview dependable"——CLI、MCP 契约、索引兼容性策略都还没宣布稳定。 现在把它写进团队基础设施要有返工准备;
  • PDF / Office 现在搜不了。.pdf、.docx、.pptx、.xlsx 被明文列在"抽取前跳过"里。 "多格式检索"目前的真实含义是代码 + 文本类 + 结构化文本,不是办公文档;
  • benchmark 用的是远程 embedding。两组官方评测的 Profile 都是 qwen/qwen3.7-text-embedding, 而卖点之一是"默认本地"。本地小模型(尤其 Model2Vec 的 Potion)能不能复现同样的 token 降幅,文档没给数据—— 这不是造假,但读的时候要分清"验证过的配置"和"默认的体验";
  • 每个工作区都得单独建索引,换 embedding 模型必须全量重建。索引存在项目根目录的.zvec-grep/,记得自己加进 .gitignore(文档只保证扫描时永远排除它);
  • 收益取决于模型会不会用。官方自己写了:"Agent 会自主决定何时及如何调用,结果因此会随模型和运行波动;多次运行均值更可靠"。 换个弱一点的模型,它可能压根不调用,或者调用一次就误判;
  • 本地 Server 默认不带 token,只监听回环。共享机器、容器里跑多租户的场景请显式开 Bearer;
  • 它会改你的 Agent 配置文件。~/.claude/CLAUDE.md、~/.codex/config.toml 这些都会被写入检索指引。 有标记圈住、块外内容保留,但装之前最好先看一眼 diff。

我的收获

  1. "检索层的双受众设计"是个真问题,而答案不是两套工具。同一份索引、同一个引擎, 给人开 --human(富输出 + 完整预览),给 Agent 用默认(紧凑 + 省 token)。--preview 默认关闭这种细节,是"上下文是要花钱的"这个新约束真正落进 CLI 设计里的样子。
  2. MCP 工具的克制本身就是准确率。默认只暴露一个搜索工具,把精确查找留给代理原生的 grep, 把索引管理留在 CLI,并且明文禁止 Agent 悄悄重建或删除索引。 多数 MCP server 该学的不是"再加一个能力",而是"再砍一个入口"。
  3. 别问哪个检索技术更强,问这次有没有精确锚点。这条路由规则可以脱离 zg 用到任何检索设计上: 有锚点走精确,没锚点走语义,锚点已知但要综合就先语义再精确,与本地无关就出去查。 把"选引擎"变成"读问题",Agent 的决策成本一下就降了。
  4. 配对 A/B + 报告中位数,是 Agent 工具评测的最低诚实标准。 "平均降 37.6%、中位降 25.8%、67% 的 case 更低"三句话,比一句"效率提升 37%"可信十倍。 那五条评测注意事项(条件一致、指引通用、随机波动、工具可用性冒烟、信息泄漏含训练数据污染)我建议存下来, 自己评任何 Agent 工具都用得上。
  5. 省下的 token 就是省下的钱和续命的上下文窗口。输入 token 少 47%、工具调用少 59%, 在一个五十步的任务上不是"优雅一点",而是"能不能在窗口内跑完"的区别。 以后判断一个检索/上下文工具值不值得装,我就看这两个数:它替我省了几次工具调用,以及质量掉了没有。

想深入可以读 GitHub 仓库、架构文档、Agent 集成 和benchmark 说明—— 中文 README 和中文 benchmark 文档都在库里,读起来比英文顺。 想试又懒得动手,就照官方那个书架例子来:两个 txt 文件、一条 zg index, 然后问它一句你手上任何"知道意思但不知道关键词"的问题——那一次调用省掉的往返,基本就是它全部的价值所在。