Technical Learning Note · 2026-09-03

zvec-grep:从关键词搜索到 Agent 本地检索层

围绕 zvec-grep 0.2.1 的源码、项目文档和 Qwen Developers 发布内容,建立一套从文件进入索引,到 Agent 获得紧凑证据的完整心智模型。

Local-first BM25 / FTS Vector Search RRF Tree-sitter MCP
一句话理解:zg 不是“带向量的 grep”,而是放在工作区与 Agent 之间的本地检索层:语义搜索负责发现,BM25/FTS 负责词面相关性,ripgrep 负责穷尽式精确验证,结构化切分与紧凑输出负责减少进入模型上下文的噪声。
2 条检索路径
索引检索 + 直接扫描文件的 managed rg
RRF k=60
按名次融合 FTS 与向量结果,不直接混合异构分数
本地默认
扫描、切分、向量生成、索引与查询均可留在设备内
01

它解决什么问题

ripgrep 的强项是“已经知道要找什么”:函数名、错误字符串、配置键、文件路径或正则表达式。Agent 常遇到的却是“知道意图,不知道代码中的叫法”。例如,用户询问“启动时如何恢复主题”,代码可能使用 hydratePreferences,词面几乎不重合。

问题类型普通 rg 的表现zg 的处理
已知符号或原句快、精确、可穷尽直接保留 rg 路径,不必建索引
只知道自然语言意图需要反复猜关键词向量召回语义相近片段
跨文件关系与设计原因匹配结果没有相关性排序FTS + 向量多路召回,统一排序
Agent 上下文预算有限容易读取整文件或大量匹配返回带路径、行号的紧凑片段
DISCOVER

语义搜索

位置和命名未知时,以含义发现候选。

FOCUS

FTS / 混合检索

出现关键词线索后,提高相关性并缩小范围。

VERIFY

ripgrep

用确定的符号、精确文本或正则完成验证。

这一过程不是必须顺序执行的固定流水线;线索充分时可直接跳到精确查找。参见 Qwen 帖子对应的 Zvec 官方文章。

02

系统架构:一个入口,两条检索路径

三层职责

zvec-grep
接口层:CLI / MCP
协调层:Client Router / Local Daemon / Freshness
执行层:Scanner → Extractor → Embedding → Zvec Storage → Search
  • 接口层把人和 Agent 的请求规范化为查询计划。
  • 协调层选择进程模式、维护模型池、监听变更并管理索引新鲜度。
  • 执行层完成文件解析、增量索引、FTS/向量查询与结果融合。
重要区别:Server 与 Direct 最终调用同一个引擎,搜索算法没有两套;区别主要是进程生命周期、并发协调、模型复用和后台刷新。MCP 请求固定经过本地 Server。
03

索引构建:文件如何变成可检索证据

STEP 1
扫描与过滤应用 ignore、glob、类型、大小与二进制规则
STEP 2
识别内容类型代码、Markdown、文本、结构化文本或图像
STEP 3
结构化切分按符号、标题或滑动窗口生成 fragment
STEP 4
补充元数据注入符号、作用域、签名、标题层级
STEP 5
生成向量按模型批量嵌入,区分 query/document purpose
STEP 6
双索引写入同一 fragment 同时写入 FTS 字段与 HNSW 向量
STEP 7
记录文件状态保存哈希、时间、实体数量与失败信息
STEP 8
Optimize提交批量写入并优化本地 collection

3.1 扫描阶段不是“把整个仓库塞进向量库”

Scanner 会始终排除 .git 与 .zvec-grep,默认跳过依赖、构建、缓存、日志、生成目录与锁文件。它还执行二进制探测、符号链接安全检查、嵌套 Git 仓库判断、文件类型与大小限制。默认上限按内容类型区分:代码 1 MiB、文本/Markdown 256 MiB、结构化数据 16 MiB、图片 10 MiB。

3.2 增量更新采用“便宜判断优先,哈希兜底”

FAST PATH
先比较 size + mtime
两者未变且已有内容哈希时,直接视为 unchanged,避免重复读取与嵌入。
VERIFY
元信息变化后计算 SHA-256
如果大小与内容哈希仍一致,仍判定 unchanged;否则进入 modified。
COMMIT
删除旧 fragment,再批量 upsert 新数据
新增、修改、失败待重试与删除文件分别处理;失败文件会自动再尝试一轮。

3.3 工作区中的三个持久化部分

