PHP SDK

The official PHP SDK for brinicle provides an idiomatic HTTP client for communicating with the brinicle vector engine server. It requires PHP 8.1+ and uses Guzzle HTTP under the hood.

Installation

composer require bicardinal/brinicle-php

Quick Start

<?php

use Bicardinal\Brinicle\BrinicleClient;
use Bicardinal\Brinicle\Models\HNSWParams;

// Create a client
$client = new BrinicleClient('http://localhost:1984');

// Create an index
$params = new HNSWParams(M: 48, efConstruction: 1024, efSearch: 512);
$client->createIndex('products', dim: 384, params: $params);

// Initialize and ingest
$client->init('products', 'build');
$client->ingest('products', 'p1', array_fill(0, 384, 0.1));
$client->ingest('products', 'p2', array_fill(0, 384, 0.2));
$client->finalize('products', optimize: true);

// Search
$results = $client->search('products', array_fill(0, 384, 0.1), k: 5);
print_r($results); // ['p1', 'p2']

Configuration

Constructor Options

$client = new BrinicleClient(
    baseUrl: 'http://localhost:1984',
    config: [
        'timeout' => 60,
        'connect_timeout' => 10,
        'verify' => true,   // SSL verification
        'headers' => [
            'X-Custom-Header' => 'value',
        ],
    ]
);
The config array is passed directly to the Guzzle HTTP client constructor, so any Guzzle-compatible option is supported.

API Reference

healthCheck

Check if the server is running and get the number of loaded indexes.
$response = $client->healthCheck();
// SuccessResponse { success: true, message: "Vector Engine API is running. 1 index(es) loaded." }

createIndex

Create a new vector index on the server.
$response = $client->createIndex(
    indexName: 'my_index',
    dim: 384,
    deltaRatio: 0.10,
    params: new HNSWParams(M: 48, efConstruction: 1024, efSearch: 512)
);

listIndexes

Get a list of all loaded indexes.
$response = $client->listIndexes();
// ListIndexesResponse { indexes: ['my_index'], count: 1 }

deleteIndex

Close or destroy an index.
// Close the index (data remains on disk)
$client->deleteIndex('my_index');

// Permanently destroy the index
$client->deleteIndex('my_index', destroy: true);

getIndexStatus

Check the status of an index.
$status = $client->getIndexStatus('my_index');
// IndexStatusResponse { indexName: 'my_index', dim: 384, hasIndex: true, needsRebuild: false }

loadIndex

Load an existing index from disk.
$client->loadIndex('my_index');

init

Initialize an ingest session.
$client->init('my_index', 'build');    // New index
$client->init('my_index', 'insert');   // Add to existing
$client->init('my_index', 'upsert');   // Add or update

ingest

Ingest a single vector.
$client->ingest('my_index', 'item_001', [0.1, 0.2, 0.3, 0.4]);

ingestBatch

Batch ingest using the binary protocol for maximum throughput.
$ids = ['item_001', 'item_002', 'item_003'];
$vectors = [
    [0.1, 0.2, 0.3, 0.4],
    [0.5, 0.6, 0.7, 0.8],
    [0.9, 1.0, 1.1, 1.2],
];
$response = $client->ingestBatch('my_index', $ids, $vectors, dim: 4);
// BatchIngestResponse { success: true, count: 3 }

finalize

Complete an ingest session.
$client->finalize('my_index', optimize: true);

deleteItems

Remove items from an index.
$response = $client->deleteItems('my_index', ['item_001', 'item_002'], returnNotFound: true);
// DeleteResponse { deletedCount: 2, notFound: [] }

rebuild

Perform a compact rebuild.
$client->rebuild('my_index', new HNSWParams(M: 48, efConstruction: 1024, efSearch: 512));
Search for nearest neighbors.
$results = $client->search('my_index', [0.1, 0.2, 0.3, 0.4], k: 10, efs: 64);
// ['item_001', 'item_042', 'item_137']

optimize

Optimize the HNSW graph.
$client->optimize('my_index');

ItemSearch

