symfony/ai-open-search-store
OpenSearch vector store integration for Symfony AI Store. Index and query embeddings using OpenSearch knn_vector fields and k‑NN/approximate k‑NN search. Links to OpenSearch docs and contribution resources in the main Symfony AI repo.
composer require symfony/ai-open-search-store symfony/ai symfony/http-client opensearch/opensearch
config/opensearch.php:
return [
'client' => OpenSearch\ClientBuilder::create()
->setHosts(['http://localhost:9200'])
->build(),
];
knn_vector field (e.g., via opensearch-php client or API):
curl -X PUT "localhost:9200/vector_index" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"embedding": { "type": "knn_vector", "dimension": 768 }
}
}
}'
OpenSearchStore to fetch nearest vectors:
use Symfony\Component\AI\Store\OpenSearchStore;
use OpenSearch\Client;
$client = config('opensearch.client');
$store = new OpenSearchStore($client, 'vector_index');
$results = $store->nearest([0.1, 0.2, ...], limit: 5); // Replace with actual embedding
OpenSearchStore.php for implementation details.Semantic Search:
symfony/ai or Hugging Face).$store->add('doc_id', ['embedding' => $embeddingArray]);
$similarDocs = $store->nearest($queryEmbedding, limit: 3);
$store->add('id_123', ['embedding' => $vector, 'metadata' => ['title' => 'Doc']]);
$store->remove('id_123'); // Requires OpenSearch 2.4+
$results = $store->nearest($vector, limit: 5);
$results = $store->nearest($vector, limit: 5, filter: [
'term' => ['category' => 'tech']
]);
engine in the index (e.g., hnsw):
"embedding": {
"type": "knn_vector",
"dimension": 768,
"method": { "name": "hnsw", "space_type": "l2", "engine": "lucene" }
}
Combine keyword and vector queries:
$client->search([
'index' => 'vector_index',
'body' => [
'query' => [
'bool' => [
'must' => [
'knn' => ['embedding' => ['vector' => $vector, 'k' => 5]],
'match' => ['title' => 'AI']
]
]
]
]
]);
symfony/ai or a custom model (e.g., SentenceTransformer):
$embedding = $model->embed('Your text here');
$store->add('doc_id', ['embedding' => $embedding->toArray()]);
$context = $store->nearest($queryEmbedding, limit: 3);
Use Laravel’s scheduling to refresh embeddings:
// app/Console/Commands/RefreshEmbeddings.php
public function handle() {
$docs = Document::all();
foreach ($docs as $doc) {
$embedding = $model->embed($doc->content);
$store->add($doc->id, ['embedding' => $embedding->toArray()]);
}
}
Schedule:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule) {
$schedule->command('refresh:embeddings')->daily();
}
Bind the store as a singleton:
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->singleton(\Symfony\Component\AI\Store\OpenSearchStore::class, function ($app) {
return new \Symfony\Component\AI\Store\OpenSearchStore(
$app['opensearch.client'],
'vector_index'
);
});
}
Create a fluent query builder:
// app/Services/OpenSearchQueryBuilder.php
class OpenSearchQueryBuilder {
public function nearest(array $vector, int $limit = 5): array {
return $this->store->nearest($vector, $limit);
}
public function withFilter(array $filter): self {
$this->filter = $filter;
return $this;
}
}
Wrap operations in try-catch:
try {
$results = $store->nearest($vector);
} catch (\OpenSearch\Common\Exceptions\ClientException $e) {
Log::error('OpenSearch query failed', ['error' => $e->getMessage()]);
return [];
}
vector-search plugin installed:
bin/opensearch-plugin install analysis-icu
bin/opensearch-plugin install vector-search
knn_vector fields require dimension and optionally method (e.g., hnsw). Mismatched dimensions cause errors.StoreInterface may conflict with Laravel’s Store facade. Use aliases:
'aliases' => [
'SymfonyStore' => \Symfony\Component\AI\Store\StoreInterface::class,
],
vendor/symfony/ai is not excluded from composer.json autoload.hnsw is faster but less precise than brute-force. Test with your data:
"method": { "name": "hnsw", "space_type": "l2", "engine": "lucene", "parameters": { "ef_construction": 128, "m": 24 } }
hnsw struggles with >1000D vectors. Use PCA or dimensionality reduction if needed.use Symfony\Component\Process\Exception\ProcessFailedException;
try {
$results = $store->nearest($vector);
} catch (ProcessFailedException $e) {
sleep(2 ** $attempt++); // Exponential backoff
retry();
}
| Error | Cause | Solution |
|---|---|---|
Invalid dimension |
Vector size mismatch in index definition. | Recreate index with correct dimension. |
No mapping for [field] |
Field not defined in index mappings. | Add field to index mappings. |
knn query not supported |
Missing vector search plugin. | Install vector-search plugin. |
Symfony\Component\AI\Exception\StoreException |
Invalid query syntax. | Check OpenSearch query DSL docs |
How can I help you explore Laravel packages today?