symfony/ai-elasticsearch-store
Elasticsearch Store integrates Elasticsearch as a vector store for Symfony AI Store. It supports kNN vector search using dense_vector fields, enabling similarity search and retrieval over embeddings with Elasticsearch-backed indexing and querying.
Install the Package
composer require symfony/ai-elasticsearch-store
Configure Elasticsearch Client
Add the Elasticsearch client to your Laravel service container (e.g., in config/services.php or a custom config file):
'elasticsearch' => [
'hosts' => [
['host' => 'localhost', 'port' => 9200],
],
'index' => 'vector_store', // Your Elasticsearch index name
],
Register the Store
Bind the Elasticsearch store to Symfony AI’s StoreInterface in your service container (e.g., AppServiceProvider):
use Symfony\Component\AI\Store\StoreInterface;
use Symfony\Component\AI\ElasticsearchStore\ElasticsearchStore;
public function register()
{
$this->app->singleton(StoreInterface::class, function ($app) {
$client = new \Elasticsearch\Client($app['config']['elasticsearch']);
return new ElasticsearchStore($client, $app['config']['elasticsearch']['index']);
});
}
First Use Case: Storing and Retrieving Embeddings
use Symfony\Component\AI\Store\StoreInterface;
public function storeAndRetrieve(StoreInterface $store)
{
// Store an embedding with metadata
$store->add('doc1', [0.1, 0.2, 0.3], ['title' => 'Laravel AI', 'category' => 'framework']);
// Retrieve similar embeddings
$results = $store->nearest([0.15, 0.25, 0.35], 3);
// Filtered nearest search
$filteredResults = $store->nearest([0.15, 0.25, 0.35], 3, [
'filter' => ['term' => ['category' => 'framework']],
]);
}
Verify Elasticsearch Index
Ensure your Elasticsearch index has a dense_vector field. Example mapping:
PUT /vector_store
{
"mappings": {
"properties": {
"embedding": {
"type": "dense_vector",
"dims": 3 // Adjust to your embedding dimension
}
}
}
}
$store->add('unique_id', $embeddingArray, ['metadata' => 'value']);
$store->addAll([
'id1' => [$embedding1, ['category' => 'tech']],
'id2' => [$embedding2, ['category' => 'science']],
]);
$results = $store->nearest($queryEmbedding, $limit = 5, [
'filter' => ['term' => ['category' => 'tech']],
'knn' => true, // Enable approximate search for performance
]);
Returns an array of ['id' => string, 'embedding' => array, 'metadata' => array].Combine Elasticsearch’s query DSL with vector similarity:
$results = $store->nearest($queryEmbedding, 5, [
'filter' => [
'bool' => [
'must' => [
'term' => ['category' => 'books'],
'range' => ['price' => ['gte' => 10]],
],
],
],
]);
$store->remove('doc1');
$store->removeAll(['doc1', 'doc2']);
public function ragPipeline(StoreInterface $store, LLM $llm)
{
$queryEmbedding = $llm->embed("What is Laravel AI?");
$relevantDocs = $store->nearest($queryEmbedding, 3);
$context = implode("\n\n---\n\n", array_map(
fn($doc) => "Title: {$doc['metadata']['title']}\nContent: {$doc['metadata']['content']}",
$relevantDocs
));
return $llm->complete("Answer the question using only the context below:\n\n$context\n\nQuestion: What is Laravel AI?");
}
Service Container Binding Extend the store binding to include Laravel-specific features (e.g., caching):
$this->app->singleton(StoreInterface::class, function ($app) {
$client = new \Elasticsearch\Client($app['config']['elasticsearch']);
$store = new ElasticsearchStore($client, $app['config']['elasticsearch']['index']);
// Cache results for 5 minutes
Cache::remember("vector_store_{$query}", 300, function() use ($store, $query) {
return $store->nearest($query['embedding'], $query['limit'], $query['filter']);
});
return $store;
});
Queue Background Jobs Offload bulk operations to queues:
public function handleBulkInsert(BulkInsertRequest $request)
{
BulkInsertJob::dispatch($request->embeddings);
}
// BulkInsertJob.php
public function handle()
{
$store = app(StoreInterface::class);
$store->addAll($this->embeddings);
}
Event Listeners for Index Management Listen to model events to sync embeddings:
public function boot()
{
Document::saved(function ($document) {
$store = app(StoreInterface::class);
$embedding = $this->generateEmbedding($document->content);
$store->add($document->id, $embedding, $document->toArray());
});
}
Index Configuration Define a custom index with optimized settings:
$client->indices()->create([
'index' => 'vector_store',
'body' => [
'mappings' => [
'properties' => [
'embedding' => [
'type' => 'dense_vector',
'dims' => 384, // Adjust to your embedding dimension
],
'metadata' => [
'properties' => [
'title' => ['type' => 'text'],
'category' => ['type' => 'keyword'],
],
],
],
],
'settings' => [
'index' => [
'knn' => true,
'knn.algo_param.ef_search' => 100, // Optimize for search performance
],
],
],
]);
Sharding and Replication For large datasets, configure shards and replicas:
$client->indices()->putSettings([
'index' => 'vector_store',
'body' => [
'index.number_of_shards' => 3,
'index.number_of_replicas' => 1,
],
]);
Alias for Zero-Downtime Updates Use aliases to switch indices without downtime:
$client->indices()->createAlias(['index' => 'vector_store_v2', 'name' => 'vector_store']);
Index Mapping Mismatches
dense_vector field or using incorrect dimensions causes failures.$client->indices()->getMapping(['index' => 'vector_store']);
dims setting.Filter Syntax Errors
term vs match) returns no results.GET /vector_store/_search
{
"query": {
"bool": {
"filter": {
"term": { "category.keyword": "tech" }
}
}
}
}
keyword for exact matches (e.g., category.keyword) and text for full-text.Performance Bottlenecks
ef_search values.$params = [
'body' => [
How can I help you explore Laravel packages today?