位置职责关键内容
manifest.json索引契约版本、rootPaths、模型、维度、距离度量、运行时设置
files.zvec/文件元数据 collection路径、大小、mtime、hash、状态、entity IDs
index.zvec/实体与 fragment collection文本字段、向量、范围、结构元数据与过滤字段

实现依据:indexing/index.ts、scanner/index.ts、storage/layout.ts。

04

结构化切分:检索质量的隐性核心

向量模型只能处理给定片段。片段边界如果破坏了函数、标题或上下文,即使模型再强,也会得到模糊的向量。因此 zg 的核心价值不仅是“向量化”,还包括把本地文件变成可独立定位的证据单元。

输入切分策略保留的结构降级路径
C/C++、Go、Java、JS/TS、Python、RustTree-sitter AST + 语言 adaptersymbol、scope、signature、doc、breadcrumb、行号无 grammar/adapter 时按纯文本切分
Vue / Svelte提取 script block 后按 JS/TS 解析脚本中的结构信息未识别结构时回退文本
Markdown按 ATX/Setext 标题构造 sectionheading、level、breadcrumb,避开 fenced code 中的伪标题无标题时回退文本
普通文本与数据按行聚合的滑动窗口原始行号与字符偏移超长单行单独切片

Major fragment + 子 fragment

代码函数或 Markdown 章节超过模型预算时,zg 不只把它切成若干孤立小块。它先建立一个 major fragment(结构提纲),再把多个内容窗口挂到同一个 group。查询阶段,命中任意子 fragment 都回收到对应的大实体,避免重复结果,也保留“这是同一个函数/章节”的语义。

为什么向量输入里还要加入元数据

vectorContentForFragment() 会把结构元数据放到内容前,例如:

symbol: method hydratePreferences
scope: ThemeService
signature: hydratePreferences(store: Store): Promise<void>
doc: Restore persisted theme settings during startup.

// 原始代码片段……

元数据预算最多占片段字符预算的 25%。这使“类名、方法名、作用域、标题路径”等结构信号进入向量空间,同时防止元数据挤占主体内容。

默认切分参数:代码、Markdown 与普通文本均以约 3,600 字符为窗口、540 字符重叠;实际窗口还会根据所选模型的输入预算与元数据长度收缩。字符数只是预切分近似,最终仍由模型 tokenizer 检查,超限会截断并计入 truncated_fragments。
05

查询阶段:FTS、向量召回与 RRF 如何协作

QUERY
解析查询计划query / queries / fts / vector 形成一个或多个 route
FILTER
缩小文件集合路径、glob、类型、symbolType、修改时间
RECALL
多路召回FTS 查询 text 字段,Vector 查询 HNSW
FUSE
分组、去重、融合按 entity group 聚合并用 RRF 统一排序

5.1 底层索引

  • FTS 字段:Zvec 的全文索引,tokenizer 为 jieba,并应用 lowercase 过滤;面向“词面相关但需要排名”的检索。
  • 向量字段:VECTOR_FP32 + HNSW;当前模型目录统一使用 cosine similarity。
  • 属性过滤:file_id、group、symbol_name、symbol_type 使用可过滤索引,先收窄候选空间。

5.2 自适应召回不是固定 top-k

源码初始每条 route 召回 200 条。如果去重后的候选数不足 max(limit × 5, 50),且当前查询仍“打满”深度,就按 2 倍扩大到 400、800、1600,最多 2000。这是在过滤、group 合并后仍保证候选丰富度的保护机制。

5.3 为什么使用 RRF,而不是直接相加分数

BM25/FTS 分数与余弦相似度不在同一量纲,直接相加需要标定权重。RRF 只使用每条召回路线中的名次:

score(d) = Σ 1 / (60 + rankroute(d))
某个实体在多个 route 中排名越靠前,累积分数越高;源码常量 RRF_K = 60。

假设 A 在 FTS 排第 1、向量排第 10,B 只在向量排第 2:

A = 1/(60+1) + 1/(60+10) ≈ 0.03068
B = 1/(60+2)                 ≈ 0.01613

结论:跨路线稳定出现的 A 会优先于只在单一路线靠前的 B。
这里没有模型重排器:当前版本的“rerank”核心是基于 route 名次的 RRF、group 去重和上下文优先级处理,不是把候选交给 Qwen Reranker 或 Cross-Encoder 再打分。项目路线图把 model reranking 标为后续能力。

实现依据:pipeline/search/index.ts 与 storage/zvec.ts。

06

Qwen 在 zg 中的实际角色

