zg(zvec-grep)使用指南:把 ripgrep、BM25 和语义搜索装进一条命令
ripgrep(rg)是所有开发者的肌肉记忆——但它有个天花板:你只知道意思、不知道措辞时,它无能为力。「处理用户余额扣成负数的那段逻辑在哪?」这种问题,rg 只能靠你猜出 balance、deduct 之类的词碰运气。反过来,纯向量搜索又会漏掉精确标识符,还常常不给你源码位置,等于让 LLM 猜。
zg(zvec-grep,阿里 Zvec 团队开源,Apache 2.0)的答案很直接:别选,全都要。它把 ripgrep、BM25 和向量语义搜索统一在一个本地优先的命令行界面后面,人能用、AI 代理能用,还共享同一份索引。Rust 实现正在 rust/ 目录并行开发中,目前的 TypeScript 版要求 Node.js 22+。
一、三种检索为什么必须混合
| 检索方式 | 擅长 | 天生短板 |
|---|---|---|
| ripgrep | 精确文本、正则 | 找不到「意思相近但措辞不同」的内容 |
| BM25 | 关键词词法排序 | 同上,且不懂同义 |
| 向量语义 | 按含义发现、跨措辞召回 | 会漏精确标识符,需要嵌入模型 |
zg 的融合排序把三者合并成带源码位置的紧凑证据。官方基准里最说明问题的数字不是「答得多好」,而是输入 token 和工具调用次数同时下降——对按 token 计费的代理来说,这是省钱的;对人来说,这是少翻十几个文件的。官方在 pylint、matplotlib、django 三个真实仓库上的案例研究表明,zg 最亮的场景是:证据跨文件/跨模块、且目标位置事先未知的调用链、数据流、架构类问题(比如「FontInfo 这个 NamedTuple 在数学文本渲染管线里怎么传播」)。如果问题本身就是一个精确标识符,rg 依然是最快的答案——zg 不取代它,而是补上它够不着的那半边。
二、五分钟上手:官方书架实验
拿两本公版小说当语料,最适合体会「语义搜索」和「关键词搜索」的区别:
# 需要 Node.js 22+
npm install -g @zvec/zvec-grep
mkdir zg-mystery && cd zg-mystery
curl --retry 3 --retry-all-errors --progress-bar -fL \
-o alice-in-wonderland.txt https://raw.githubusercontent.com/GITenberg/Alice-s-Adventures-in-Wonderland_11/master/11.txt \
-o sherlock-holmes.txt https://raw.githubusercontent.com/GITenberg/The-Memoirs-of-Sherlock-Holmes_834/master/834.txt
# 建索引,用本地嵌入模型
zg index --embedding local/potion-retrieval-32m
然后提一个不含任何关键词的问题:
zg query --human \
"An unseen creature left a few marks. What did the detective infer?" \
--limit 3
zg 会把《福尔摩斯回忆录》里相关段排在《爱丽丝》前面返回——注意查询里没有 “Sherlock”、没有 “footprint”,rg 在这里一个字都搜不出来。索引存在项目根的 .zvec-grep/ 目录下,之后自动增量刷新,不用手动维护。
命令挂了就加 --debug 重跑(直连和 server 模式都支持);zg status --mode direct --debug 能看到索引里每个文件的失败原因。
三、嵌入模型:本地是默认,不是妥协
zg 的立场是 local by default——文件、索引、模型全部留在本机,只有你明确授权时才会把数据发给远程嵌入服务。本机默认配置(~/.zvec-grep/config.json)长这样:
{ "version": 1, "defaults": { "embedding": "local/potion-code-16m-v2" } }
potion-code-16m-v2 是个极小的代码向模型,CPU 上跑得飞快,这也是 zg 敢默认本地的底气。需要更高召回质量时可以换:
- llama.cpp / Transformers.js 本地模型——重一些,质量更好;
- 远程嵌入模型(如 Qwen 系 text embedding)——质量上限最高,数据出本机需明确授权。
GPU 用户注意一个真实的坑:索引时的嵌入并发默认按「空闲显存 × 25% / 150MB」估算,上限 8。遇到 CUDA 报错、显存耗尽或原生崩溃,先把并发降到 1 重试:
export ZVEC_GREP_INDEX_EMBEDDING_CONCURRENCY=1 # PowerShell: $env:ZVEC_GREP_INDEX_EMBEDDING_CONCURRENCY="1"
zg --index --mode direct
也可以加 --device cpu 直接绕开 GPU。查询侧的推理不受这个并发参数影响。
四、接入 AI 代理(MCP)
一行命令把 zg 装进支持的代理(Codex、Claude Code、Qwen Code、Cursor、GitHub Copilot、VS Code、OpenCode 等):
zg install --target opencode --yes # 换成你在用的代理名
装完后代理获得 zvec_grep_search 工具。以官方 OpenCode 演示为例,代理的实际调用长这样:
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
}
注意这个参数设计的巧思:query 走语义,fts 数组同时喂关键词路,fuse: true 融合——一次调用就把三种检索全用了。代理拿到的是带文件名和行号的证据(sherlock-holmes.txt:5479-5486 这种),下一步精确读文件就行,不需要把整个仓库扫一遍。
五、索引策略:不是每个目录都值得建索引
zg 建索引很快,但索引不是免费的(磁盘 + 首次嵌入耗时)。我的实践规则,供参考:
- 真实项目、问题类型是「语义/架构/跨文件」时才建——比如「这个项目的鉴权是怎么串起来的」;
- 精确文本、标识符、路径、正则查找,直接 rg,不要为一次 grep 建索引;
- 家目录、下载目录、临时目录不建——零散文件没有检索价值,白白吃磁盘;
- 建了索引的 git 仓库,往
.gitignore加一行.zvec-grep/,别让索引进版本库; - 索引一旦存在就信任它的自动刷新,不要频繁重建或换模型(换了模型等于全部重嵌入)。
写在最后
参考:zvec-ai/zvec-grep(Apache 2.0)· 阿里云官方博客:From rg to zg · 路线图 Issue #128 · 架构图为本刊依据官方文档自绘。
zg 代表的方向我觉得值得所有工具作者抄作业:检索能力的升级,不该以改变使用习惯为代价。rg 的用户依然是 rg 的用户,只是多了一层语义;代理不是多了个要学的工具,而是少了一堆要翻的文件。「Know the words—or don’t. Just zg.」这句 slogan,起得很准。
❦ 终 ❦