symfony/ai-redis-store
Redis vector store integration for Symfony AI Store. Index and query embeddings with Redis Search, using FT.CREATE and FT.SEARCH commands, KNN vector search, and DIALECT 2 querying for fast similarity lookups.
Architecture fit
symfony/ai-redis-store is a store implementation, not a framework-agnostic library. It sits inside Symfony AI's StoreInterface contract and translates store operations into Redis Query Engine calls: FT.CREATE for index definition and FT.SEARCH with KNN + DIALECT 2 for similarity retrieval. That is a correct, narrow design — the abstraction is thin and the failure surface is Redis, not PHP.symfony/ai-store (interfaces) plus this package via Composer, wrap instantiation in a Laravel service provider, and supply a Redis transport backed by Laravel's Redis facade (rawCommand on PhpRedis, executeRaw on Predis).VectorDocumentInterface and reimplement the ~few-hundred lines of FT.CREATE/FT.SEARCH glue directly. The package's durable value is the document contract, not the command strings.VectorStore interface and keep the Symfony package behind it. This is the single most important design decision: it makes the vendor package swappable and turns a potential framework lock-in into a replaceable adapter.VectorDocumentInterface (v0.14) is the seam that makes the Laravel bridge tractable — a small, stable target (id, vector, metadata) rather than mapping into undocumented Symfony internals. It also makes a per-backend adapter strategy realistic later (Redis today, pgvector/Qdrant tomorrow) without touching feature code.count capability (v0.14) is materially useful operationally: index-size and filtered counts without hydrating vectors. Maps to tenant quota enforcement, "N results" UX, and pre-flight guards before expensive KNN calls.Integration feasibility
symfony/* coexisting is normal. Expect no class collisions; expect a version-constraint negotiation against symfony/ai-store if we are pinned to an older Laravel.laravel-ai-redis-store, no service provider, no config file, no package discovery, no published docs for Laravel. Everything from config to connection lifecycle is ours to write.PhpRedisConnection/PredisConnection. Bridging them is a small but non-obvious adapter, and it is where most integration bugs will land (connection reuse, pipelining, error mapping, cluster routing).FT.CREATE/FT.SEARCH. Many managed Redis services (including several ElastiCache/Memorystore tiers and serverless variants) do not expose these commands. Verify against our actual target environment before writing code.Technical risk
clear() arrived in v0.11; VectorDocumentInterface in v0.14. Both useful, both proof that the API surface is still moving. Pin exact minors and read changelogs before every bump.symfony/ai monorepo with a real release cadence and review process, so abandonment risk is lower than the star count implies. The realistic risk is API drift, not project death.FT.SEARCH code is days, not months — provided we kept it behind our own interface.Key questions
FT.CREATE/FT.SEARCH KNN? (Blocking.)maxmemory-policy noeviction and a dedicated instance or logical separation.FT.CREATE time and changing them means a full rebuild.Stack fit
ext-redis (PhpRedis) or Predis. Symfony components are already in the dependency tree. Expect a clean composer require, then all the real work in our own app/VectorStore/ namespace.RedisVectorStoreServiceProvider — binds our interface to the Symfony store instance.LaravelRedisTransportAdapter — implements the Symfony Redis client contract over Laravel's connection; rawCommand/executeRaw passthrough.IndexSchemaProvisioner — an artisan command (vector:index:create|drop|info) so index DDL is a deploy step, not a request-time side effect.VectorStore facade + config file (config/vector-store.php) sourced from env: connection name, index name, dimensions, metric, algorithm.VectorStore contract.Migration path
composer require the package, prove FT.CREATE + KNN FT.SEARCH end-to-end from a Laravel command, with Laravel's Redis connection as transport. Deliverable: a go/no-go on transport bridging and a measured query latency on a 100k-vector synthetic corpus.VectorStore interface, the service provider, config, and the transport adapter. Contract tests (upsert / query / remove / clear / count) run against a containerized Redis in CI. Nothing in the app depends on the feature yet.count()-driven pagination or totals and a query timeout.count(), per-tenant quota enforcement, alert thresholds.VectorDocumentInterface genuinely decouples us.Compatibility
DIALECT 2 is required for KNN — confirm the server version supports it.symfony/ai-store and symfony/ai-redis-store to exact minors in composer.json, with a Renovate/Dependabot PR gate on changelog review.illuminate/* version conflict on the Symfony component constraints; if there is, the fallback is Phase 0's "contract-only" shape, so the decision is never fully blocked.allkeys-lru eviction on a vector workload.Sequencing rule Contracts → adapter → ingestion → query → observability → portability. Never invert the first two; the adapter behind a local interface is what makes every later decision reversible.
Recommendation to the steering group: approve as a bounded pilot (one feature, one index, ≤250k vectors) with an explicit 6-week review. The cost of trying is a few engineer-weeks; the cost of adopting broadly without the portability seam is a framework and vendor commitment we cannot unwind cheaply.
Maintenance
docs_v1 → docs_v2, dual-write, cut over, drop old) rather than an in-place change.Support
symfony/ai monorepo. Response quality will be good; response time is Symfony-volunteer-shaped, not SLA-shaped.M, EF_CONSTRUCTION, EF_RUNTIME), memory sizing, and index rebuild mechanics. Without that person, the first capacity incident becomes an outage.Scaling
vectors × dimensions × 4 bytes for float32, plus HNSW graph overhead (typically 30–60%), plus metadata and Redis overhead. Model this before committing to a corpus size.count() gives us the missing capacity primitive. Wire it into: a per-tenant quota check before ingest, a Prometheus/Grafana gauge for index size, and alert thresholds at 70% and 85% of projected ceiling. This turns capacity from a post-incident discovery into a leading indicator.Failure modes
| Failure | Symptom | Detection | Mitigation |
|---|---|---|---|
| Vector module absent on target Redis | FT.CREATE errors at deploy |
Deploy-time provisioner command fails fast | Version check in vector:index:create; block deploy |
| Index schema mismatch (dimension/metric change) | Errors on upsert, or silently wrong neighbors | CI contract test + dimension assertion on boot | Versioned index names; never mutate in place |
| Memory eviction of index data | Missing results, corrupt-looking index | Redis evicted_keys, INFO memory alarm |
Dedicated instance, noeviction policy |
| Shared instance with cache/session | Key collisions, FT.SEARCH scanning unrelated keys |
Prefix discipline audit | Separate DB/instance — non-negotiable |
| Unbounded KNN query | p99 latency spikes | Slow-query logging on FT.SEARCH |
LIMIT, TIMEOUT args, request-level query budget |
| Embedding provider outage | Ingestion queue backs up, corpus goes stale | Queue depth alert | Backoff, dead-letter, staleness metric surfaced to users |
| Upstream breaking change in a minor bump | Fatal errors after composer update |
Pinned versions + changelog PR gate | Exact-version pins; adapter isolation limits blast radius |
| Replication lag on read replicas | Stale search results post-ingest | Consistency probe in staging | Read-your-writes path to primary for critical flows |
Ramp-up
DIALECT 2, HNSW parameters); 2–3 days on the Symfony AI store contract and this package's source (it is small — reading it is faster than finding docs); the rest on the transport adapter and CI harness.How can I help you explore Laravel packages today?