symfony/ai-open-search-store
OpenSearch vector store integration for Symfony AI Store, providing k-NN vector search through OpenSearch's knn_vector field type and approximate k-NN query DSL.
StoreInterface, which is a clean abstraction for vector stores. While Laravel lacks native Symfony integration, this abstraction allows for minimal coupling if wrapped properly (e.g., via facade or service container). The core value—OpenSearch’s knn_vector—is stack-agnostic and aligns with Laravel’s need for scalable vector search.#2753) that bumps version constraints to the 0.15 line. There are no new features, no API surface changes to StoreInterface, and no deprecations. The architectural shape of the integration — thin Laravel wrapper over a Symfony store backed by OpenSearch — is unchanged.symfony/ai core 0.15. The store can no longer be upgraded in isolation: taking v0.15.0 drags the entire Symfony AI stack from 0.14.x to 0.15.x. Any behavioral change that matters to us will arrive via the core 0.15 release, not this diff. The review burden therefore shifts from symfony/ai-open-search-store to symfony/ai._bulk write-path fix remains in effect — failed bulk operations throw rather than silently producing partial index writes. This is still the most consequential behavioral property of the package for our ingestion pipelines, and it is unchanged by v0.15.0.StoreInterface can be adapted to Laravel with a custom wrapper (e.g., facade or service), hiding Symfony dependencies.knn_vector field, dimensionality) and performance tuning (e.g., engine for ANN).knn_vector is stable, but approximate NN trade-offs (speed vs. accuracy) must be benchmarked.symfony/ai-open-search-store:^0.15 plus symfony/ai:^0.15 — a coordinated two-package bump. This is mechanically simple but is not a drop-in patch in the way 0.14.0 → 0.14.1 was. It is a minor-version move on a pre-1.0 dependency, which by definition may carry behavior changes. Treat it as a change-window item, not a composer update reflex.knn_vector fields may require index reindexing.symfony/ai 0.14.x → 0.15.x, which we have not reviewed in this assessment. Before adopting, pull the core changelog and confirm no changes to StoreInterface, request serialization, or exception types.^0.14.1 indefinitely is a dead end — future store patches will target 0.15+. Conversely, jumping immediately forgoes the observation window. The correct posture is scheduled adoption, not reflexive or deferred adoption.StoreInterface without exposing Symfony to the rest of Laravel, or is a custom OpenSearch client (e.g., opensearchphp/opensearch) a better fit?0.15.*) and review each patch?knn_vector for the target embedding dimensionality (e.g., 768D) and query latency?knn_vector format?symfony/ai core changelog before we upgrade?symfony/process) or willing to isolate Symfony dependencies.Assess and Plan:
pgvector, Weaviate) for Laravel’s stack.Symfony Integration Strategy:
composer require symfony/ai:^0.15 symfony/ai-open-search-store:^0.15
^0.15 for the store is required to receive v0.15.0+; ^0.14.1 will no longer track upstream. If you are not ready for the core bump, stay pinned at symfony/ai:^0.14 + symfony/ai-open-search-store:0.14.1 deliberately, and record that as a known-frozen state.StoreInterface:
// app/Services/OpenSearchVectorStore.php
namespace App\Services;
use Symfony\Component\AI\Store\OpenSearchStore;
use OpenSearch\Client;
class OpenSearchVectorStore {
public function __construct(private Client $client) {}
public function nearest(array $vector, int $limit = 5) {
$store = new OpenSearchStore($this->client, 'vector_index');
return $store->nearest($vector, $limit);
}
}
AppServiceProvider:
public function register() {
$this->app->singleton(\App\Services\OpenSearchVectorStore::class, function ($app) {
return new \App\Services\OpenSearchVectorStore(
new \OpenSearch\Client([...])
);
});
}
opensearchphp/opensearch directly to avoid Symfony:
// app/Services/OpenSearchVectorStore.php
namespace App\Services;
use OpenSearch\Client;
class OpenSearchVectorStore {
public function __construct(private Client $client) {}
public function nearest(array $vector, int $limit = 5) {
$params = [
'index' => 'vector_index',
'body' => [
'query' => [
'knn' => [
'embedding' => [
'vector' => $vector,
'k' => $limit,
],
],
],
],
];
return $this->client->search($params);
}
}
OpenSearch Setup:
knn_vector field:
curl -X PUT "localhost:9200/vector_index" -H 'Content-Type: application/json' -d'
{
"settings": {
"index": {
"knn": true,
"knn.algo_param.ef_search": 100
}
},
"mappings": {
"properties": {
"embedding": {
"type": "knn_vector",
"dimension": 768
},
"metadata": {
"type": "object"
}
}
}
}'
knn.algo_param for approximate NN trade-offs (e.g., ef_search for speed vs. brute_force for accuracy).Laravel Service Integration:
use App\Services\OpenSearchVectorStore;
public function semanticSearch(Request $request) {
$embedding = $request->input('embedding');
$results = app(OpenSearchVectorStore::class)->nearest($embedding, 3);
return response()->json($results);
}
Bulk Write Hardening (required since v0.14.1):
try {
$store->bulk($documents);
} catch (\Throwable $e) {
Log::error('vector.bulk.failed', ['count' => count($documents), 'error' => $e->getMessage()]);
// requeue with backoff, or park for manual reconciliation
throw $e;
}
_id per document) so retries do not duplicate vectors.opensearch-php client, not the Laravel major. Validate against the app’s Laravel 10/11 LTS targets via the wrapper, not the package.symfony/ai:^0.15 + symfony/ai-open-search-store:^0.15symfony/ai:^0.14 + symfony/ai-open-search-store:0.14.1 (explicit freeze)symfony/ai:^0.14 with symfony/ai-open-search-store:^0.15 — the constraint bump exists precisely to prevent that skew.symfony/ai 0.15 — the core changelog for 0.15 is the authoritative source for behavioral change and must be reviewed before adoption. The store diff alone is not a sufficient basis for approving the upgrade.symfony/ai 0.15 release notes and diff. Identify any StoreInterface, serialization, or exception-type changes. Gate: no unresolved findings.^0.15 together; enable error logging; run a full ingestion against a staging index; run the wrapper’s test suite.StoreInterface change lands there and nowhere else. Assign a named owner. v0.15.0 reinforces the value of this seam: the version bump is a one-line composer change because the Symfony surface is confined.ef_search). Laravel-side scaling is trivial — the wrapper is stateless and horizontally scalable. Unchanged by v0.15.0.| Failure | Detection | Mitigation |
|---|---|---|
| OpenSearch cluster down | Health checks, query error rate | Local embedding cache; read-through fallback to keyword search |
| Bulk write partial failure (since v0.14.1) | Throws — exception logs, failed jobs | Idempotent retries, DLQ, reconciliation job |
| Silent index gaps (pre-0.14.1) | Largely eliminated by v0.14.1 | Maintain periodic document-count reconciliation against source of truth |
| Approximate NN quality drift | Precision@k monitoring, sampled human eval | Tune ef_search; escalate to brute-force for critical queries |
| Schema/mapping change | Deploy-time index validation | Alias-based blue/green reindex |
| Symfony AI API break on upgrade | CI test suite on the wrapper | Version pinning; wrapper absorbs change |
| Store/core version skew (new) | Composer solve failure, or runtime StoreInterface mismatch |
Pin both to the same minor; adopt as a coordinated pair |
| Core 0.15 behavior change (new) | Wrapper CI failures; staging ingestion/query diffs | Core changelog review gate before bump; rollback to frozen 0.14.1 |
| Embedding model version skew | Mismatch in vector dimensionality | Store model version in document metadata; reject mismatched dimensions |
Net effect of v0.15.0: This release is operationally inert — it adds no failure surface and removes none. Its significance is procedural: it converts a previously implicit rule ("keep the Symfony AI stack aligned") into a hard composer constraint. The v0.14.1 fix remains the last substantive change to system behavior; v0.15.0 only changes how we upgrade.
How can I help you explore Laravel packages today?