symfony/ai-typesense-store
Typesense Store integrates the Typesense vector database with Symfony AI Store, enabling vector indexing and similarity search via Typesense’s vector search API. Part of the Symfony AI ecosystem, with issues and PRs handled in the main Symfony AI repo.
Install Dependencies:
composer require symfony/ai-typesense-store typesense/typesense
Ensure symfony/ai (≥v0.8.0) is also installed.
Configure Typesense Client:
Create a Typesense client instance (e.g., in config/typesense.php):
return [
'nodes' => ['http://typesense.example.com:8108'],
'api_key' => env('TYPESENSE_API_KEY'),
'connection_timeout_seconds' => 2,
];
First Use Case: Semantic Search Initialize the store and query embeddings:
use Symfony\AI\Store\StoreInterface;
use Symfony\AI\TypesenseStore\TypesenseStore;
use Typesense\Typesense;
$client = new Typesense(config('typesense.nodes'), config('typesense.api_key'));
$store = new TypesenseStore($client, 'products'); // 'products' = collection name
// Add a vector (e.g., from an embedding)
$store->add($embeddingVector, ['id' => 123, 'name' => 'Smartphone']);
// Query similar vectors
$results = $store->find(
(new Query())->setVector($queryEmbedding)->setLimit(5)
);
Vector Storage/Retrieval:
// Store
$store->add($vector, $metadata);
// Retrieve (with filtering)
$results = $store->find(
(new Query())
->setVector($queryVector)
->setFilter('category: "electronics"')
->setLimit(10)
);
Batching for Efficiency: Use bulk operations to reduce API calls:
$store->addMany([
[$vector1, $metadata1],
[$vector2, $metadata2],
]);
Dynamic Collection Management: Create collections on-the-fly (e.g., per-tenant):
$client->collections()->create('tenant_1_products', [
'fields' => [
['name' => 'vector', 'type' => 'float[]', 'facet' => false],
['name' => 'id', 'type' => 'int32'],
],
]);
Laravel Service Provider: Bind the store to the container for dependency injection:
public function register()
{
$this->app->singleton(StoreInterface::class, function ($app) {
$client = new Typesense(config('typesense.nodes'), config('typesense.api_key'));
return new TypesenseStore($client, config('typesense.collection'));
});
}
Query Builder Pattern: Chain methods for complex queries:
$query = (new Query())
->setVector($embedding)
->setFilter('price > 50 AND stock > 0')
->setLimit(5)
->setIncludeMetadata(true);
Error Handling: Wrap store operations in try-catch blocks:
try {
$results = $store->find($query);
} catch (TypesenseException $e) {
Log::error("Typesense query failed: " . $e->getMessage());
// Fallback logic (e.g., return cached results)
}
Hybrid Search:
Combine keyword and vector search using Typesense’s query_by:
$query->setQueryBy('vector', $embedding);
$query->setQueryBy('text', 'smartphone'); // Fallback to keyword
Schema Mismatches:
vector, id) will fail.$client->collections()->create('dynamic_collection', [
'fields' => [
['name' => 'vector', 'type' => 'float[]'],
['name' => 'metadata', 'type' => 'json'],
],
]);
Vector Dimension Limits:
if (count($embedding) > 100000) {
throw new \InvalidArgumentException('Embedding exceeds Typesense dimension limit.');
}
Filter Syntax:
$query->setFilter('category: "electronics" AND price: >50'); // Note: `>` requires space
Connection Timeouts:
$client = new Typesense(config('typesense.nodes'), config('typesense.api_key'), [
'connection_timeout_seconds' => 5,
'read_timeout_seconds' => 10,
]);
Metadata Size Limits:
$store->add($embedding, [
'id' => $id,
'name' => $name,
// Avoid storing large blobs here; use external references instead
]);
Enable Logging: Configure the Typesense client to log requests/responses:
$client = new Typesense(config('typesense.nodes'), config('typesense.api_key'), [
'log_level' => \Monolog\Logger::DEBUG,
]);
Query Validation: Use Typesense’s API playground to test queries before implementing them in code.
Performance Profiling: Monitor query latency with:
$start = microtime(true);
$results = $store->find($query);
$latency = microtime(true) - $start;
Log::debug("Query latency: {$latency}s");
Custom Distance Metrics: Extend the store to support non-Euclidean distances (e.g., cosine similarity):
class CustomTypesenseStore extends TypesenseStore {
public function __construct(Typesense $client, string $collection, string $distanceMetric = 'euclidean') {
parent::__construct($client, $collection);
$this->distanceMetric = $distanceMetric;
}
protected function buildQuery(Query $query): array {
return [
'vector' => $query->getVector(),
'distance_metric' => $this->distanceMetric,
// ... other params
];
}
}
Batch Processing: Implement chunked operations for large datasets:
public function addBatch(array $vectors, array $metadatas, int $chunkSize = 100) {
foreach (array_chunk($vectors, $chunkSize) as $i => $chunk) {
$this->addMany(array_map(null, $chunk, array_chunk($metadatas, $chunkSize)));
}
}
Fallback Mechanisms: Decorate the store to handle failures gracefully:
class FallbackTypesenseStore implements StoreInterface {
public function __construct(private StoreInterface $store, private StoreInterface $fallback) {}
public function find(Query $query) {
try {
return $this->store->find($query);
} catch (Exception $e) {
Log::warning("Typesense fallback triggered: " . $e->getMessage());
return $this->fallback->find($query);
}
}
}
Dynamic Collection Routing: Route vectors to collections based on metadata:
public function add($vector, $metadata) {
$collection = $metadata['tenant_id'] ?? 'default';
$store = new TypesenseStore($this->client, $
How can I help you explore Laravel packages today?