We rewrote zg in Rust, cutting indexing time by 65% and peak memory by 53%
zg (zvec-grep) combines vector-based semantic search, BM25 keyword search, and ripgrep-style exact matching in a local command-line tool. It also exposes these capabilities over MCP for coding agents such as Claude Code, Codex, and Cursor. In version 0.3.0, we replaced the original Node.js implementation entirely with Rust. In our benchmarks, this reduced the installed size by 79.5%, CLI startup time by 82.5%, indexing time by 64.5%, and peak memory during indexing by 53.2%.
Repository: zvec-ai/zvec-grep
---
What is zg?
zg is an open-source local search tool from Alibaba, built for both developers and AI agents. It provides a single interface for semantic search, BM25 keyword search, and ripgrep-style exact matching.
!zg demo: set up an agent integration, index a workspace, and let the agent search it with zg
Many Rust developers use rg every day. If you know the function name, configuration key, or error message you are looking for, it is fast, precise, and useful when you need to find every occurrence.
Increasingly, though, search requests come from AI agents and start with a natural-language question: “How is the theme preference restored at startup?” or “Where are user permissions checked?” The implementation might be named hydratePreferences, or the documentation might discuss “account authorization and approval,” without sharing any words with the query. Text matching alone can miss the relevant material, or require such a broad search that it produces a large set of unranked matching lines. The agent then has to keep trying different keywords, searching again, and reading files. That adds tool calls, latency, and token usage.
zg is designed to help with this. It keeps the exact, fast, exhaustive matching that makes rg useful, and adds semantic discovery, relevance ranking, and structured context:
- Several search modes, one interface. Vector search, BM25, hybrid search using reciprocal rank fusion (RRF), and rg-style exact/regex matching. You can start with semantic search, narrow the scope with keywords, and use a regex to check specific matches.
- Structure-aware parsing. zg uses tree-sitter to parse languages including C/C++, Go, Java, JavaScript/TypeScript, Python, and Rust, splitting content into chunks based on symbols, signatures, and hierarchy. Markdown is split by headings and sections. Text data such as JSON, TOML, YAML, and CSV can also be searched.
- Compact results with source locations. By default, results are ranked and include short previews, file paths, and line numbers. The agent can read the full content when needed, keeping unrelated material out of its context.
- Local by default. File scanning, embedding generation, indexing, and search all run locally by default. Several local embedding models are supported out of the box. Remote embeddings require explicit user authorization.
- One index for the CLI and agents. zg uses the Zvec embedded vector database for both vector and BM25 indexes, so there is no separate database service to deploy. The CLI and MCP interface share the same index, with incremental updates and automatic refresh.
We also ran paired A/B evaluations on SWE-QA-Bench, which covers questions about code repositories, and BrowseComp-Plus, which covers deep-research questions. Each pair used the same agent, model, and prompt; the only difference was whether zg was available:
- SWE-QA-Bench: tool calls fell by more than half, input token usage fell by nearly half, and the evaluation score increased by 1.50 points.
- BrowseComp-Plus: accuracy increased from 98.67% to 99.00%, while input token usage fell by 37.56%, tool calls by 43.52%, and agent execution time by 38.58%.
---
From Node.js to Rust
Why we rewrote it
The earlier releases of zg (0.2.x) were written in TypeScript and ran on Node.js. For a CLI search tool that is invoked frequently, that implementation had a few recurring costs:
- Each standalone CLI invocation had to start the Node.js runtime and load JavaScript modules. Those costs added up when an agent made repeated calls.
- Parts of the indexing pipeline converted between regular JavaScript arrays and typed arrays, and passed data between worker threads. This introduced extra copies and temporary allocations.
- The installation included the JavaScript dependency tree and native addons, which made it relatively large.
Large workspaces and agent workflows are sensitive to indexing time, query latency, and resource usage, so we rewrote the core execution pipeline in Rust.
Benchmark results
The benchmarks ran on an Apple M3 Pro with 18 GB of RAM, macOS 27.2, and Node.js v24.16.0. For indexing, we used four public BEIR datasets—SciFact, NFCorpus, ArguAna, and FiQA—containing 75,128 documents in total. Both versions used the same local embedding model, ran inference on the CPU, and had embedding concurrency set to 4.
| Metric | Node.js 0.2.2 | Rust 0.3.0 | Reduction |
| --- | --- | --- | --- |
| Installed size, including dependencies | 167.5 MiB | 34.3 MiB | 79.5% |
| CLI startup time | 153.1 ms | 26.8 ms | 82.5% |
| Total indexing time | 219.13 s | 77.84 s | 64.5% |
| Average peak memory during indexing (RSS) | 4,210.26 MiB | 1,968.61 MiB | 53.2% |
| Average search latency over MCP | 20.93 ms | 14.88 ms | 28.9% |
In practical terms, building the initial indexes for all four datasets went from about 3 minutes 39 seconds to about 1 minute 18 seconds. Average peak RSS during indexing dropped from about 4.11 GiB to 1.92 GiB, with lower peak memory on every dataset. That leaves more memory available when an editor, an agent, and other development tools are running on the same machine.
Where the gains came from
- Native startup. The compiled executable runs directly, without initializing Node.js or loading JavaScript modules. CLI cold-start time dropped from 153 ms to 27 ms.
- A more direct computation path. Text encoding and vector operations run in native code with explicit numeric types and contiguous arrays, making the computation loops easier for the compiler to optimize.
- Fewer intermediate conversions. Results stay in native numeric arrays instead of moving back and forth between regular JavaScript arrays and typed arrays. This reduces copying and temporary allocations.
- Shared resources and centralized scheduling. On supported local inference paths, threads share loaded tokenizers and model data, avoiding repeated initialization. A native thread pool handles task scheduling.
---
Getting started
1. Install
zg is currently distributed through npm, with other installation methods planned. The installed executable is native and does not require Node.js at runtime. macOS, Linux, and Windows are supported.
npm install -g @zvec/zvec-grep --allow-scripts=@zvec/zvec-grep
zg --version
> Installation uses an npm postinstall script to put the native executable in place and download CUDA components when needed. The --allow-scripts option allows this script to run.
2. Set up an agent integration (optional)
# Detect installed agents and configure MCP automatically
zg --install
# Or select a specific integration
zg --install --target claude --yes
zg --install --target codex --yes
Supported targets currently include claude, codex, opencode, cursor, qwen, qoder, copilot, vscode, grok, and pi. Reconnect your agent after setup; it can then decide when to use zg based on the question.
3. Build an index
cd your-repository
# Use the default lightweight local model for code, local/potion-code-16m-v2
zg --index
# Check index coverage, the embedding model, and storage status
zg --status
The index is stored in .zvec-grep/ at the workspace root. Subsequent changes are processed incrementally; files whose contents have not changed do not need new embeddings. To switch models, rebuild with zg --index --rebuild --embedding <model>.
> In an interactive terminal, you can skip this step and start with a search: zg will guide you through selecting a model and building an index first. In non-interactive environments such as scripts or CI, indexing must be started explicitly with zg --index.
4. Search
# Natural-language search: hybrid vector + BM25 search with RRF by default
zg "where is the connection pool timeout configured"
zg "where are user permissions checked"
# BM25 keyword search only, or vector search only
zg --fts "AuthService"
zg --vector "retry with exponential backoff"
# Run multiple queries; --fuse merges and ranks their results together
zg --hybrid "connection pool" --hybrid "socket timeout" --fuse
# rg-style exact/regex matching, with no index required
zg --rg -n "AuthService" src
zg --rg -F "fn main(" crates
# Filter by file type and glob, and limit the number of results
zg "error handling for invalid config" -t rust -g 'crates/**' --limit 5
# Choose a preview level: none / short (default) / full / detail
zg --preview detail "how indexing jobs are scheduled" --limit 3
5. Try it on some fiction
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
# Use a local text-retrieval model for this collection of English fiction
zg --index --embedding local/potion-retrieval-32m
zg --preview detail "An unseen creature left a few marks. What did the detective infer?" --limit 3
The query does not contain Holmes, weasel, or foot-marks, all of which appear in the source text. Even so, zg ranks the passage in “The Crooked Man,” from The Memoirs of Sherlock Holmes, where Holmes identifies an animal from its footprints, as the top result, with line numbers pointing to the passage.
---
What's next
zg 0.3.0 is the first official release of the Rust implementation. Next, we plan to work on graph-based retrieval and reranking, support for more formats such as PDF and Office documents, more efficient use of context, and further improvements to local model and indexing performance.
We'd welcome feedback from the Rust community, whether through issues, pull requests, or reports of how zg works with your codebases, knowledge bases, and agent workflows.
More details: Rust release blog post.
Mentions
We rewrote zg in Rust, cutting indexing time by 65% and peak memory by 53% zg (zvec-grep) combines vector-based semantic search, BM25 keyword search, and ripgrep-style exact matching in a local command-line tool. It also exposes these capabilities over MCP for coding agents such as Claude Code, Codex, and Cursor. In version 0.3.0, we replaced the original Node.js implementation entirely with Rust. In our benchmarks, this reduced the installed size by 79.5%, CLI startup time by 82.5%, indexing time by 64.5%, and peak memory during indexing by 53.2%. Repository: zvec-ai/zvec-grep --- What is zg? zg is an open-source local search tool from Alibaba, built for both developers and AI agents. It provides a single interface for semantic search, BM25 keyword search, and ripgrep-style exact matching. !zg demo: set up an agent integration, index a workspace, and let the agent search it with zg Many Rust developers use rg every day. If you know the function name, configuration key, or error message you are looking for, it is fast, precise, and useful when you need to find every occurrence. Increasingly, though, search requests come from AI agents and start with a natural-language question: “How is the theme preference restored at startup?” or “Where are user permissions checked?” The implementation might be named hydratePreferences, or the documentation might discuss “account authorization and approval,” without sharing any words with the query. Text matching alone can miss the relevant material, or require such a broad search that it produces a large set of unranked matching lines. The agent then has to keep trying different keywords, searching again, and reading files. That adds tool calls, latency, and token usage. zg is designed to help with this. It keeps the exact, fast, exhaustive matching that makes rg useful, and adds semantic discovery, relevance ranking, and structured context: - Several search modes, one interface. Vector search, BM25, hybrid search using reciprocal rank fusion (RRF), and rg-style exact/regex matching. You can start with semantic search, narrow the scope with keywords, and use a regex to check specific matches. - Structure-aware parsing. zg uses tree-sitter to parse languages including C/C++, Go, Java, JavaScript/TypeScript, Python, and Rust, splitting content into chunks based on symbols, signatures, and hierarchy. Markdown is split by headings and sections. Text data such as JSON, TOML, YAML, and CSV can also be searched. - Compact results with source locations. By default, results are ranked and include short previews, file paths, and line numbers. The agent can read the full content when needed, keeping unrelated material out of its context. - Local by default. File scanning, embedding generation, indexing, and search all run locally by default. Several local embedding models are supported out of the box. Remote embeddings require explicit user authorization. - One index for the CLI and agents. zg uses the Zvec embedded vector database for both vector and BM25 indexes, so there is no separate database service to deploy. The CLI and MCP interface share the same index, with incremental updates and automatic refresh. We also ran paired A/B evaluations on SWE-QA-Bench, which covers questions about code repositories, and BrowseComp-Plus, which covers deep-research questions. Each pair used the same agent, model, and prompt; the only difference was whether zg was available: - SWE-QA-Bench: tool calls fell by more than half, input token usage fell by nearly half, and the evaluation score increased by 1.50 points. - BrowseComp-Plus: accuracy increased from 98.67% to 99.00%, while input token usage fell by 37.56%, tool calls by 43.52%, and agent execution time by 38.58%. --- From Node.js to Rust Why we rewrote it The earlier releases of zg (0.2.x) were written in TypeScript and ran on Node.js. For a CLI search tool that is invoked frequently, that implementation had a few recurring costs: - Each standalone CLI invocation had to start the Node.js runtime and load JavaScript modules. Those costs added up when an agent made repeated calls. - Parts of the indexing pipeline converted between regular JavaScript arrays and typed arrays, and passed data between worker threads. This introduced extra copies and temporary allocations. - The installation included the JavaScript dependency tree and native addons, which made it relatively large. Large workspaces and agent workflows are sensitive to indexing time, query latency, and resource usage, so we rewrote the core execution pipeline in Rust. Benchmark results The benchmarks ran on an Apple M3 Pro with 18 GB of RAM, macOS 27.2, and Node.js v24.16.0. For indexing, we used four public BEIR datasets—SciFact, NFCorpus, ArguAna, and FiQA—containing 75,128 documents in total. Both versions used the same local embedding model, ran inference on the CPU, and had embedding concurrency set to 4. | Metric | Node.js 0.2.2 | Rust 0.3.0 | Reduction | | --- | --- | --- | --- | | Installed size, including dependencies | 167.5 MiB | 34.3 MiB | 79.5% | | CLI startup time | 153.1 ms | 26.8 ms | 82.5% | | Total indexing time | 219.13 s | 77.84 s | 64.5% | | Average peak memory during indexing (RSS) | 4,210.26 MiB | 1,968.61 MiB | 53.2% | | Average search latency over MCP | 20.93 ms | 14.88 ms | 28.9% | In practical terms, building the initial indexes for all four datasets went from about 3 minutes 39 seconds to about 1 minute 18 seconds. Average peak RSS during indexing dropped from about 4.11 GiB to 1.92 GiB, with lower peak memory on every dataset. That leaves more memory available when an editor, an agent, and other development tools are running on the same machine. Where the gains came from - Native startup. The compiled executable runs directly, without initializing Node.js or loading JavaScript modules. CLI cold-start time dropped from 153 ms to 27 ms. - A more direct computation path. Text encoding and vector operations run in native code with explicit numeric types and contiguous arrays, making the computation loops easier for the compiler to optimize. - Fewer intermediate conversions. Results stay in native numeric arrays instead of moving back and forth between regular JavaScript arrays and typed arrays. This reduces copying and temporary allocations. - Shared resources and centralized scheduling. On supported local inference paths, threads share loaded tokenizers and model data, avoiding repeated initialization. A native thread pool handles task scheduling. --- Getting started 1. Install zg is currently distributed through npm, with other installation methods planned. The installed executable is native and does not require Node.js at runtime. macOS, Linux, and Windows are supported. npm install -g @zvec/zvec-grep --allow-scripts=@zvec/zvec-grep zg --version > Installation uses an npm postinstall script to put the native executable in place and download CUDA components when needed. The --allow-scripts option allows this script to run. 2. Set up an agent integration (optional) # Detect installed agents and configure MCP automatically zg --install # Or select a specific integration zg --install --target claude --yes zg --install --target codex --yes Supported targets currently include claude, codex, opencode, cursor, qwen, qoder, copilot, vscode, grok, and pi. Reconnect your agent after setup; it can then decide when to use zg based on the question. 3. Build an index cd your-repository # Use the default lightweight local model for code, local/potion-code-16m-v2 zg --index # Check index coverage, the embedding model, and storage status zg --status The index is stored in .zvec-grep/ at the workspace root. Subsequent changes are processed incrementally; files whose contents have not changed do not need new embeddings. To switch models, rebuild with zg --index --rebuild --embedding <model>. > In an interactive terminal, you can skip this step and start with a search: zg will guide you through selecting a model and building an index first. In non-interactive environments such as scripts or CI, indexing must be started explicitly with zg --index. 4. Search # Natural-language search: hybrid vector + BM25 search with RRF by default zg "where is the connection pool timeout configured" zg "where are user permissions checked" # BM25 keyword search only, or vector search only zg --fts "AuthService" zg --vector "retry with exponential backoff" # Run multiple queries; --fuse merges and ranks their results together zg --hybrid "connection pool" --hybrid "socket timeout" --fuse # rg-style exact/regex matching, with no index required zg --rg -n "AuthService" src zg --rg -F "fn main(" crates # Filter by file type and glob, and limit the number of results zg "error handling for invalid config" -t rust -g 'crates/**' --limit 5 # Choose a preview level: none / short (default) / full / detail zg --preview detail "how indexing jobs are scheduled" --limit 3 5. Try it on some fiction 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 # Use a local text-retrieval model for this collection of English fiction zg --index --embedding local/potion-retrieval-32m zg --preview detail "An unseen creature left a few marks. What did the detective infer?" --limit 3 The query does not contain Holmes, weasel, or foot-marks, all of which appear in the source text. Even so, zg ranks the passage in “The Crooked Man,” from The Memoirs of Sherlock Holmes, where Holmes identifies an animal from its footprints, as the top result, with line numbers pointing to the passage. --- What's next zg 0.3.0 is the first official release of the Rust implementation. Next, we plan to work on graph-based retrieval and reranking, support for more formats such as PDF and Office documents, more efficient use of context, and further improvements to local model and indexing performance. We'd welcome feedback from the Rust community, whether through issues, pull requests, or reports of how zg works with your codebases, knowledge bases, and agent workflows. More details: Rust release blog post.
zg 使用 Rust 重写,索引构建耗时减少 65%,峰值内存降低 53% zg(zvec-grep)把 向量语义检索、BM25 关键词检索和 ripgrep 精确匹配装进了同一个本地命令行工具,同时通过 MCP 提供给 Claude Code、Codex、Cursor 等 AI Agent 使用。0.3.0 版本用 Rust 完整替换了原来的 Node.js 实现:安装包体积减少 79.5%,CLI 启动耗时减少 82.5%,索引构建耗时减少 64.5%,构建期峰值内存降低 53.2%。 项目地址:[zvec-ai/zvec-grep]( zvec-ai/zvec-grep) --- 一、zg 是什么 zg(zvec-grep)是阿里巴巴开源的本地检索工具,面向人和 AI Agent,把向量语义检索、BM25 关键词检索和 ripgrep 精确匹配整合到同一个入口。  rg 是很多 Rust 开发者每天都在用的工具。函数名、配置项、报错信息这类明确目标,用 rg 查又快又准,而且能搜全。 但现在越来越多的检索请求来自 AI Agent,输入也从明确的符号变成了自然语言:“启动时怎么恢复主题偏好?”“用户权限在哪里校验?”。代码里对应的实现可能叫 hydratePreferences,文档里可能写成“账号授权与审批”,和问题本身没有一个相同的词。这种时候只用文本匹配,要么漏掉,要么把范围放得很大,得到一堆没有排序的匹配行。Agent 只能反复换关键词、搜索、读文件,工具调用次数、耗时和 Token 都会上去。 zg(zvec-grep) 就是为这个问题做的本地检索工具。它保留 rg 精确、快速、可以穷尽匹配的优点,再补上语义发现、相关性排序和上下文组织: - 一个入口,多种检索:向量语义检索、BM25、二者混合检索(RRF 融合排序),以及 rg 风格的精确/正则匹配。可以先按语义找方向,再按关键词收窄,最后用正则精确核对。 - 理解代码结构:借助 tree-sitter 解析 C/C++、Go、Java、JavaScript/TypeScript、Python、Rust 等语言,按符号、签名和层级切分;Markdown 按标题和章节切分;JSON、TOML、YAML、CSV 等文本数据也能参与检索。 - 结果紧凑、来源可查:默认返回排序后的紧凑预览,每条结果带文件路径和行号,需要时再读全文,避免无关内容塞满 Agent 的上下文。 - 默认本地运行:文件扫描、Embedding、索引和检索默认都在本机完成。内置多种端侧模型,远程 Embedding 必须经过用户明确授权才会启用。 - 人和 Agent 共用一份索引:zg 底层用 [Zvec]( alibaba/zvec) 嵌入式向量数据库同时存放向量索引和 BM25 索引,不用另外部署数据库服务。CLI 和 MCP 读写的是同一份索引,并支持增量更新和自动刷新。 在代码仓库问答 SWE-QA-Bench 和深度研究问答 BrowseComp-Plus 的配对 A/B 评测中(同一 Agent、同一模型、同一 Prompt,只比较是否接入 zg): - SWE-QA-Bench:工具调用减少一半以上,输入 Token 减少近一半,评审得分提高 1.50 分; - BrowseComp-Plus:准确率从 98.67% 提高到 99.00%,输入 Token 减少 37.56%,工具调用减少 43.52%,Agent 耗时减少 38.58%。 --- 二、从 Node.js 到 Rust:重写带来了什么 为什么重写 zg 的前几个版本(0.2.x)用 TypeScript/Node.js 实现。对一个高频调用的命令行检索工具来说,这个运行时有几个绕不开的成本: - 每次单独调用 CLI 都要启动 Node.js 运行时并加载 JS 模块,Agent 连续调用时,这部分时间会累积起来; - 部分索引路径要在 JavaScript 普通数组和 TypedArray 之间转换,还要在 worker 线程之间传数据,带来额外的复制和临时内存分配; - 安装包要带上整套 JS 依赖和原生插件,体积很大。 大型工作区和 Agent 工作流对索引耗时、查询延迟和资源占用都很敏感,所以我们用 Rust 重写了整条核心执行链路。 性能对比 测试环境:Apple M3 Pro、18 GB 内存、macOS 27.2,Node.js v24.16.0。索引数据集使用 BEIR 中的 SciFact、NFCorpus、ArguAna 和 FiQA 四个公开语料,共 75,128 篇文档。两个版本使用同一个本地 Embedding 模型,都走 CPU 推理,Embedding 并发设为 4。 | 指标 | Node.js 0.2.2 | Rust 0.3.0 | 改善 | | --- | --- | --- | --- | | 安装包体积(含依赖) | 167.5 MiB | 34.3 MiB | 减少 79.5% | | CLI 启动耗时 | 153.1 ms | 26.8 ms | 减少 82.5% | | 索引构建总耗时 | 219.13 s | 77.84 s | 减少 64.5% | | 平均构建峰值内存(RSS) | 4,210.26 MiB | 1,968.61 MiB | 降低 53.2% | | 平均检索耗时(MCP) | 20.93 ms | 14.88 ms | 减少 28.9% | 换算成实际体验:四个语料的首次建索引时间从约 3 分 39 秒缩短到约 1 分 18 秒;构建期峰值内存从约 4.11 GiB 降到约 1.92 GiB,而且四组测试每一组都下降了。本机同时开着编辑器、Agent 和其他开发工具时,这部分内存节省很明显。 性能从哪里来 - 原生启动:直接运行编译好的原生程序,不再需要初始化 Node.js 运行时和加载 JS 模块。CLI 冷启动从 153 ms 降到 27 ms。 - 计算路径更直接:文本编码和向量计算在原生代码中完成,使用明确的数值类型和连续内存数组,编译器更容易优化计算循环。 - 减少中间转换:计算结果一直以原生数值数组的形式存在,不再在 JS 普通数组和 TypedArray 之间来回转换,数据复制和临时分配都少了。 - 共享资源、统一调度:在支持的本地推理路径上,各计算线程共享已加载的分词器和模型数据,避免重复初始化,任务由原生线程池统一调度。 --- 三、上手:安装与使用 1. 安装 zg 目前通过 npm 分发(后续会支持其他安装方式),运行时直接执行原生程序,不依赖 Node.js。支持 macOS、Linux 和 Windows。 npm install -g @zvec/zvec-grep --allow-scripts=@zvec/zvec-grep zg --version 注:安装依赖 npm 的 postinstall 脚本把原生程序放入合适位置和按需下载 CUDA,所以要加上 --allow-scripts 允许它运行。 2. 接入 AI Agent(可选) # 自动发现本机已安装的 Agent 并配置 MCP zg --install # 也可以指定目标,例如 Claude Code / Codex / OpenCode zg --install --target claude --yes zg --install --target codex --yes 目前支持的目标包括 claude、codex、opencode、cursor、qwen、qoder、copilot、vscode、grok、pi。配置完成后重新连接 Agent,Agent 会根据问题自己判断什么时候调用 zg。 3. 建立索引 cd your-repository # 默认使用面向代码的轻量本地模型 local/potion-code-16m-v2 zg --index # 查看索引覆盖范围、Embedding 模型和存储状态 zg --status 索引保存在工作区根目录下的 .zvec-grep/。之后文件有变化时只做增量处理,内容没变的文件不会重新计算 Embedding。如果要换模型,用 zg --index --rebuild --embedding <model> 重建索引。 注:在交互式终端里,即使跳过这一步直接搜索,zg 也会先引导你选模型并建好索引。脚本或 CI 这类非交互环境不会自动建索引,需要先显式执行 zg --index。 4. 检索 # 自然语言 / 语义检索(默认混合检索:向量 + BM25,RRF 融合排序) zg "where is the connection pool timeout configured" zg "用户权限在哪里校验" # 只走 BM25 关键词检索,或只走向量检索 zg --fts "AuthService" zg --vector "retry with exponential backoff" # 同时执行多个查询,--fuse 把多组结果合并后统一排序 zg --hybrid "connection pool" --hybrid "socket timeout" --fuse # 用 rg 风格做精确/正则匹配(不需要索引) zg --rg -n "AuthService" src zg --rg -F "fn main(" crates # 限定范围:文件类型、glob、条数 zg "error handling for invalid config" -t rust -g 'crates/**' --limit 5 # 调整预览详细程度:none / short(默认)/ full / detail zg --preview detail "how indexing jobs are scheduled" --limit 3 5. 示例:用小说测试一下 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 --preview detail "An unseen creature left a few marks. What did the detective infer?" --limit 3 查询里没有出现 Holmes、weasel、foot-marks 这些原文用词,zg 依然会把《福尔摩斯回忆录》中《驼背人》里根据脚印推断动物的段落排在最前面,并给出具体行号。 --- 四、写在最后 zg 0.3.0 是 Rust 版的第一个正式版本。接下来我们会继续推进图检索与重排、PDF/Office 等更多内容格式、更高的上下文效率,以及本地模型和索引的性能优化。 欢迎 Rust 社区的朋友试用,也欢迎提 Issue、PR,或者分享你在大型代码库、知识库和 Agent 工作流里的使用情况: - GitHub:<zvec-ai/zvec-grep> - 文档:<zvec.org> - Rust 版发布博客:<zvec.org> - 开源发布博客:<zvec.org> 如果觉得有用,欢迎给项目点个 Star。
zvecとzvec-grepの概要:Docker Composeでローカル検索を試す 文書を意味で探したいとき、ベクトルDBに加えて、文書の分割や埋め込みモデルの呼び出しも必要になります。まず検索を試すなら、その処理をまとめたCLIから始めると全体像をつかみやすくなります。 この記事では、検索の保存・実行基盤である zvec と、それを使う検索ツール zvec-grep(コマンド名は zg) の役割を整理します。その後、英語のMarkdown文書3件をDocker Compose経由でインデックス化し、文字列検索・キーワード検索・ベクトル検索・ハイブリッド検索を試す構成を紹介します。LLMによる回答生成までは行いません。 ベクトル検索・RAG・zvecの技術的な関係 ベクトル検索は「情報を探す方法」、RAGは「探した情報を使って回答を生成する構成」、zvecは「検索を実装するためのDB」です。同じ階層の技術ではなく、RAGの検索部分でベクトル検索を採用し、その保存・検索基盤にzvecを使う、という関係になります。 要素 担当すること この構成での位置 埋め込みモデル 文書や質問を数値の並び(ベクトル)に変換する 検索前の変換処理 ベクトル検索 質問のベクトルに近い文書ベクトルを探す 関連する文書を選ぶ方法 zvec ベクトルや関連データを保存し、類似度に基づいて検索する 検索を支えるDB RAG 外部の情報を検索し、その内容を生成モデルに渡して回答を作る 検索と生成を組み合わせる全体構成 回答生成用LLM 質問と検索した文書を入力として回答を生成する 検索後の生成処理 ベクトル検索は、意味を数値で比較して文書を探す 文章を埋め込みモデルへ入力すると、内容を表すベクトルが得られます。文書と質問を対応する同じベクトル空間へ変換し、cosine類似度などで近いものを探すことで、表現が異なっても関連する文書を候補にできます。意味の近さをどれだけ捉えられるかは、埋め込みモデルや文書の分割方法にも依存します。 例えば「アカウントに入れなくなった」という質問から、「パスワード再設定」の文書を探す場面です。zvecは登録されたベクトルを保存・検索します。文書をベクトルへ変換する埋め込み処理と、検索結果から文章を生成する処理は、それぞれ別の役割です。 RAGは、検索した文書を回答生成につなぐ RAGはRetrieval-Augmented Generation(検索拡張生成)の略です。RAGの原論文では、文書の検索と生成モデルを組み合わせる手法が示されています。ここでは、その考え方を使った典型的な文書検索・回答生成の流れを示します。 文書側の分割・埋め込み・保存は、事前準備として行います。質問を受け取った後は、質問を埋め込み、関連文書を検索し、その文書と質問を回答生成用LLMへ渡します。埋め込みモデルと回答生成用LLMは、役割も必要な計算資源も異なります。 この説明ではベクトル検索を使いますが、RAGの検索部分はキーワード検索やハイブリッド検索で構成することもできます。zvecも選択肢の一つであり、RAG全体を自動的に完成させるものではありません。また、関連文書を渡しても回答の正確性が保証されるわけではなく、検索結果と生成結果をそれぞれ評価します。 この記事では、RAGの検索部分までを試す この後に使うzvec-grepは、文書の探索・抽出・埋め込み・zvecを使った検索をまとめて試す入口です。今回のサンプルは、関連する文書を検索して表示するところまでを扱います。回答生成用LLMは起動せず、RAG全体を動かす構成ではありません。 既存のOSSベクトルDBと比べたzvecの優位性 zvecの強みは、単一アプリやローカルツールへ、独立したDBサーバーを増やさずに検索機能を組み込めることです。特に、個人開発のRAG、デスクトップアプリ、CLIなど、データを利用するアプリの近くに置きたい用途で利点があります。 ただし、比較する製品の実行形態を揃える必要があります。QdrantやMilvusにはサーバー型だけでなく、ローカル実行向けの選択肢もあります。以下は公式資料にある構成の比較であり、処理速度やメモリ使用量の比較実験ではありません。 サーバー型DBに対して、導入・運用する構成を小さくできる 比較対象 公式資料にある実行形態 zvecを選ぶ利点・比較時の留意点 Qdrantのサーバー構成 DBコンテナを起動し、クライアントからAPIで接続 アプリ内で検索でき、DBサービスの起動・接続先・ポート管理を省ける Milvus Standaloneの公式Compose例 Milvus・etcd・MinIOの3コンテナを起動 この構成に比べ、別途管理するDB・メタデータ・オブジェクトストレージのサービスを減らせる Qdrant Python clientのLocal mode メモリ内またはローカルパスで動き、DBサーバー不要 サーバー不要という点は共通。利用言語、必要な機能、データ規模で選ぶ Milvus Lite Pythonアプリへ組み込み、ローカルファイルに保存 組み込み型という点は共通。Milvusの他構成とAPIを共有する点も比較する 根拠は、Qdrant Quickstart、MilvusのCompose手順、Qdrant clientのLocal mode、Milvus Liteの説明です。Milvusの3コンテナという記載は参照した公式Compose例を指し、すべてのMilvus構成に当てはめるものではありません。 この構成差から、zvecには次の設計上の利点があると考えられます。 管理するサービスを減らせる:検索のためだけにDBサーバーの起動・停止、接続設定、サービス監視を追加せずに済みます。単一アプリへ検索を追加するときの導入負担を抑えられます。 DBへのネットワーク往復を省ける:プロセス内の呼び出しで検索するため、DBクライアントとサーバー間の通信経路が不要です。これは通信に伴う処理を省けるという意味で、検索全体が必ず高速になるという測定結果ではありません。 アプリと一緒に配布・検証しやすい:SDKと保存先をアプリの構成へまとめられます。この記事でも、検索用サービス1個の中でzvecを利用します。バックアップや依存バージョンの管理は引き続き必要です。 組み込み型でも、検索機能を組み合わせられる zvec v0.7.0の公式READMEでは、Dense・Sparseベクトル、全文検索、構造化フィルター、それらを組み合わせるハイブリッド検索が案内されています。サーバーを立てずに、これらをアプリの検索基盤として使える点が魅力です。PythonとNode.jsのSDKがあり、本記事ではNode.js側から利用します。 文書の取り込みから検索までを早く試したい場合は、zvec-grepを入口にできます。ただし、そのファイル探索・抽出・CLI機能はzvec-grepが提供するものであり、zvec本体の機能とは区別します。また、ハイブリッド検索やローカル実行は他のOSSにもあるため、それらの存在だけでzvecが全面的に優位とは判断できません。 優位性が出る条件と、サーバー型を検討する条件 自分のPCや単一アプリで使う検索、まず小さく始めたいRAGでは、zvecの構成の少なさが利点になります。一方、複数アプリから同じデータをAPIで共有したい、DBをアプリと別に運用・増強したい、障害の影響範囲をプロセスごとに分けたい場合は、サーバー型DBの構成も比較対象になります。プロセス内で動くzvecでは、検索処理が使うCPU・メモリもアプリと同じプロセスに属するためです。 zvecは同一コレクションに対する複数プロセスの読み取りと、単一プロセスによる排他的な書き込みを区別しています。複数の書き込み元が必要なら、この制約を踏まえた設計が必要です。 本記事のサンプル3件の結果から、QdrantやMilvusより高速・省メモリ・高精度とは結論付けません。性能を比較する場合は、同じベクトル、検索条件、ハードウェア、同時実行数に加え、検索の再現率を揃えて、応答時間・スループット・インデックス作成時間・メモリ使用量を測る必要があります。 ! 検証範囲を先に示します。Linux x86_64・Node.js 24.19.0上で、配布版zvec-grep 0.2.1とzvec Node.js SDK 0.7.1を使い、同じ文書の登録、4種類の検索、再インデックスを実行しました。 執筆環境にはDockerがなく、以下のDockerイメージのビルドとCompose実行は未確認です。コンテナでの再現確認が必要なサンプルとして読んでください。 zvecとzvec-grepの役割 項目 zvec zvec-grep 主な役割 アプリに組み込むプロセス内ベクトルDB コードや文書を探すCLI・エージェント向け検索ツール 使い方 SDKでスキーマ・データ登録・検索を実装 zg index や zg query を実行 扱う処理 ベクトル・全文検索、構造化フィルターなど ファイル探索・抽出・埋め込み・検索結果の提示など 保存先 指定したローカルコレクション ワークスペースの .zvec-grep/ サーバー 独立したDBサーバーを必須としない 単発CLI実行とローカルの常駐Serverを選べる zvecの公式READMEでは、Dense・Sparseベクトル、全文検索、ハイブリッド検索などが案内されています。自作アプリに検索機能を組み込みたい場合は、このSDK側から設計します。文章からベクトルを作る埋め込み処理は、DBに保存する処理とは別に考える必要があります。 zvec-grepの公式READMEは、ripgrep、BM25、ベクトル検索をまとめるツールとして紹介しています。文書を用意して検索を試したい場合はこちらが入口になります。現行mainには別実装の案内もありますが、この記事はnpm配布のTypeScript/Node.js版を対象とします。 検索方法の使い分け 方法 入力の例 向いている場面 ripgrep password という文字列 既知の文字列・正規表現を漏れなく探す BM25/全文検索 password というキーワード キーワードに関連する文書を順位付きで探す ベクトル検索 recover access to an account 同じ単語がなくても近い意味の文書を探す ハイブリッド検索 自然文とキーワード 意味の近さと語句の手掛かりを併用する zvec-grepのインデックス検索は、抽出した文書断片の語句とベクトルから候補を探し、順位を統合できます。公式ArchitectureではRRF(Reciprocal Rank Fusion)が説明されています。対してripgrepは元ファイルを直接走査するため、インデックスも埋め込みモデルも不要です。 RAGへ進む場合は、この検索結果を根拠としてLLMに渡す処理を追加します。検索できることと、正確な回答を生成できることは別々に検証します。 前提条件 対象バージョン 要素 この記事の指定 コンテナ node:24-bookworm-slim(Debian・glibc系) zvec-grep @zvec/zvec-grep@0.2.1 zvec Node.js SDK @zvec/zvec@0.7.1(overrideで固定) 埋め込みモデル local/potion-retrieval-32m 実行方式 direct、CPU、単発コマンド コンテナの対象 linux/amd64 ホスト側 Docker EngineまたはDocker Desktop、Compose v2 公式mainのCLIガイドは変更が進んでいます。0.2.1では zg index・zg query を使用します。 mainにある zg --index などの構文を混ぜないでください。この記事の構文はv0.2.1のCLIガイドと、インストールしたパッケージのヘルプで確認しています。 Linux・Windows(Docker DesktopのLinuxコンテナ)などから実行できますが、記事のシェル例はBashを前提にします。ARM64ホストではamd64エミュレーションが必要になり、速度・互換性は別途確認してください。zvec本体が対応するプラットフォームと、CLIの依存パッケージが動く条件は同一ではありません。 小さな検証用として、Dockerへ2 vCPU・4 GiB程度のRAMと数GBの空きディスクを割り当てるところから始めます。これは公式の最低要件や実測による必要量ではありません。npm依存・モデル・イメージ・インデックスが容量を使い、対象文書が増えると追加のメモリと保存領域が必要です。GPUと外部LLM APIキーは使いません。 英語文書向けの軽量モデルを選ぶため、サンプルも英語です。日本語の検索品質をこの結果から判断しないでください。 手順 1. 作業ディレクトリを用意する zg-compose-demo を作り、その下に次のファイルを保存します。ファイル内容は後述の「コード例」にあります。 ファイル 用途 package.json zgとzvecのバージョン指定 Dockerfile CLIの実行イメージ compose.yaml 実行位置と永続化先 docs/password.md パスワード再設定の文書 docs/backup.md DBバックアップの文書 docs/cache.md 画像キャッシュの文書 mkdir -p zg-compose-demo/docs cd zg-compose-demo 構成はComposeサービス1個です。検索用コンテナの中でzvecも動きます。DBサーバーのサービスや公開ポートは追加しません。 2. ビルドとバージョン確認 ファイルを保存してから、以下を実行します。 docker compose config docker compose build docker compose run --rm zg --version docker compose run --rm zg index --help CLIのバージョンが 0.2.1 であることを確認します。config はCompose設定の確認であり、イメージのビルド成功や検索成功までは保証しません。 3. インデックスを作る docker compose run --rm zg index \ --embedding local/potion-retrieval-32m \ --mode direct \ --embedding-concurrency 1 docker compose run --rm zg status --mode direct コンテナの作業場所は /workspace で、ホストの docs/ をマウントします。その配下に .zvec-grep/ が作られます。 モデルは初回利用時にダウンロードされます。モデルキャッシュは ZVEC_GREP_MODEL_CACHE=/models に変更し、名前付きボリュームに保存します。今回の0.2.1のModel2Vec実装はHugging Faceからモデルを取得します。初回から完全オフラインでは動きません。ビルド時もコンテナレジストリとnpmへの接続が必要です。 4. 4種類の検索を比較する 既知の文字列を直接探します。この処理だけならモデルもインデックスも不要です。 docker compose run --rm zg query --rg -n password . 次は、作成したインデックスを使う検索です。 キーワード検索 docker compose run --rm zg query --fts password \ --mode direct --human --limit 3 ベクトル検索 docker compose run --rm zg query \ --vector "recover access to an account" \ --mode direct --human --limit 3 ハイブリッド検索 docker compose run --rm zg query \ "recover access to an account" \ --mode direct --human --limit 3 キーワードを明示して融合する場合は、次のようにも書けます。 docker compose run --rm zg query \ --hybrid "recover access to an account" \ --fts password --fuse \ --mode direct --human --limit 3 ネイティブ実行では3ファイルの登録に成功し、status は3件・512次元・cosineを表示しました。--fts password は password.md を返し、上記のベクトル検索とハイブリッド検索も同ファイルを1位に返しました。これはサンプル3件での確認であり、検索精度や性能のベンチマークではありません。 5. 文書の変更・再作成を確認する 文書を編集・追加した後は再インデックスします。direct の検索は既定では更新待ちをしないため、明示的な更新を基本にします。 docker compose run --rm zg index --mode direct \ --embedding-concurrency 1 変更なしで再実行したネイティブ検証では、3件が unchanged、失敗0件となりました。既存インデックスは保存したモデル設定を引き継ぎます。 モデルを変えるときは、ベクトル空間が変わるため再構築します。日本語などを扱う検証の出発点として、多言語モデルへ切り替える例です。 docker compose run --rm zg index --rebuild \ --embedding local/potion-multilingual-128m \ --mode direct --embedding-concurrency 1 この多言語モデルへの切り替えは未実行です。別モデルの取得容量、時間、品質を確認してください。 6. 終了・削除する run --rm は処理が終わると実行コンテナを削除します。インデックスはホストの docs/.zvec-grep/、モデルは名前付きボリュームに残るため、次の実行で再利用できます。 Composeが管理するコンテナやネットワークを片付ける docker compose down インデックスを削除する。原本のMarkdownは残る docker compose run --rm zg index --drop --yes --mode direct モデルキャッシュの名前付きボリュームも削除する docker compose down --volumes down --volumes だけでは、バインドマウント先の docs/.zvec-grep/ は削除されません。キャッシュ削除後は次回のモデル取得が必要です。ビルドしたイメージもこのコマンドでは削除しません。 コード例 package.json { "name": "zg-compose-demo", "version": "1.0.0", "private": true, "dependencies": { "@zvec/zvec-grep": "0.2.1" }, "overrides": { "@zvec/zvec": "0.7.1" } } zvec-grepが内部で使うzvecもoverrideで固定します。ただし、これだけでは間接依存の全バージョンは固定されません。継続利用するプロジェクトでは package-lock.json を生成・保存し、Dockerfileも npm ci を使う構成に変更します。 Dockerfile FROM node:24-bookworm-slim WORKDIR /opt/zg COPY package.json ./ RUN npm install --omit=dev \ && npm cache clean --force ENV PATH="/opt/zg/node_modules/.bin:${PATH}" WORKDIR /workspace ENTRYPOINT ["zg"] Debian系を使い、glibc向けネイティブバインディングを利用する構成です。--omit=optional は付けません。zvecやripgrepのプラットフォーム別バイナリもoptionalDependenciesとして配布されているためです。依存パッケージのインストールスクリプトが実行される点も含め、ビルドログを確認してください。 compose.yaml name: zg-compose-demo services: zg: platform: linux/amd64 build: context: . working_dir: /workspace environment: ZVEC_GREP_MODE: direct ZVEC_GREP_MODEL_CACHE: /models volumes: - ./docs:/workspace - model-cache:/models command: ["--help"] volumes: model-cache: /workspace は検索対象だけに絞ります。Dockerfileやnpm依存まで検索する必要はありません。モデルキャッシュを別のマウント先に置くことで、そのファイル群も検索対象から外します。 docs/password.md Password reset Send a time-limited reset link by email when a user forgets a password. docs/backup.md Database backup Create a daily database backup and test restoration regularly. docs/cache.md Image cache Cache resized images to reduce page loading time. 注意点 ローカルモデルと通信・保存を区別する 今回のモデルを使う埋め込み計算はローカルです。一方、初回のモデル取得は外部通信を伴います。原文・検索語の外部埋め込みAPIへの送信は、この構成では設定していません。 モデルキャッシュとインデックスは別のデータです。特にインデックスには検索用の情報が残るため、元文書と同じように扱ってください。モデルをキャッシュしても、完全オフラインでの初回起動や更新を検証したことにはなりません。 書き込みとファイル所有者に注意する この例はイメージの既定ユーザーで動くため、Linuxホストでは docs/.zvec-grep/ がroot所有になる場合があります。共有環境へ持ち込む際はUID/GIDと書き込み権限を調整します。 同じインデックスへ複数の index を同時実行しないでください。zvecの公式READMEでは、読み取りの並行実行と、単一プロセスによる排他的な書き込みが区別されています。 常駐サーバーが必要になる場面 今回は単発CLIを選びました。direct はコマンドごとにプロセスを終了するため、モデルの読み込みも繰り返します。頻繁に検索する場合やエージェントからMCPで利用する場合は、モデルを再利用できるServer構成を検討します。 zvec-grepのServerはローカルループバックでの利用を前提とします。今回のComposeへポート公開だけを足してリモート検索APIにする設計は扱いません。 実行済みと未確認を整理する 対象 確認状況 配布版0.2.1のヘルプとコマンド構文 ネイティブ実行で確認 3文書の登録・状態確認・4種類の検索 ネイティブ実行で確認 複数クエリの融合・変更なしの再インデックス ネイティブ実行で確認 Docker build・Compose起動・マウント先の永続化 未実行 インデックス削除・キャッシュ削除 手順掲載のみ、未実行 多言語モデル・日本語の検索品質 未実行 大量データの性能・最低必要スペック 未検証 ネイティブ検証では、インストールスクリプトを抑止して依存を配置した後、zvecのバイナリ準備処理を実行しています。Dockerfileの通常の npm install と同一のビルド経路を検証したものではありません。コンテナ側の依存解決・ネイティブバイナリ・保存先を確認する際は、「ビルドとバージョン確認」から順に進めてください。 参考資料 zvec v0.7.0 README:サンプルに近いリリース世代の組み込み型DBとしての構成と検索機能。 Qdrant Local Quickstart:サーバー構成の起動・接続方法。 Qdrant Python client Local mode:サーバー不要のローカル実行。 Milvus Docker Compose:Standaloneの公式Compose例。 Milvus Lite:Pythonへの組み込みと他のMilvus構成とのAPI共有。 Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks:検索と生成を組み合わせるRAGの原論文。 alibaba/zvec:プロセス内DB、検索機能、対応プラットフォーム、並行アクセスの説明。 zvec-ai/zvec-grep:検索CLIの概要とzvecとの関係。 zvec-grep v0.2.1 CLI guide:本文で使用したコマンドとオプション。 zvec-grep v0.2.1 Embedding models:モデル選択、キャッシュ、モデル変更の方針。 zvec-grep Architecture:インデックス検索とripgrepの経路、BM25・ベクトル・RRFの関係。 zvec-grep Server and execution modes:Direct/Serverの考え方。mainのCLI構文は配布版0.2.1と異なるため、実行時は配布版のヘルプも確認する。 資料と配布パッケージは2026年10月21日(日本時間)に確認しました。
Remember Obi Wan. Tools that matter. zvec-ai/zvec-grep
在本地找文件,精确匹配和语义检索分两套,Agent 容易空转:一个对不上文件名,一个对不上口头说的意思。 zvec-ai/zvec-grep 把 ripgrep、BM25 和向量检索收进同一个本地命令。语义和词法要先建索引;zg --rg 可不建。给 Agent 只开索引检索,精确正则走本机 grep。入口合一,目录权限要收紧。 t.co