先纠正一个容易产生的误解:Qwen Developers 的帖子是 zg 的发布与原理解读;zg 并不依赖 Qwen 大语言模型完成搜索,也不会默认把仓库上传给 Qwen。
模型运行位置输入上限 / 维度适用场景
local/potion-code-16m-v2本地,默认1,024 tokens / 256D快速建立代码仓库索引;静态 Model2Vec,无需 GPU
local/qwen3-embedding-0.6b本地 GGUF Q8_08,192 tokens / 1,024D更高容量的多语言检索
qwen/qwen3.7-text-embedding远程 API128,000 tokens / 1,024D超长文本片段与托管推理
qwen/qwen3-vl-embedding远程 API32,000 tokens / 2,560D文本 + 显式纳入索引的图片

Qwen3.7 Text Embedding 调用链

1
选择模型并授权凭据只解决“能否访问 API”,授权解决“能否发送工作区内容”
2
批量提交文本最多 20 条/批,请求 OpenAI-compatible embeddings endpoint
3
返回 1024D FP32文档 fragment 和查询分别生成向量
4
余弦近邻召回向量候选再与 FTS 候选通过 RRF 融合

源码向服务提交的核心字段是 model、input、dimensions: 1024 与 encoding_format: float,并用 Bearer API key 鉴权。Qwen3.7 只负责把文本映射为向量;“何时检索、走哪些 route、如何融合、读哪些源文件、最终如何回答”仍由 zg 与上层 Agent 协作完成。

隐私模型:Credential ≠ Authorization

  • 设置 API key 只是配置供应商访问凭据,并不等于允许发送代码。
  • --allow-remote 仅授权当前 CLI 命令。
  • zg auth grant --capability embedding --scope workspace 才建立可复用的工作区授权,可随时 revoke。
  • MCP 工具审批只允许 Agent 调本地服务,也不等于允许远程 Embedding。

模型参数来自 models/catalog.ts;远程请求实现见 models/backends/qwen.ts。

07

Agent 与 MCP:如何把“会搜索”变成系统能力

zg install不只添加 MCP server 配置,还会向支持的 Agent 写入检索路由指导。真正影响 Agent 效率的并非多一个工具,而是让它在恰当的时机选择恰当的路线。

Agent 当前掌握的信息推荐动作理由
已有准确函数名、配置键、原句或正则Native grep / rg低开销、可穷尽,不需要语义推断
只知道行为、意图、原因或关系zvec_grep_search用向量与 FTS 发现命名不同的实现
有一些符号锚点,但答案跨多个文件先 hybrid,再 rg 精确验证先扩大语义召回,再验证关键证据
问题与本地工作区无关外部资料工具避免把工作区搜索误用于开放世界事实

默认 MCP 面刻意保持很小

默认 agent toolset 只暴露 zvec_grep_search;索引创建、删除、状态与 managed rg 仅在 full toolset 中出现。这个设计减少 Agent 的工具选择负担,也避免它擅自创建或删除持久化索引。

新鲜度与后台更新

WATCH
文件系统事件进入 ChangeSet
默认 750 ms 防抖、最长 5 秒强制 flush,将高频事件合并。
INCREMENTAL
只更新受影响的路径
查询可选择先返回当前索引,并在后台排队更新。
RECONCILE
每小时执行完整一致性探测
修复 watcher 漏事件、系统休眠恢复或 watcher 错误导致的漂移。
执行模式适合场景默认刷新行为
auto绝大多数终端使用有 ready Server 就复用,否则当前进程 Direct
serverAgent、重复查询、多请求共享模型background:先返回现有结果并安排更新
direct一次性任务、CI、前台调试off:不自动后台刷新

MCP 默认为 http://127.0.0.1:7999/mcp,仅监听 loopback;可额外配置 Bearer token。实现与规则见 mcp/tools.ts 与 daemon/watch-manager.ts。

08

性能数据怎么看:优化的是任务总成本

官方强调的指标不是“单次向量查询快了多少毫秒”,而是 Agent 完成整个问题所消耗的搜索轮数、工具调用、输入 Token 与墙钟时间。它对应的优化链条是:

结构化切分 + 多路召回
→ 更早发现高相关证据
→ 减少广泛 grep 与整文件读取
→ 更少工具调用、更少上下文、更短任务时间

BrowseComp-Plus 当前公开报告

−37.56%
平均输入 Token:1,675,379 → 1,046,115
−43.52%
平均工具调用:25.42 → 14.36
−38.58%
Agent 时间:259.39s → 159.31s

