zvec-grep:从关键词搜索到 Agent 本地检索层
围绕 zvec-grep 0.2.1 的源码、项目文档和 Qwen Developers 发布内容,建立一套从文件进入索引,到 Agent 获得紧凑证据的完整心智模型。
它解决什么问题
ripgrep 的强项是“已经知道要找什么”:函数名、错误字符串、配置键、文件路径或正则表达式。Agent 常遇到的却是“知道意图,不知道代码中的叫法”。例如,用户询问“启动时如何恢复主题”,代码可能使用 hydratePreferences,词面几乎不重合。
| 问题类型 | 普通 rg 的表现 | zg 的处理 |
|---|---|---|
| 已知符号或原句 | 快、精确、可穷尽 | 直接保留 rg 路径,不必建索引 |
| 只知道自然语言意图 | 需要反复猜关键词 | 向量召回语义相近片段 |
| 跨文件关系与设计原因 | 匹配结果没有相关性排序 | FTS + 向量多路召回,统一排序 |
| Agent 上下文预算有限 | 容易读取整文件或大量匹配 | 返回带路径、行号的紧凑片段 |
语义搜索
位置和命名未知时,以含义发现候选。
FTS / 混合检索
出现关键词线索后,提高相关性并缩小范围。
ripgrep
用确定的符号、精确文本或正则完成验证。
这一过程不是必须顺序执行的固定流水线;线索充分时可直接跳到精确查找。参见 Qwen 帖子对应的 Zvec 官方文章。
系统架构:一个入口,两条检索路径
MCP:本地 Server
三层职责
- 接口层把人和 Agent 的请求规范化为查询计划。
- 协调层选择进程模式、维护模型池、监听变更并管理索引新鲜度。
- 执行层完成文件解析、增量索引、FTS/向量查询与结果融合。
索引构建:文件如何变成可检索证据
3.1 扫描阶段不是“把整个仓库塞进向量库”
Scanner 会始终排除 .git 与 .zvec-grep,默认跳过依赖、构建、缓存、日志、生成目录与锁文件。它还执行二进制探测、符号链接安全检查、嵌套 Git 仓库判断、文件类型与大小限制。默认上限按内容类型区分:代码 1 MiB、文本/Markdown 256 MiB、结构化数据 16 MiB、图片 10 MiB。
3.2 增量更新采用“便宜判断优先,哈希兜底”
3.3 工作区中的三个持久化部分
| 位置 | 职责 | 关键内容 |
|---|---|---|
manifest.json | 索引契约 | 版本、rootPaths、模型、维度、距离度量、运行时设置 |
files.zvec/ | 文件元数据 collection | 路径、大小、mtime、hash、状态、entity IDs |
index.zvec/ | 实体与 fragment collection | 文本字段、向量、范围、结构元数据与过滤字段 |
结构化切分:检索质量的隐性核心
向量模型只能处理给定片段。片段边界如果破坏了函数、标题或上下文,即使模型再强,也会得到模糊的向量。因此 zg 的核心价值不仅是“向量化”,还包括把本地文件变成可独立定位的证据单元。
| 输入 | 切分策略 | 保留的结构 | 降级路径 |
|---|---|---|---|
| C/C++、Go、Java、JS/TS、Python、Rust | Tree-sitter AST + 语言 adapter | symbol、scope、signature、doc、breadcrumb、行号 | 无 grammar/adapter 时按纯文本切分 |
| Vue / Svelte | 提取 script block 后按 JS/TS 解析 | 脚本中的结构信息 | 未识别结构时回退文本 |
| Markdown | 按 ATX/Setext 标题构造 section | heading、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%。这使“类名、方法名、作用域、标题路径”等结构信号进入向量空间,同时防止元数据挤占主体内容。
truncated_fragments。查询阶段:FTS、向量召回与 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 只使用每条召回路线中的名次:
假设 A 在 FTS 排第 1、向量排第 10,B 只在向量排第 2:
A = 1/(60+1) + 1/(60+10) ≈ 0.03068
B = 1/(60+2) ≈ 0.01613
结论:跨路线稳定出现的 A 会优先于只在单一路线靠前的 B。
Qwen 在 zg 中的实际角色
| 模型 | 运行位置 | 输入上限 / 维度 | 适用场景 |
|---|---|---|---|
local/potion-code-16m-v2 | 本地,默认 | 1,024 tokens / 256D | 快速建立代码仓库索引;静态 Model2Vec,无需 GPU |
local/qwen3-embedding-0.6b | 本地 GGUF Q8_0 | 8,192 tokens / 1,024D | 更高容量的多语言检索 |
qwen/qwen3.7-text-embedding | 远程 API | 128,000 tokens / 1,024D | 超长文本片段与托管推理 |
qwen/qwen3-vl-embedding | 远程 API | 32,000 tokens / 2,560D | 文本 + 显式纳入索引的图片 |
Qwen3.7 Text Embedding 调用链
源码向服务提交的核心字段是 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。
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 的工具选择负担,也避免它擅自创建或删除持久化索引。
新鲜度与后台更新
| 执行模式 | 适合场景 | 默认刷新行为 |
|---|---|---|
auto | 绝大多数终端使用 | 有 ready Server 就复用,否则当前进程 Direct |
server | Agent、重复查询、多请求共享模型 | background:先返回现有结果并安排更新 |
direct | 一次性任务、CI、前台调试 | off:不自动后台刷新 |
MCP 默认为 http://127.0.0.1:7999/mcp,仅监听 loopback;可额外配置 Bearer token。实现与规则见 mcp/tools.ts 与 daemon/watch-manager.ts。
性能数据怎么看:优化的是任务总成本
官方强调的指标不是“单次向量查询快了多少毫秒”,而是 Agent 完成整个问题所消耗的搜索轮数、工具调用、输入 Token 与墙钟时间。它对应的优化链条是:
BrowseComp-Plus 当前公开报告
准确率从 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 测试。
能力边界、风险与适用判断
| 当前边界 | 实际影响 | 应对方式 |
|---|---|---|
| 项目仍处于 0.2.x / work in progress | CLI、配置和索引兼容性可能变化 | 生产接入固定版本,升级前重跑验证集 |
| 没有模型 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 已经足够
- 寻找唯一的函数名、错误信息、配置键或正则匹配。
- 一次性的小仓库任务,没有复用索引的机会。
- 必须穷尽全部出现位置,而不是获取排名靠前的样本。
动手学习路径:用同一问题比较四种路线
实验 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 条件的总输入 |
| 索引成本 | 首次构建时间、磁盘大小、增量更新时间 |
| 隐私与运维 | 数据是否出机、模型下载、服务与权限复杂度 |