The ItemSearch API enables structured search over items with lexical fields, combining vector similarity with metadata filtering. Unlike the basic vector search which operates on raw embeddings, ItemSearch lets you ingest items with human-readable fields such as titles, descriptions, and categories, then perform filtered searches that combine full-text matching with vector similarity. This makes it ideal for product catalogs, document collections, and any dataset where you need to search by both content and structured attributes. Each item index is backed by its own vector index, so you still benefit from brinicle’s high-performance HNSW graph for similarity lookups. The difference is that ItemSearch adds a lexical configuration layer that controls how text fields are tokenized and matched, allowing you to fine-tune relevance for your specific domain.

createItemIndex

Create a new item search index with an optional lexical configuration. The lexicalConfig parameter controls how text fields are analyzed during ingestion and search — you can specify which fields are searchable, how they are tokenized (e.g., word boundaries, lowercasing), and whether stemming or stop-word removal should be applied. If omitted, a default configuration is used that treats all text fields as searchable with standard tokenization.
use Bicardinal\Brinicle\Models\LexicalConfig;

$lexicalConfig = new LexicalConfig(
    searchableFields: ['title', 'description', 'category'],
    tokenizer: 'standard',
    lowercase: true,
    stem: false,
    removeStopWords: true,
);

$response = $client->createItemIndex(
    indexName: 'products',
    dim: 384,
    deltaRatio: 0.10,
    params: new HNSWParams(M: 48, efConstruction: 1024, efSearch: 512),
    lexicalConfig: $lexicalConfig,
);

initItemIngest

Initialize an ingest session for an item index. This works identically to the vector engine’s init method, accepting a mode of 'build', 'insert', or 'upsert'. You must call this before ingesting any items, and you must call finalize after all items have been ingested. The build mode creates a fresh index from scratch, while insert and upsert modes allow you to add or update items in an existing index.
$client->initItemIngest('products', 'build');    // New index
$client->initItemIngest('products', 'insert');   // Add to existing
$client->initItemIngest('products', 'upsert');   // Add or update

ingestItem

Ingest a single item with its external ID, vector, and associated metadata fields. The item fields are arbitrary key-value pairs that will be indexed according to the lexical configuration specified when the index was created. Fields not listed in the searchableFields of the LexicalConfig are stored but not indexed for text search. The vector must match the dimension specified during index creation.
$response = $client->ingestItem(
    indexName: 'products',
    externalId: 'p1',
    vector: array_fill(0, 384, 0.1),
    fields: [
        'title' => 'Apple iPhone 15 Pro Max',
        'description' => 'Latest iPhone with A17 Pro chip',
        'category' => 'Electronics',
        'price' => '1199',
    ],
);

$response = $client->ingestItem(
    indexName: 'products',
    externalId: 'p2',
    vector: array_fill(0, 384, 0.2),
    fields: [
        'title' => 'Samsung Galaxy S24 Ultra',
        'description' => 'Flagship Android with AI features',
        'category' => 'Electronics',
        'price' => '1299',
    ],
);

searchItems

Search for items using a combination of vector similarity and structured filters. The query parameter provides the text query which is matched against the searchable fields defined in the lexical configuration. The filters parameter accepts structured conditions that narrow results by exact field values or ranges. You can control the number of results with k and the search beam width with efs for fine-grained recall/latency trade-offs.
use Bicardinal\Brinicle\Models\ItemSearchFilters;

// Simple text search
$results = $client->searchItems('products', query: 'iphone', k: 10);
// ItemSearchResponse { results: [...], total: 5 }

// Search with filters
$filters = new ItemSearchFilters(
    must: ['category' => 'Electronics'],
    mustNot: ['price' => ['0', '100']],
);

$results = $client->searchItems(
    indexName: 'products',
    query: 'smartphone',
    filters: $filters,
    k: 10,
    efs: 64,
);

deleteItemIndex

Delete or close an item search index. When called with destroy: false (the default), the index is closed but its data remains on disk and can be reloaded later. When called with destroy: true, the index and all its data are permanently removed. This method is identical in behavior to deleteIndex but operates on item search indexes specifically.
// Close the index (data remains on disk)
$client->deleteItemIndex('products');

