Skip to content

Commit 8fe0900

Browse files
authored
Merge branch 'main' into fix/non-x86-build
2 parents 6040065 + 1492175 commit 8fe0900

18 files changed

Lines changed: 260 additions & 95 deletions

File tree

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
<p align="right">
2+
English | <a href="./README_CN.md">简体中文</a>
3+
</p>
4+
15
<div align="center">
26
<picture>
37
<source media="(prefers-color-scheme: dark)" srcset="https://zvec.oss-cn-hongkong.aliyuncs.com/logo/github_log_2.svg" />
@@ -32,10 +36,11 @@
3236

3337
> [!IMPORTANT]
3438
> **🚀 v0.3.0 Released on April 3, 2026**
39+
>
3540
> - **New Platforms**: Initial **Windows (MSVC)** and **Android** support. Published official Windows **Python** and **Node.js** packages.
3641
> - **Efficiency**: **RabitQ** quantization and **CPU Auto-Dispatch** for optimized SIMD execution.
3742
> - **Ecosystem**: **C-API** for custom language bindings and **[MCP](https://github.com/zvec-ai/zvec-mcp-server) / [Skill](https://github.com/zvec-ai/zvec-agent-skills)** integration for AI Agents.
38-
>
43+
>
3944
> 👉 [Read the Release Notes](https://github.com/alibaba/zvec/releases/tag/v0.3.0) | [View Roadmap 📍](https://github.com/alibaba/zvec/issues/309)
4045
4146
## 💫 Features
@@ -120,7 +125,7 @@ Stay updated and get support — scan or click:
120125

121126
| 💬 DingTalk | 📱 WeChat | 🎮 Discord | X (Twitter) |
122127
| :---: | :---: | :---: | :---: |
123-
| <img src="https://zvec.oss-cn-hongkong.aliyuncs.com/qrcode/dingding.png" width="150" alt="DingTalk QR Code"/> | <img src="https://zvec.oss-cn-hongkong.aliyuncs.com/qrcode/wechat.png?v=4" width="150" alt="WeChat QR Code"/> | [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/rKddFBBu9z) | [![X (formerly Twitter) Follow](https://img.shields.io/twitter/follow/ZvecAI)](<https://x.com/ZvecAI>) |
128+
| <img src="https://zvec.oss-cn-hongkong.aliyuncs.com/qrcode/dingding.png" width="150" alt="DingTalk QR Code"/> | <img src="https://zvec.oss-cn-hongkong.aliyuncs.com/qrcode/wechat.png?v=5" width="150" alt="WeChat QR Code"/> | [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/rKddFBBu9z) | [![X (formerly Twitter) Follow](https://img.shields.io/twitter/follow/ZvecAI)](<https://x.com/ZvecAI>) |
124129
| Scan to join | Scan to join | Click to join | Click to follow |
125130

126131
</div>

README_CN.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<p align="right">
2+
<a href="./README.md">English</a> | 简体中文
3+
</p>
4+
5+
<div align="center">
6+
<picture>
7+
<source media="(prefers-color-scheme: dark)" srcset="https://zvec.oss-cn-hongkong.aliyuncs.com/logo/github_log_2.svg" />
8+
<img src="https://zvec.oss-cn-hongkong.aliyuncs.com/logo/github_logo_1.svg" width="400" alt="zvec logo" />
9+
</picture>
10+
</div>
11+
12+
<p align="center">
13+
<a href="https://codecov.io/github/alibaba/zvec"><img src="https://codecov.io/github/alibaba/zvec/graph/badge.svg?token=O81CT45B66" alt="代码覆盖率"/></a>
14+
<a href="https://github.com/alibaba/zvec/actions/workflows/01-ci-pipeline.yml"><img src="https://github.com/alibaba/zvec/actions/workflows/01-ci-pipeline.yml/badge.svg?branch=main" alt="Main"/></a>
15+
<a href="https://github.com/alibaba/zvec/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="许可证"/></a>
16+
<a href="https://pypi.org/project/zvec/"><img src="https://img.shields.io/pypi/v/zvec.svg" alt="PyPI 版本"/></a>
17+
<a href="https://pypi.org/project/zvec/"><img src="https://img.shields.io/badge/python-3.10%20~%203.14-blue.svg" alt="Python 版本"/></a>
18+
<a href="https://www.npmjs.com/package/@zvec/zvec"><img src="https://img.shields.io/npm/v/@zvec/zvec.svg" alt="npm 版本"/></a>
19+
</p>
20+
21+
<p align="center">
22+
<a href="https://trendshift.io/repositories/20830" target="_blank"><img src="https://trendshift.io/api/badge/repositories/20830" alt="alibaba%2Fzvec | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
23+
</p>
24+
25+
<p align="center">
26+
<a href="https://zvec.org/en/docs/quickstart/">🚀 <strong>快速开始</strong> </a> |
27+
<a href="https://zvec.org/en/">🏠 <strong>主页</strong> </a> |
28+
<a href="https://zvec.org/en/docs/">📚 <strong>文档</strong> </a> |
29+
<a href="https://zvec.org/en/docs/benchmarks/">📊 <strong>性能报告</strong> </a> |
30+
<a href="https://deepwiki.com/alibaba/zvec">🔎 <strong>DeepWiki</strong> </a> |
31+
<a href="https://discord.gg/rKddFBBu9z">🎮 <strong>Discord</strong> </a> |
32+
<a href="https://x.com/ZvecAI">🐦 <strong>X (Twitter)</strong> </a>
33+
</p>
34+
35+
**Zvec** 是一款开源的嵌入式(进程内)向量数据库 — 轻量、极速,可直接嵌入应用程序。以极简的配置提供生产级、低延迟、可扩展的向量检索能力。
36+
37+
> [!IMPORTANT]
38+
> **🚀 v0.3.0 已于 2026 年 4 月 3 日发布**
39+
>
40+
> - **新平台支持**:支持 **Windows (MSVC)****Android**。发布了官方 Windows **Python****Node.js** 安装包。
41+
> - **性能优化**:集成 **RabitQ** 量化以及 **CPU 指令集自适应检测**,优化 SIMD 执行。
42+
> - **生态集成**:提供 **C-API** 用于多种编程语言绑定,以及 **[MCP](https://github.com/zvec-ai/zvec-mcp-server) / [Skill](https://github.com/zvec-ai/zvec-agent-skills)** 集成。
43+
>
44+
> 👉 [查看发布说明](https://github.com/alibaba/zvec/releases/tag/v0.3.0) | [查看路线图 📍](https://github.com/alibaba/zvec/issues/309)
45+
46+
## 💫 核心特性
47+
48+
- **极致性能**:毫秒级响应,轻松检索数十亿级向量。
49+
- **开箱即用**[安装](#-安装)后即刻开始搜索,无需服务器、无需配置、零门槛。
50+
- **稠密 + 稀疏向量**:支持稠密向量和稀疏向量,提供多向量联合查询的原生支持。
51+
- **混合检索**:向量语义搜索 + 标量条件过滤,获得精确结果。
52+
- **进程内运行**:无需单独部署服务,纯进程内运行。Notebook、高性能服务器、CLI 工具、边缘设备 — 随处可用。
53+
54+
## 📦 安装
55+
56+
### [Python](https://pypi.org/project/zvec/)
57+
58+
**环境要求**:Python 3.10 - 3.14
59+
60+
```bash
61+
pip install zvec
62+
```
63+
64+
### [Node.js](https://www.npmjs.com/package/@zvec/zvec)
65+
66+
```bash
67+
npm install @zvec/zvec
68+
```
69+
70+
### ✅ 支持的平台
71+
72+
- Linux (x86_64, ARM64)
73+
- macOS (ARM64)
74+
- Windows (x86_64)
75+
76+
### 🛠️ 源码构建
77+
78+
如需从源码构建 Zvec,请参考[源码构建指南](https://zvec.org/en/docs/build/)
79+
80+
## ⚡ 一分钟上手
81+
82+
```python
83+
import zvec
84+
85+
# 定义 collection schema
86+
schema = zvec.CollectionSchema(
87+
name="example",
88+
vectors=zvec.VectorSchema("embedding", zvec.DataType.VECTOR_FP32, 4),
89+
)
90+
91+
# 创建 collection
92+
collection = zvec.create_and_open(path="./zvec_example", schema=schema)
93+
94+
# 插入 documents
95+
collection.insert([
96+
zvec.Doc(id="doc_1", vectors={"embedding": [0.1, 0.2, 0.3, 0.4]}),
97+
zvec.Doc(id="doc_2", vectors={"embedding": [0.2, 0.3, 0.4, 0.1]}),
98+
])
99+
100+
# 向量相似度检索
101+
results = collection.query(
102+
zvec.VectorQuery("embedding", vector=[0.4, 0.3, 0.3, 0.1]),
103+
topk=10
104+
)
105+
106+
# 查询结果:按相关性排序的 {'id': str, 'score': float, ...} 列表
107+
print(results)
108+
```
109+
110+
## 📈 极致性能
111+
112+
Zvec 提供极致的速度和效率,能够轻松应对高要求的生产环境负载。
113+
114+
<img src="https://zvec.oss-cn-hongkong.aliyuncs.com/qps_10M.svg" width="800" alt="Zvec 性能基准测试" />
115+
116+
有关具体的测试方法、配置及完整结果,请参阅[性能报告](https://zvec.org/en/docs/benchmarks/)
117+
118+
## 🤝 加入社区
119+
120+
<div align="center">
121+
122+
获取最新动态和技术支持:
123+
124+
<div align="center">
125+
126+
| 💬 钉钉群 | 📱 微信群 | 🎮 Discord | X (Twitter) |
127+
| :---: | :---: | :---: | :---: |
128+
| <img src="https://zvec.oss-cn-hongkong.aliyuncs.com/qrcode/dingding.png" width="150" alt="钉钉二维码"/> | <img src="https://zvec.oss-cn-hongkong.aliyuncs.com/qrcode/wechat.png?v=5" width="150" alt="微信二维码"/> | [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/rKddFBBu9z) | [![X (formerly Twitter) Follow](https://img.shields.io/twitter/follow/ZvecAI)](<https://x.com/ZvecAI>) |
129+
| 扫码加入 | 扫码加入 | 点击加入 | 点击关注 |
130+
131+
</div>
132+
133+
</div>
134+
135+
## ❤️ 参与贡献
136+
137+
非常欢迎来自社区的每一份贡献!无论是修复 Bug、新增功能,还是完善文档,都将让 Zvec 变得更好。
138+
139+
请查阅我们的[贡献指南](./CONTRIBUTING.md)开始参与!

python/tests/detail/test_collection_create_and_open.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,11 @@ def check_collection_full(coll: Collection):
127127
valid_path_list = [
128128
"/tmp/nonexistent/directory/test_collection",
129129
"test/collection/with/slashes",
130+
"test/collection/with/slashes/哈哈",
130131
]
131132
invalid_path_list = [
132-
"invalid:path",
133+
"invalid\0path",
133134
"",
134-
"test_collection_with_spaces ",
135-
"test@#$%collection",
136135
]
137136

138137

src/ailego/utility/file_helper.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ bool FileHelper::IsSame(const char *path1, const char *path2) {
227227
return (!strcmp(real_path1, real_path2));
228228
}
229229

230+
std::string FileHelper::GetLastErrorString() {
231+
return strerror(errno);
232+
}
233+
230234
#else
231235
#undef RemoveDirectory
232236
#undef DeleteFile
@@ -352,6 +356,25 @@ bool FileHelper::IsSymbolicLink(const char *path) {
352356
(attr & FILE_ATTRIBUTE_REPARSE_POINT));
353357
}
354358

359+
std::string FileHelper::GetLastErrorString() {
360+
DWORD err = GetLastError();
361+
if (err == 0) {
362+
return "No error";
363+
}
364+
char buf[256];
365+
DWORD len = FormatMessageA(
366+
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, err,
367+
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, sizeof(buf), nullptr);
368+
if (len > 0) {
369+
// Strip trailing newline that FormatMessage often appends
370+
while (len > 0 && (buf[len - 1] == '\r' || buf[len - 1] == '\n')) {
371+
buf[--len] = '\0';
372+
}
373+
return std::string(buf, len);
374+
}
375+
return "Unknown error " + std::to_string(err);
376+
}
377+
355378
bool FileHelper::IsSame(const char *path1, const char *path2) {
356379
char real_path1[MAX_PATH];
357380
char real_path2[MAX_PATH];

src/core/framework/index_mapping.cc

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
#include <zvec/ailego/io/mmap_file.h>
16+
#include <zvec/ailego/utility/file_helper.h>
1617
#include <zvec/core/framework/index_error.h>
1718
#include <zvec/core/framework/index_logger.h>
1819
#include <zvec/core/framework/index_mapping.h>
@@ -60,7 +61,8 @@ static inline bool WritePadding(ailego::File &file, size_t size) {
6061
static inline int UnpackMappingSize(ailego::File &file, size_t *len) {
6162
IndexFormat::MetaHeader header;
6263
if (file.read(&header, sizeof(header)) != sizeof(header)) {
63-
LOG_ERROR("Failed to read file, errno %d, %s", errno, std::strerror(errno));
64+
LOG_ERROR("Failed to read file, %s",
65+
ailego::FileHelper::GetLastErrorString().c_str());
6466
return IndexError_ReadData;
6567
}
6668

@@ -93,13 +95,8 @@ int IndexMapping::open(const std::string &path, bool cow, bool full_mode) {
9395

9496
bool read_only = copy_on_write_ && !full_mode_;
9597
if (!file_.open(path.c_str(), read_only, false)) {
96-
#if defined(_WIN32) || defined(_WIN64)
97-
LOG_ERROR("Failed to open file %s, win32 error %lu", path.c_str(),
98-
GetLastError());
99-
#else
100-
LOG_ERROR("Failed to open file %s, errno %d, %s", path.c_str(), errno,
101-
std::strerror(errno));
102-
#endif
98+
LOG_ERROR("Failed to open file %s, %s", path.c_str(),
99+
ailego::FileHelper::GetLastErrorString().c_str());
103100
return IndexError_OpenFile;
104101
}
105102

@@ -111,8 +108,8 @@ int IndexMapping::open(const std::string &path, bool cow, bool full_mode) {
111108
}
112109

113110
if (!file_.seek(0, ailego::File::Origin::End)) {
114-
LOG_ERROR("Failed to seek file %s, errno %d, %s", path.c_str(), errno,
115-
std::strerror(errno));
111+
LOG_ERROR("Failed to seek file %s, %s", path.c_str(),
112+
ailego::FileHelper::GetLastErrorString().c_str());
116113
return IndexError_SeekFile;
117114
}
118115
return this->init_index_mapping(mapping_size);
@@ -125,8 +122,8 @@ int IndexMapping::create(const std::string &path, size_t seg_meta_capacity) {
125122

126123
// write() & copying to mmap() will auto extend the file size
127124
if (!file_.create(path.c_str(), 0)) {
128-
LOG_ERROR("Failed to create file %s, errno %d, %s", path.c_str(), errno,
129-
std::strerror(errno));
125+
LOG_ERROR("Failed to create file %s, %s", path.c_str(),
126+
ailego::FileHelper::GetLastErrorString().c_str());
130127
return IndexError_CreateFile;
131128
}
132129
huge_page_ = Ishugetlbfs(path);
@@ -155,22 +152,22 @@ int IndexMapping::init_meta_section() {
155152
// Write index header
156153
IndexFormat::SetupMetaHeader(&meta_header, len - sizeof(meta_footer), len);
157154
if (!file_.seek(current_header_start_offset_, ailego::File::Origin::Begin)) {
158-
LOG_ERROR("Failed to seek file %s, errno %d, %s", path.c_str(), errno,
159-
std::strerror(errno));
155+
LOG_ERROR("Failed to seek file %s, %s", path.c_str(),
156+
ailego::FileHelper::GetLastErrorString().c_str());
160157
return IndexError_SeekFile;
161158
}
162159
if (file_.write(&meta_header, sizeof(meta_header)) != sizeof(meta_header)) {
163-
LOG_ERROR("Failed to write file: %s, errno %d, %s", path.c_str(), errno,
164-
std::strerror(errno));
160+
LOG_ERROR("Failed to write file: %s, %s", path.c_str(),
161+
ailego::FileHelper::GetLastErrorString().c_str());
165162
return IndexError_WriteData;
166163
}
167164

168165
// Write padding data
169166
uint32_t segments_meta_size =
170167
static_cast<uint32_t>(len - (sizeof(meta_header) + sizeof(meta_footer)));
171168
if (!WritePadding(file_, segments_meta_size)) {
172-
LOG_ERROR("Failed to write file: %s, errno %d, %s", path.c_str(), errno,
173-
std::strerror(errno));
169+
LOG_ERROR("Failed to write file: %s, %s", path.c_str(),
170+
ailego::FileHelper::GetLastErrorString().c_str());
174171
return IndexError_WriteData;
175172
}
176173

@@ -180,8 +177,8 @@ int IndexMapping::init_meta_section() {
180177
meta_footer.total_size = len;
181178
IndexFormat::UpdateMetaFooter(&meta_footer, 0);
182179
if (file_.write(&meta_footer, sizeof(meta_footer)) != sizeof(meta_footer)) {
183-
LOG_ERROR("Failed to write file: %s, errno %d, %s", path.c_str(), errno,
184-
std::strerror(errno));
180+
LOG_ERROR("Failed to write file: %s, %s", path.c_str(),
181+
ailego::FileHelper::GetLastErrorString().c_str());
185182
return IndexError_WriteData;
186183
}
187184
return this->init_index_mapping(len);
@@ -310,8 +307,8 @@ int IndexMapping::append(const std::string &id, size_t size) {
310307
}
311308

312309
if (!copy_on_write_ && !file_.truncate(index_size_ + size)) {
313-
LOG_ERROR("Failed to truncate file, errno %d, %s", errno,
314-
std::strerror(errno));
310+
LOG_ERROR("Failed to truncate file, %s",
311+
ailego::FileHelper::GetLastErrorString().c_str());
315312
return IndexError_TruncateFile;
316313
}
317314

@@ -425,8 +422,8 @@ void IndexMapping::unmap_all(void) {
425422

426423
int IndexMapping::flush(void) {
427424
if ((file_.size() < index_size_) && !file_.truncate(index_size_)) {
428-
LOG_ERROR("Failed to truncate file size %zu, errno %d, %s", index_size_,
429-
errno, std::strerror(errno));
425+
LOG_ERROR("Failed to truncate file size %zu, %s", index_size_,
426+
ailego::FileHelper::GetLastErrorString().c_str());
430427
return IndexError_TruncateFile;
431428
}
432429

@@ -443,8 +440,8 @@ int IndexMapping::flush(void) {
443440
segment_info.segment_header->content_offset +
444441
item->meta()->data_index;
445442
if (file_.write(off, item->data(), segment_size) != segment_size) {
446-
LOG_ERROR("Failed to write segment, size %zu, errno %d, %s",
447-
segment_size, errno, std::strerror(errno));
443+
LOG_ERROR("Failed to write segment, size %zu, %s", segment_size,
444+
ailego::FileHelper::GetLastErrorString().c_str());
448445
return IndexError_WriteData;
449446
}
450447
} else {
@@ -464,8 +461,9 @@ int IndexMapping::flush(void) {
464461
auto header = item.second;
465462
if (file_.write(header_start_offset, header, header->content_offset) !=
466463
header->content_offset) {
467-
LOG_ERROR("Failed to write segment, size %lu, errno %d, %s",
468-
header->content_offset, errno, std::strerror(errno));
464+
LOG_ERROR("Failed to write segment, size %lu, %s",
465+
header->content_offset,
466+
ailego::FileHelper::GetLastErrorString().c_str());
469467
return IndexError_WriteData;
470468
}
471469
}
@@ -487,7 +485,8 @@ int IndexMapping::init_index_mapping(size_t len) {
487485
uint8_t *start = reinterpret_cast<uint8_t *>(ailego::File::MemoryMap(
488486
file_.native_handle(), current_header_start_offset_, len, opts));
489487
if (!start) {
490-
LOG_ERROR("Failed to map file, errno %d, %s", errno, std::strerror(errno));
488+
LOG_ERROR("Failed to map file, %s",
489+
ailego::FileHelper::GetLastErrorString().c_str());
491490
return IndexError_MMapFile;
492491
}
493492

src/core/quantizer/record_quantizer.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,10 @@ class RecordQuantizer {
4444
scale = 254 / std::max(max - min, epsilon);
4545
bias = -min * scale - 127;
4646
for (size_t i = 0; i < dim; ++i) {
47-
float v = vec[i] * scale + bias;
47+
float v = std::round(vec[i] * scale + bias);
4848
squared_sum += v * v;
4949
sum += v;
50-
(reinterpret_cast<int8_t *>(out))[i] =
51-
static_cast<int8_t>(std::round(v));
50+
(reinterpret_cast<int8_t *>(out))[i] = static_cast<int8_t>(v);
5251
int8_sum += (reinterpret_cast<int8_t *>(out))[i];
5352
}
5453
extras = reinterpret_cast<float *>(static_cast<int8_t *>(out) + dim);

0 commit comments

Comments
 (0)