大家好,我是小木头。最近我看到阿里开源的 zvec 以及建立在它上面的 zvec-grep,它可以给代码仓库建立向量索引,再让 Codex、Claude Code 这些智能体做语义搜索。但这里有一个很实际的问题:Codex 现在用 grep 搜代码,看起来好像已经相当好用了。在日常的开发中,它通常都能找到需要的文件,那我们为什么还要多建一份索引呢?这期视频我们就从它提供的功能开始了解,并尝试回答这么一个问题:zvec-grep 到底在什么场景下有用?

首先我们来看看 Codex 的搜索工具的底层原理。当使用 Codex 的文件搜索工具时,在你提出问题以后,模型会猜测可能的函数名、错误信息、目录等等,再用 RG 进行搜索。然后打开文件读取内容,再尝试基于关键词进行匹配。如果第一次没有找到,它还会更换关键词继续搜索。所以我们感觉它很好用,真正起作用的是精确的文本搜索加上模型的多轮查询和代码推理。

如果你已经知道函数名、配置键错误码或一段原文,grep 通常就是更好的工具。它直接搜索当前磁盘,并不需要索引,结果可以穷举,而且不会因为代码刚刚修改过而过期。比如查找 loadTheme 或者某个错误码出现在哪些文件里,向量搜索没有明显优势,甚至可能把本来很确定的问题复杂化。

差异出现在另一类问题上:当你知道现象和意图,但不知道代码里用了什么词。比如应用刷新后忘记暗色模式,代码里可能没有 forget refresh 或 dark mode,而是 hydrateStore、loadPersistedState 或者 bootstrapSession。grep 本身无法匹配含义,Codex 可以通过不断的猜测关键词来补上这层的能力。但每猜一次,就意味着多一次搜索、多一次读取和更多的上下文消耗。向量检索想缩短的正是这种场景的数据检索。

首先我们简单了解一下今天的主角 zvec 和 zvec-grep 两个项目。zvec 是一个开源的进程内向量数据库,核心技术来自阿里的 Proxima。它像向量检索领域的 SQLite——如果你熟悉 SQLite 的话,直接运行在进程里,数据保存在本地的目录,不需要单独维护数据库服务。Python 可以用 pip install zvec,Node.js 可以用 npm install @zvec/zvec 来安装。

zvec-grep 则是基于 zvec 面向代码仓库和智能体的搜索工具,它能够发现文件、切分代码生成 embedding,并把全文向量和精确搜索组织成一个工具。独立安装 zvec 是为了开发使用它的应用,并不是安装 zvec-grep 的前置步骤。

zvec-grep 并不会把所有的查询都强行的交给向量搜索:已知符号和原文时,可以继续用 ripgrep;关键词需要相关度排序时,可以走 BM25;表达不确定时才使用 Vector retrieval(也就是向量检索)。它还可以用 RRF 合并多条检索路径。它的目标并不是返回整个仓库,而是先排出一小组可能相关的代码,让智能体继续阅读和验证。

zvec-grep 安装非常简单,只需要两步:先运行 npm install -g @zvec/zvec-grep,再运行 zg install。安装过程中,它会检测像 Codex、Claude Code、Qwen Code、Qoder Open Code 和 Cursor,选择 Codex 后,它会更新 MCP 配置和搜索规则,并启动一个只监听本机回环地址的 Server(也就是 127.0.0.1)。重新打开智能体会话以后,就会多出一个叫 zvec-grep 的搜索工具。

接下来咱们进入代码仓库运行 ZG index,第一次需要选择 embedding 模型。代码仓库可以从本地的 potion-code-16m-v2 开始,模型源文件和索引都留在本机。云端 embedding 也是支持的,但向云端发送文件或查询文本之前,需要对当前的工作区做明确授权。这里要注意 ZG index 是建立或更新索引,只有使用 --rebuild 才会完整的重建。

接下来我们就在之前我开源的一个项目 chat-ollama 中,这个真实项目里做一轮对比。问题不能包含文件名、函数名或变量名,否则呢它只是在测试精确匹配。我只描述一个功能:用户分别在个人目录和项目目录下添加了自定义的智能体功能,应用如何发现这些内容、处理重名,并在一次新的智能体请求中让它生效。

两边使用相同的源码范围并排除已经记录过答案的开发文档,我们记录查询的轮次、打开文件和第一次找到的关键证据的位置。最后呢都要回到源码的验证。

如果只使用文件搜索,直接搜索问题里的 custom agent capabilities、personal directory 和 repository directory,在源码中没有命中。模型需要先判断这个功能在项目里可能被叫做 skill,再搜索相关文件。这一步会得到 8 个候选文件。接下来还要找到重名处理和 agent 创建入口等等,才能把完整流程拼起来。

grep 仍然能够帮助找到答案,但模型需要先把用户对功能的描述转换成项目采用的实现词汇。然后把含义相同的英文问题交给 zvec-grep,一次索引查询就把 agent 创建入口排在第一位,同时召回了负责扫描技能目录的实现。

顺着结果读取源码就可以看到:它会同时扫描用户目录和项目目录,用技能名称合并结果,并让项目级定义覆盖同名的用户级定义。创建 agent 时,它又把这些技能加入 middleware 和 instruction。这里的收益不是 grep 做不到,而是索引查询更快,给出了完整查询所需要的两个入口。

但把同一个问题直接换成中文呢?结果明显变差,排在第一的是完全无关的 onTopChat。真正需要的 createAgent 和 userSkills.ts 也没有进入前 10。这说明语义检索并不是天然跨语言的,在实际应用中我们需要考虑所使用的 embedding model(也就是文本嵌入模型)的多语言支持情况。

向量索引有效性的关键是数据:索引是否总是与源数据保持一致?使用 ZG install 启动 Server 的时候,它会监听工作区的文件变化并对修改过的路径做增量的更新。所以不需要每改一个文件都手动执行 ZG index,默认的后台策略会先返回当前的结果同时安排更新。

检测到变化时,结果会标记 possibly stale(可能过时)。如果这次查询必须看到刚写入的代码,也可以使用 --refresh wait direct 模式。默认是不刷新,这时如果不手动更新索引确实可能过时。

通过上面的比对,zvec-grep 适合的场景也更加清楚了:第一类是大型的陌生的代码仓库,智能体还不知道应该从哪个目录和符号开始;第二类是用自然语言描述故障、业务行为或架构问题,问题中的词与代码里的命名并不一致;

第三类是答案分散在多个文件甚至横跨代码、配置和文档,需要先召回一组相关的证据;第四类我想是同一个仓库会被多个会话或多个智能体反复查询,第一次建立索引的成本可以被后续使用来摊薄。

在这些场景中,zvec-grep 的价值主要是减少搜索轮次和进入上下文的无关内容。当然了它的代价是建立和维护索引,并且排序结果仍然需要回到源码来验证。

zvec-grep 本身并不是单一的向量搜索,它把精确匹配、BM25、向量召回和结果融合放进了同一个搜索层。真正值得测试的是这套检索层能不能针对问题选择合适的路径,并用更少的查询拿到高质量的候选。

需要对实时文件做完整确定的匹配时应该走 rg;需要相关度排序、语义发现或跨文件综合时可以走索引查询。复杂问题还可以把几条路线组合起来,找到入口以后再读取实时的源代码来确认。

是否值得维护这份索引应该由查询轮次、上下文的消耗、索引的成本和最终答案的质量来共同决定。

好了,我是小木头,今天的分享就到这里,我们下期再见。