// Permanently destroy the index
$client->deleteItemIndex('products', destroy: true);

getItemIndexStatus

Check the status of an item search index. Returns the index name, dimension, whether the HNSW graph has been built, and whether a rebuild is needed after recent ingestions. This is useful for monitoring the health of your indexes and determining when a rebuild or optimization might be necessary.
$status = $client->getItemIndexStatus('products');
// ItemIndexStatusResponse { indexName: 'products', dim: 384, hasIndex: true, needsRebuild: false }

LexicalConfig Model

The LexicalConfig model controls how text fields are analyzed and indexed for full-text search within an item index. Proper configuration of the lexical analyzer is critical for search quality — for example, enabling stemming allows searches for “running” to match documents containing “run”, while stop-word removal prevents common words like “the” and “and” from polluting the index.
use Bicardinal\Brinicle\Models\LexicalConfig;

$lexicalConfig = new LexicalConfig(
    searchableFields: ['title', 'description'],
    tokenizer: 'standard',
    lowercase: true,
    stem: true,
    removeStopWords: true,
);

// Convert to array for API requests
$array = $lexicalConfig->toArray();
// ['searchable_fields' => ['title', 'description'], 'tokenizer' => 'standard', ...]
FieldTypeDefaultDescription
searchableFieldsstring[][]Fields that should be indexed for text search
tokenizerstring'standard'Tokenizer to use (standard, whitespace, ngram)
lowercasebooltrueWhether to lowercase all tokens
stemboolfalseWhether to apply stemming to tokens
removeStopWordsboolfalseWhether to remove common stop words

Autocomplete

The Autocomplete API provides prefix-based search for type-ahead and suggestion use cases. Autocomplete indexes are optimized for fast prefix matching, making them ideal for search boxes, tag suggestion, and any interface where users type partial queries and expect instant results. Unlike ItemSearch which combines vector similarity with text matching, Autocomplete focuses on sub-millisecond prefix lookups over string keys. Each autocomplete index stores a set of string keys and their associated vectors. When a user types a partial query, the engine searches for keys that begin with the typed prefix and returns the closest matches ranked by vector similarity. This allows you to build autocomplete experiences that are both fast and semantically relevant — for example, typing “iph” could suggest “iPhone 15 Pro Max” based on both the prefix match and the semantic proximity to other electronics products.

createAutocompleteIndex

Create a new autocomplete index with an optional autocomplete configuration. The autocompleteConfig parameter controls how suggestions are matched and ranked — for example, you can set the minimum prefix length before suggestions are returned, the maximum number of suggestions, and whether to apply fuzzy matching for typo tolerance. If omitted, sensible defaults are used that work well for most use cases.
use Bicardinal\Brinicle\Models\AutocompleteConfig;

$autocompleteConfig = new AutocompleteConfig(
    minPrefixLength: 1,
    maxSuggestions: 10,
    fuzzyMatch: true,
    fuzzyDistance: 2,
);

$response = $client->createAutocompleteIndex(
    indexName: 'product_suggestions',
    dim: 384,
    deltaRatio: 0.10,
    params: new HNSWParams(M: 48, efConstruction: 1024, efSearch: 512),
    autocompleteConfig: $autocompleteConfig,
);

initAutocompleteIngest

Initialize an ingest session for an autocomplete index. Like the vector engine’s init method, this must be called before ingesting any autocomplete entries. The mode controls whether you are building a new index from scratch or adding entries to an existing one. After ingesting all entries, you must call finalize to commit the data and build the prefix index.
$client->initAutocompleteIngest('product_suggestions', 'build');
$client->initAutocompleteIngest('product_suggestions', 'insert');
$client->initAutocompleteIngest('product_suggestions', 'upsert');

ingestAutocomplete

