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.
composer require symfony/ai symfony/ai-typesense-store typesense/typesense
config/typesense.php):
return [
'client' => [
'nodes' => ['http://localhost:8108'],
'api_key' => 'your-api-key',
'connection_timeout_seconds' => 2,
],
'collection' => 'your_collection_name',
'vector_dimension' => 384, // Match your embedding size
];
$this->app->bind(\Symfony\Contracts\Ai\StoreInterface::class, function ($app) {
$client = new \Typesense\Typesense(
$app['config']['typesense.client']
);
return new \Symfony\AI\TypesenseStore\TypesenseStore(
$client,
$app['config']['typesense.collection'],
$app['config']['typesense.vector_dimension']
);
});
use Symfony\Contracts\Ai\StoreInterface;
class ChatService {
public function __construct(private StoreInterface $store) {}
public function storeEmbedding(array $embedding, array $metadata) {
$this->store->add($embedding, $metadata);
}
public function findSimilar(array $embedding, int $limit = 5) {
return $this->store->find($embedding, $limit);
}
}
symfony/ai for method signatures (add(), find(), remove()).TypesenseStore class for advanced usage (e.g., filters, custom metrics).// Store embeddings with metadata
$store->add($embeddingArray, [
'document_id' => 'doc_123',
'source' => 'user_guide',
'category' => 'laravel'
]);
// Retrieve top-3 similar embeddings with filters
$results = $store->find($queryEmbedding, 3, [
'filter_by' => 'category:"laravel" AND source:"user_guide"',
'include_metadata' => true
]);
// Batch insert (Typesense supports bulk API)
$store->addMany([
[$embedding1, $metadata1],
[$embedding2, $metadata2],
]);
// Batch delete by metadata
$store->removeMany([
['document_id' => 'doc_123'],
['document_id' => 'doc_456']
]);
// Combine semantic and keyword search
$results = $store->find($embedding, 10, [
'filter_by' => 'category:"php" AND rating>4',
'query_by' => 'vector', // Default; can also use 'keyword'
]);
Service Container Binding:
// Bind with config overrides
$this->app->bind(StoreInterface::class, function ($app) {
return new TypesenseStore(
new TypesenseClient($app['config']['typesense.client']),
$app['config']['typesense.collection'],
$app['config']['typesense.vector_dimension'],
$app['config']['typesense.timeout'] ?? 5.0
);
});
Queue Jobs for Async Operations:
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class StoreEmbeddingJob implements ShouldQueue
{
use Queueable;
public function __construct(
private array $embedding,
private array $metadata
) {}
public function handle(StoreInterface $store) {
$store->add($this->embedding, $this->metadata);
}
}
Caching Layer:
// Cache frequent queries (e.g., top-5 recommendations)
$cacheKey = 'recommendations:' . $userId;
$results = Cache::remember($cacheKey, now()->addMinutes(10), function () use ($store, $embedding) {
return $store->find($embedding, 5);
});
Dynamic Collection Management:
// Create collection if it doesn’t exist
$client = new TypesenseClient($config);
$collection = $client->collections()->retrieve('your_collection');
if (!$collection) {
$client->collections()->create([
'name' => 'your_collection',
'fields' => [
['name' => 'vector', 'type' => 'float[]', 'facet' => false],
['name' => 'document_id', 'type' => 'string'],
['name' => 'category', 'type' => 'string'],
],
'default_sorting_field' => 'vector',
]);
}
Custom Metrics for Observability:
// Extend TypesenseStore to track query metrics
class InstrumentedTypesenseStore extends TypesenseStore
{
public function find(array $vector, int $limit = 10, array $options = []): array
{
$start = microtime(true);
$results = parent::find($vector, $limit, $options);
$duration = microtime(true) - $start;
// Log or send to APM
\Log::info('Typesense query', [
'duration_ms' => $duration * 1000,
'limit' => $limit,
'vector_dim' => count($vector),
]);
return $results;
}
}
Schema Migrations:
// Add a new field to the collection
$client->collections()->update('your_collection', [
'fields' => [
['name' => 'vector', 'type' => 'float[]'],
['name' => 'document_id', 'type' => 'string'],
['name' => 'updated_at', 'type' => 'int64'], // New field
],
]);
Vector Dimension Mismatch:
TypesenseStore constructor:
public function __construct(..., private int $expectedDimension) {
if (count($embedding) !== $expectedDimension) {
throw new \InvalidArgumentException("Vector dimension mismatch");
}
}
Filter Syntax Errors:
category:"laravel" vs. category:laravel) cause silent failures.typesense-filter-parser to sanitize filters.Rate Limiting:
use Symfony\Component\Process\Exception\ProcessFailedException;
try {
$client->collections()->retrieve($collectionName);
} catch (ProcessFailedException $e) {
if (str_contains($e->getMessage(), 'rate limit')) {
sleep(2); // Backoff
retry();
}
throw $e;
}
Metadata Field Types:
string → int) requires collection recreation.Embedding Normalization:
$normalized = array_map(function ($val) {
return $val / array_sum(array_map('pow', $embedding, $embedding));
}, $embedding);
$client = new TypesenseClient([
'nodes' => ['http://localhost:8108
How can I help you explore Laravel packages today?