PHPVector
一个纯 PHP 向量数据库,实现了 HNSW(Hierarchical Navigable Small World)用于近似最近邻搜索,以及 BM25 用于全文检索。两种引擎可以组合成一个单一的 混合搜索(hybrid search) 管道。
要求
- ext-pcntl(可选)。启用异步文档写入,可显著降低插入延迟
安装
composer require ezimuel/phpvector
快速开始
1. 插入文档
Document 对象包含密集嵌入向量、可选的原始文本(用于 BM25)以及你希望随结果返回的任意元数据。id 字段可选——如果省略,将自动分配一个随机的 UUID v4。
usePHPVector\Document;use PHPVector\VectorDatabase;$db = new VectorDatabase();$db->addDocuments([new Document( id: 1, vector: [0.12, 0.85, 0.44, 0.67], text: 'PHP vector database with HNSW index', metadata: ['url' => 'https://example.com/1', 'lang' => 'en'], ),new Document( id: 2, vector: [0.91, 0.23, 0.78, 0.05], text: 'Approximate nearest neighbour search in PHP', metadata: ['url' => 'https://example.com/2', 'lang' => 'en'], ),new Document( id: 3, vector: [0.33, 0.61, 0.19, 0.88], text: 'BM25 full-text ranking algorithm explained', metadata: ['url' => 'https://example.com/3', 'lang' => 'en'], ),// 未提供 id 时,会自动分配 UUID v4new Document( vector: [0.55, 0.42, 0.71, 0.30], text: 'Hybrid search with Reciprocal Rank Fusion', ),]);
2. 向量搜索
使用 HNSW 查找与查询向量最相似的 k 个文档。
$queryVector = [0.10, 0.80, 0.50, 0.60];$results = $db->vectorSearch(vector: $queryVector, k: 2);foreach ($results as $result) {echo sprintf("[%d] score=%.4f %s\n", $result->rank, $result->score, $result->document->metadata['url'], );}// 输出示例:// [1] score=0.9987 https://example.com/1// [2] score=0.8341 https://example.com/3
3. 全文搜索
使用 BM25 根据文本查询对文档进行相关性排序。
$results = $db->textSearch(query: 'nearest neighbour PHP', k: 2);foreach ($results as $result) {echo sprintf("[%d] score=%.4f %s\n", $result->rank, $result->score, $result->document->metadata['url'], );}// 输出示例:// [1] score=1.2430 https://example.com/2// [2] score=0.8761 https://example.com/1
4. 混合搜索
将向量相似度分数和 BM25 分数融合成一个统一的排序列表。
推荐方式
RRF 基于排名且与分数尺度无关,无需调参。
usePHPVector\HybridMode;$results = $db->hybridSearch( vector: $queryVector, text: 'vector database PHP', k: 3, mode: HybridMode::RRF,);foreach ($results as $result) {echo sprintf("[%d] score=%.4f %s\n", $result->rank, $result->score, $result->document->metadata['url'], );}
加权组合
将两种分数都归一化到 [0, 1] 区间,然后应用显式权重。
$results = $db->hybridSearch( vector: $queryVector, text: 'vector database PHP', k: 3, mode: HybridMode::Weighted, vectorWeight: 0.7, textWeight: 0.3,);
配置
HNSW 和 BM25 引擎均可完全配置。将配置对象传递给 VectorDatabase 构造函数即可。
usePHPVector\BM25\ConfigasBM25Config;use PHPVector\BM25\SimpleTokenizer;use PHPVector\Distance;use PHPVector\HNSW\ConfigasHNSWConfig;use PHPVector\VectorDatabase;$db = new VectorDatabase( hnswConfig: new HNSWConfig( M: 16, // 每层每个节点的最大连接数。越高 → 召回率越好,但内存占用增加。 efConstruction: 200, // 构建索引时的束宽。越高 → 图质量越好,但插入变慢。 efSearch: 50, // 查询时的束宽。越高 → 召回率越好,但查询变慢。 distance: Distance::Cosine, // 支持:Cosine | Euclidean | DotProduct | Manhattan useHeuristic: true, // 多样化邻居选择(推荐启用)。 ), bm25Config: new BM25Config( k1: 1.5, // TF 饱和度。推荐范围 1.2–2.0。 b: 0.75, // 长度归一化。0 = 不归一化,1 = 完全归一化。 ), tokenizer: new SimpleTokenizer( stopWords: SimpleTokenizer::DEFAULT_STOP_WORDS, minTokenLength: 2, ),);
距离度量
HNSW 调优速查表
| |
|---|
| 增大 efSearch 或 efConstruction |
| |
| |
| |
持久化
PHPVector 采用基于文件夹的持久化模型。每个数据库对应一个独立目录,里面包含 HNSW 图、BM25 索引以及每个文档单独的文件。这种设计有两个主要优势:
- 加载时内存占用低 —— 仅将 HNSW 图和 BM25 索引加载到内存。单个文档文件(
docs/{n}.bin)采用懒加载,仅在搜索结果中出现时才读取。 - 插入延迟低 —— 文档文件通过子进程异步写入磁盘(需要 ext-pcntl),因此
addDocument() 调用会立即返回。
文件夹结构
/var/data/mydb/ meta.json — 距离度量、维度、文档 ID 映射 hnsw.bin — HNSW 图(向量 + 连接关系) bm25.bin — BM25 倒排索引 docs/ 0.bin — 文档 0(id、文本、元数据) 1.bin — 文档 1 …
保存
在构造函数中传入 path 参数即可启用持久化。每次 addDocument() 调用都会将文档文件写入 docs/ 目录(启用 ext-pcntl 时异步进行)。最后调用一次 save() 将 HNSW 图和 BM25 索引刷新到磁盘(会等待所有异步写入完成)。
usePHPVector\Document;usePHPVector\VectorDatabase;$db = new VectorDatabase(path: '/var/data/mydb');$db->addDocuments([new Document(id: 1, vector: [0.12, 0.85, 0.44], text: 'PHP vector search', metadata: ['source' => 'blog']),new Document(id: 2, vector: [0.91, 0.23, 0.78], text: 'Approximate nearest neighbour'),// ... 可以插入数千个文档]);// 将 HNSW 图和 BM25 索引刷盘(文档文件已异步写入)$db->save();
加载
使用 VectorDatabase::open() 加载已保存的数据库(文档加载部分此处原文被截断,实际使用时可参考源码或后续更新)。