Ingest a single autocomplete entry with its key string and associated vector. The key is the string that users will type as a prefix — for example, a product name like “iPhone 15 Pro Max”. The vector provides the semantic representation used to rank suggestions when multiple prefix matches exist. Keys should be lowercase if the AutocompleteConfig specifies lowercase: true, as the engine will not automatically normalize keys during ingestion.
$response = $client->ingestAutocomplete(
    indexName: 'product_suggestions',
    key: 'iphone 15 pro max',
    vector: array_fill(0, 384, 0.1),
);

$response = $client->ingestAutocomplete(
    indexName: 'product_suggestions',
    key: 'samsung galaxy s24 ultra',
    vector: array_fill(0, 384, 0.2),
);

searchAutocomplete

Search for autocomplete suggestions matching a given prefix. The query parameter is the partial string typed by the user, and the k parameter controls how many suggestions to return. Results are ranked by a combination of prefix match quality and vector similarity, ensuring that the most relevant suggestions appear first. This method is designed for sub-millisecond response times, making it suitable for interactive type-ahead interfaces.
$results = $client->searchAutocomplete(
    indexName: 'product_suggestions',
    query: 'iph',
    k: 5,
);
// ['iphone 15 pro max', 'iphone 15 case', 'iphone charger']

deleteAutocompleteIndex

Delete or close an autocomplete index. When called with destroy: false (the default), the index is closed but its data remains on disk. When called with destroy: true, the index and all associated data are permanently removed from disk. Use this method to clean up indexes that are no longer needed or to force a full rebuild by destroying and recreating an index.
// Close the index (data remains on disk)
$client->deleteAutocompleteIndex('product_suggestions');

// Permanently destroy the index
$client->deleteAutocompleteIndex('product_suggestions', destroy: true);

getAutocompleteIndexStatus

Check the status of an autocomplete index. Returns the index name, vector dimension, whether the prefix index has been built, and whether a rebuild is needed after recent ingestions. Monitoring index status is important for autocomplete indexes because the prefix index must be rebuilt after ingestion to reflect new entries.
$status = $client->getAutocompleteIndexStatus('product_suggestions');
// AutocompleteIndexStatusResponse { indexName: 'product_suggestions', dim: 384, hasIndex: true, needsRebuild: false }

AutocompleteConfig Model

The AutocompleteConfig model controls how the autocomplete engine matches prefixes and ranks suggestions. Tuning these parameters allows you to balance between suggestion speed and quality — for example, setting a higher minPrefixLength reduces the number of candidates the engine must evaluate for very short queries, while enabling fuzzyMatch allows the engine to suggest entries even when the user makes a typo.
use Bicardinal\Brinicle\Models\AutocompleteConfig;

$autocompleteConfig = new AutocompleteConfig(
    minPrefixLength: 1,
    maxSuggestions: 10,
    fuzzyMatch: true,
    fuzzyDistance: 2,
);

// Convert to array for API requests
$array = $autocompleteConfig->toArray();
FieldTypeDefaultDescription
minPrefixLengthint1Minimum characters before suggestions are returned
maxSuggestionsint10Maximum number of suggestions per query
fuzzyMatchboolfalseWhether to enable fuzzy matching for typos
fuzzyDistanceint2Maximum edit distance for fuzzy matches

Error Handling

use Bicardinal\Brinicle\Exceptions\BrinicleException;
use Bicardinal\Brinicle\Exceptions\ConnectionException;
use Bicardinal\Brinicle\Exceptions\ValidationException;

try {
    $client->createIndex('my_index', dim: 128);
} catch (ValidationException $e) {
    // Invalid parameters (HTTP 400)
    echo "Validation error: " . $e->getMessage();
} catch (ConnectionException $e) {
    // Could not connect to server
    echo "Connection error: " . $e->getMessage();
} catch (BrinicleException $e) {
    // Other API errors
    echo "Error ({$e->statusCode}): " . $e->getMessage();
}

HNSWParams Model

use Bicardinal\Brinicle\Models\HNSWParams;

// Default values
$params = new HNSWParams();
// M: 16, efConstruction: 200, efSearch: 64, rngSeed: 0

// Custom values
$params = new HNSWParams(
    M: 48,
    efConstruction: 1024,
    efSearch: 512,
    rngSeed: 42
);

// Convert to array
$array = $params->toArray();