Store embeddings and run k-NN search. Two interchangeable backends implement the same
FerryAI\Core\Contracts\VectorStore contract.
| Backend | Search | Best for |
|---|---|---|
| SQLite (default) | PHP brute-force, or native KNN via sqlite-vec when available | dev, demos, embedded |
| PostgreSQL + pgvector | native <=>/<->/<#>, HNSW/IVFFlat indexes |
production, large collections, concurrent access |
$store = AI::vector('docs'); // open or create a collection
// Add one or many
$store->add('doc1', $vec->vector, ['lang' => 'en', 'title' => 'Hello']);
$store->addBatch([
['id' => 'doc2', 'vector' => [...], 'metadata' => ['lang' => 'fr']],
['id' => 'doc3', 'vector' => [...], 'metadata' => ['lang' => 'en']],
]);
// Search
$hits = $store->search($query, k: 5, filter: ['lang' => ['eq' => 'en']]);
// [ ['id' => 'doc1', 'distance' => 0.02, 'metadata' => ['lang'=>'en']], ... ]
// CRUD
$store->update('doc1', $newVector); // update vector (metadata optional)
$store->delete('doc1'); // delete by id
$store->deleteByFilter(['lang' => ['eq' => 'fr']]); // returns number deleted
$store->count(); // how many vectors
$store->dimension(); // vector dimension (0 if auto-detect)
$store->collectionName(); // the collection name
$store->clear(); // delete all
$store->export(); // json-serializable snapshot
foreach ($store->iterator() as $row) { /* stream all entries */ }See examples/10-vector-store.php.
interface VectorStore
{
public function add(string $id, array $vector, ?array $metadata = null): void;
public function addBatch(array $items): void;
public function search(array $queryVector, int $k = 10, ?array $filter = null): array;
public function delete(string $id): void;
public function deleteByFilter(array $filter): int; // number deleted
public function update(string $id, ?array $vector = null, ?array $metadata = null): void;
public function count(): int;
public function dimension(): int;
public function collectionName(): string;
public function iterator(): \Iterator;
public function export(): array;
public function clear(): void;
}MetadataFilter supports these operators on JSON metadata fields:
| Operator | Syntax | Example |
|---|---|---|
eq |
['field' => ['eq' => value]] |
Exact match |
neq |
['field' => ['neq' => value]] |
Not equal |
gt / gte |
['field' => ['gt' => 100]] |
Greater than / greater or equal |
lt / lte |
['field' => ['lte' => 200]] |
Less than / less or equal |
in / nin |
['field' => ['in' => [1,2,3]]] |
In / not in |
contains |
['field' => ['contains' => 'substr']] |
String contains |
exists |
['field' => ['exists' => true]] |
Key presence check |
Boolean combinators: and, or, not wrap multiple conditions:
$store->search($q, 10, ['and' => [
['category' => ['eq' => 'tools']],
['price' => ['lt' => 200]],
['or' => [
['brand' => ['eq' => 'Makita']],
['brand' => ['eq' => 'DeWalt']],
]],
]]);Default driver. Data is brute-forced in PHP via BruteForceIndex. To accelerate with native
KNN, install the sqlite-vec vec0 extension and set FERRY_AI_VEC_EXTENSION_LIB — the
collection then uses vec0 virtual tables for unfiltered search and falls back to brute force
for filtered queries.
SqliteVecExtension manages the FFI binding to vec0. The SQLite database path is
vector.db_path (default :memory:).
See examples/23-sqlite-vec.php.
Source: asg017/sqlite-vec.
AI::config(['vector' => [
'driver' => 'pgsql',
'dsn' => 'pgsql:host=127.0.0.1;port=5432;dbname=ferryai',
'user' => 'postgres',
'password' => 'postgres',
'metric' => 'cosine',
'dimension' => 384,
]]);
$store = AI::vector('docs'); // PostgresCollection (native ANN)
// Build a HNSW index for fast approximate search (instance method over a PostgresStore)
$index = new \FerryAI\Vector\PostgresVecIndex(
new \FerryAI\Vector\PostgresStore('pgsql:host=127.0.0.1;port=5432;dbname=ferryai', 'postgres', 'postgres'),
);
$index->createIndex('docs', 'hnsw', 'cosine');Requires ext-pdo_pgsql + the pgvector extension.
Vectors live in native vector(dim) columns with jsonb metadata.
See examples/21-postgres-vector.php.
Metrics map: cosine → <=>, euclidean → <->, dot → <#>.
$json = $store->export(); // json-serializable array
\FerryAI\Vector\ExportImport::toJson($store, '/path/to/export.json');
\FerryAI\Vector\ExportImport::toCsv($store, '/path/to/export.csv');
// Import rebuilds a Collection from a JSON snapshot over an SQLiteStore
$restored = \FerryAI\Vector\ExportImport::fromJson(
'/path/to/export.json',
'docs', // collection name
384, // dimension
$sqliteStore, // SQLiteStore instance
);