准确率从 98.67% 提升到 99.00%。报告覆盖 100 个 case、每个条件 3 次试验,共 300 组配对结果;使用 Codex gpt-5.6-sol 与 qwen/qwen3.7-text-embedding。

必须同时读的实验成本

  • 索引准备成本被单独计算,不包含在 Agent 执行时间中。
  • 该 100,195 文件语料的索引构建耗时 9,721 秒,索引大小约 7.90 GB。
  • 索引可跨任务复用,增量更新只重算变更内容,因此是否划算取决于复用次数。
  • 处理条件额外获得“预建索引 + MCP 工具 + 使用指导”;提升反映的是整套检索系统,而非单独证明某个向量模型。

发布文章还报告:SWE-QA-Bench 20 个问题中,工具调用减少超过一半、输入 Token 接近减半,Judge 分数提高 1.50;这些数字属于项目方控制条件下的结果,真实仓库仍应使用自己的代表性问题做 A/B 测试。

数据来源:BrowseComp-Plus paired report 与 benchmark protocol。

09

能力边界、风险与适用判断

当前边界实际影响应对方式
项目仍处于 0.2.x / work in progressCLI、配置和索引兼容性可能变化生产接入固定版本,升级前重跑验证集
没有模型 reranker、图检索与自动 query rewriting复杂关系仍依赖 Agent 规划与 RRF混合 route + 精确锚点,必要时二次读取验证
PDF、Office、压缩包等二进制格式会跳过知识库覆盖可能出现盲区先转换为 Markdown/文本;不要误以为已被索引
图片默认排除即便使用 VL Embedding,也不会自动索引全部图片显式 include,并确认模型支持 image input
切换模型或远程 endpoint 必须 rebuild不同向量空间不可混用,重建有成本先用代表性 query 小规模比较,再确定模型
远程 Embedding 会发送 fragment 与查询可能涉及内部代码与文档默认本地;远程前完成数据分类与显式授权
需要 Node.js ≥ 22旧环境不能直接安装升级 runtime 或放入隔离的工具环境

什么时候值得使用

  • 大型仓库、跨文件调用链、数据流、生命周期、设计理由等“位置未知”的问题。
  • 同一工作区会被反复查询,索引成本能够摊薄。
  • Agent 经常用多轮 grep 猜词、反复打开文件,Token 浪费明显。
  • 代码与 Markdown 文档混合,需要同一检索入口。

什么时候 rg 已经足够

  • 寻找唯一的函数名、错误信息、配置键或正则匹配。
  • 一次性的小仓库任务,没有复用索引的机会。
  • 必须穷尽全部出现位置,而不是获取排名靠前的样本。
10

动手学习路径:用同一问题比较四种路线

实验 1:本地默认模型建立基线

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

cd /absolute/path/to/your-repository
zg index --embedding local/potion-code-16m-v2
zg status

实验 2:对同一个问题分别检索

# 语义:只描述意图
zg query --vector "where user credentials are validated" --human --limit 5

# 词面:已经知道一个符号
zg query --fts "AuthService" --human --limit 5

# 混合:意图 + 精确锚点
zg query --hybrid "authentication flow" \
  --fts "ForbiddenError" --fuse --human --limit 10

# 穷尽验证:不需要索引
zg query --rg -n -F "AuthService" -g "*.ts" src

实验 3:接入 Agent

zg install --target codex --yes
zg server status --check-ready

# 新开 Agent 会话后,用行为描述提问:
# “查清登录凭据从 HTTP 请求进入后,经过哪些组件完成验证,并引用源码位置。”

实验 4:只有明确需要时才比较 Qwen3.7

zg config provider set qwen --api-key "$DASHSCOPE_API_KEY"

# 单次授权,且必须重建,因为向量空间改变
zg index --rebuild \
  --embedding qwen/qwen3.7-text-embedding \
  --allow-remote

建议记录的对比指标

维度记录方式
Top-5 证据命中率需要的文件/函数是否出现在前 5 条
首个有效证据时间从提问到第一次找到可引用源码
Agent 工具调用数搜索、读取与命令总次数
输入 Token比较 native tools 与 zg 条件的总输入
索引成本首次构建时间、磁盘大小、增量更新时间
隐私与运维数据是否出机、模型下载、服务与权限复杂度
实践判断:先用本地默认模型验证“结构化索引 + 混合检索”是否已经解决主要问题,再决定是否为更长输入、多语言或更高召回质量付出远程 API、重建索引和数据授权成本。
11

资料与源码索引

← 返回内容索引