mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-31 10:28:43 -04:00
* feat: add Valkey Search vector store backend Add a new built-in Go gRPC store backend 'valkey-store' that implements the four Stores RPCs (Set/Get/Delete/Find) against the Valkey Search module (FT.*) using the pure-Go github.com/valkey-io/valkey-go client. It is selected via the existing per-request 'backend' field on /stores, so there is no proto or HTTP API change, and it mirrors the in-memory local-store while adding persistence across restarts and opt-in HNSW. Each vector is a Valkey HASH keyed by hex(little-endian float32); the index is created lazily on first Set (FLAT+COSINE by default), cosine similarity is derived as 1-distance, and namespaces get a collision-resistant token. Includes unit tests (valkey-go mock) and env-gated integration tests against valkey/valkey-bundle, plus build/matrix/gallery wiring and docs. Assisted-by: Kiro:claude-opus-4.8 golangci-lint Signed-off-by: Daria Korenieva <daric2612@gmail.com> * Address review feedback: recover persisted index dimension, harden Find - Load now recovers the persisted vector DIM from FT.INFO (not just index existence), so a post-restart Set/Find validates against the real DIM instead of silently re-learning a wrong one and dropping mismatched vectors from the index. This also restores Find's dimension check after a restart. - StoresFind treats a dropped/missing index as an empty store (empty result, no error) and clears the stale indexCreated flag, matching local-store's empty-store behaviour. - StoresSet reuses checkDims for its per-key length check so the four RPCs share one dimension-guard implementation. - Add unit tests for FT.INFO dimension recovery, loadIndexState, and the dropped-index Find path. Assisted-by: Kiro:claude-opus-4.8 Signed-off-by: Daria Korenieva <daric2612@gmail.com> * Address review feedback: TLS ServerName/CA, Find nil-check, config fail-fast Addresses external review comments on the valkey-store backend: - StoresFind now rejects a nil/empty query Key before dereferencing it, so a malformed gRPC request can no longer panic the backend. - TLS: derive ServerName (SNI) from the VALKEY_ADDR host so certificate verification works for IP-addressed endpoints, and add VALKEY_TLS_CA_CERT (custom CA bundle) and VALKEY_TLS_SKIP_VERIFY (testing-only) knobs. - Config integer parsing now fails fast on a malformed value (e.g. VALKEY_HNSW_M=1x6) instead of silently defaulting, matching the fail-fast behaviour of the index-algo/distance-metric validation. - Add VALKEY_DB (SELECT n) support for logical-DB isolation. - Cap the human-readable part of a namespace token at 64 chars so a very long model name cannot produce an unbounded key prefix / index name (the appended short hash keeps distinct namespaces collision-free). - Document the KNN-query injection-safety invariant (fields are constants) and why StoresGet uses a single aggregate DoMulti deadline for reads. - Unit tests for the Find nil/empty-key guard, fail-fast HNSW parsing, and VALKEY_DB parsing/validation; docs + .env updated for the new vars. Assisted-by: Kiro:claude-opus-4.8 golangci-lint Signed-off-by: Daria Korenieva <daric2612@gmail.com> * Address review feedback: configure valkey-store via model config richiejp asked that the valkey-store backend take its configuration from a model config rather than process-wide VALKEY_* environment variables, so multiple stores can each have their own Valkey config within one LocalAI process. This removes every env access from the backend and routes config through the model-config seam every other backend uses. - config.go: loadConfig(opts *pb.ModelOptions) now parses the model config `options:` list (key:value strings, split on the first ':') instead of os.Getenv. Option keys mirror the old VALKEY_* names without the prefix (addr, index_algo, distance_metric, ...). Defaults, fail-fast validation and the mandatory client name are unchanged. - store.go: Load threads opts into loadConfig; TLS comments/errors renamed off the VALKEY_* names. - core/backend/stores.go: StoreBackend and NewVectorStore take a *config.ModelConfigLoader, resolve the per-store ModelConfig by store name, and pass its Options (and Backend when unset) to the backend via WithLoadGRPCLoadModelOpts. No config -> default backend + built-in defaults, preserving the zero-config experience. - Endpoints/routes/application: thread the config loader to StoreBackend. - Unit + integration tests: configure via options; the integration test passes addr through the model-config path (VALKEY_ADDR is now only the test harness locating the server). - docs + .env: document the model-config options, drop the env var table. Assisted-by: Kiro:claude-opus-4.8 Signed-off-by: Daria Korenieva <daric2612@gmail.com> * Remove valkey-store informational comment from .env The backend is configured via model config, not env vars — the comment was unnecessary noise in .env. The configuration is already documented in docs/content/features/stores.md. Signed-off-by: Daria Korenieva <daric2612@gmail.com> * feat(valkey-store): gate Load on NamespacePrefix to refuse autoload probing Mirror local-store's pattern: reject model names without store.NamespacePrefix so the model loader's greedy autoload probe cannot bind an arbitrary model name to the vector store backend (the #9287 failure mode). Also adds unit tests for the gate covering: prefixed namespace, prefix alone, unprefixed model name, empty model, and nil opts. Signed-off-by: Daria Korenieva <daric2612@gmail.com> * feat(valkey-store): add username_env/password_env credential indirection Add support for resolving Valkey credentials from environment variables named in the model config, mirroring cloud-proxy's api_key_env pattern. This keeps secrets out of model YAML files and lets distinct store configs each reference their own credentials. Options: username_env / password_env name the env var holding the value. The direct username / password options still work and take precedence when both are set (backward compatible). Includes 5 unit tests and updated stores.md documentation. Signed-off-by: Daria Korenieva <daric2612@gmail.com> * fix: correct rebase artifacts in backend-matrix.yml and Makefile Fix two issues introduced by the conflict-resolution script during the rebase onto master: 1. .github/backend-matrix.yml: valkey-store entries were merged INTO the cloud-proxy entries (duplicate keys in same YAML map items) instead of being separate list items. This broke cloud-proxy Linux builds and the cloud-proxy darwin entry lost its build-type/lang. Fixed by making them standalone entries and restoring cloud-proxy exactly as on master. 2. Makefile: duplicated .NOTPARALLEL and docker-build-backends lines. Collapsed to single lines that are master's current content plus the valkey-store additions. Also adds the three optional pickups from #10801: - /valkey-store in .gitignore (the built binary) - valkey-store row in docs/content/reference/compatibility-table.md - valkey-store line in backend/README.md Signed-off-by: Daria Korenieva <daric2612@gmail.com> --------- Signed-off-by: Daria Korenieva <daric2612@gmail.com> Co-authored-by: Daria Korenieva <daric2612@gmail.com>
204 lines
9.0 KiB
Markdown
204 lines
9.0 KiB
Markdown
|
|
+++
|
|
disableToc = false
|
|
title = "Stores"
|
|
weight = 62
|
|
url = '/stores'
|
|
+++
|
|
|
|
Stores are an experimental feature to help with querying data using similarity search. It is
|
|
a low level API that consists of only `get`, `set`, `delete` and `find`.
|
|
|
|
{{% notice tip %}}
|
|
**Face recognition uses this store.** The 1:N face identification flow
|
|
(`/v1/face/register`, `/v1/face/identify`, `/v1/face/forget`) is built
|
|
on top of the generic store - see
|
|
[Face Recognition](/features/face-recognition/) for the face-oriented
|
|
API.
|
|
{{% /notice %}}
|
|
|
|
For example if you have an embedding of some text and want to find text with similar embeddings.
|
|
You can create embeddings for chunks of all your text then compare them against the embedding of the text you
|
|
are searching on.
|
|
|
|
An embedding here meaning a vector of numbers that represent some information about the text. The
|
|
embeddings are created from an A.I. model such as BERT or a more traditional method such as word
|
|
frequency.
|
|
|
|
Previously you would have to integrate with an external vector database or library directly.
|
|
With the stores feature you can now do it through the LocalAI API.
|
|
|
|
Note however that doing a similarity search on embeddings is just one way to do retrieval. A higher level
|
|
API can take this into account, so this may not be the best place to start.
|
|
|
|
## API overview
|
|
|
|
There is an internal gRPC API and an external facing HTTP JSON API. We'll just discuss the external HTTP API,
|
|
however the HTTP API mirrors the gRPC API. Consult `pkg/store/client` for internal usage.
|
|
|
|
Everything is in columnar format meaning that instead of getting an array of objects with a key and a value each.
|
|
You instead get two separate arrays of keys and values.
|
|
|
|
Keys are arrays of floating point numbers with a maximum width of 32bits. Values are strings (in gRPC they are bytes).
|
|
|
|
The key vectors must all be the same length and it's best for search performance if they are normalized. When
|
|
addings keys it will be detected if they are not normalized and what length they are.
|
|
|
|
All endpoints accept a `store` field which specifies which store to operate on. Stores are created
|
|
on the fly. By default the in-memory `local-store` backend is used and no configuration is required,
|
|
but you can select a different store backend per request (see [Backends](#backends) below).
|
|
|
|
## Backends
|
|
|
|
Each `/stores/*` request accepts an optional `backend` field selecting the store implementation.
|
|
Two backends ship with LocalAI:
|
|
|
|
| Backend | `backend` value | Persistence | Notes |
|
|
|---------|-----------------|-------------|-------|
|
|
| Local (default) | `local-store` (alias `embedded-store`) | In-memory, lost on restart | Exact cosine similarity, zero configuration. |
|
|
| Valkey Search | `valkey-store` (alias `valkey`) | Durable (Valkey RDB/AOF) | Backed by a Valkey Search (`FT.*`) server; survives restarts and supports opt-in HNSW. |
|
|
|
|
### Valkey store backend
|
|
|
|
The `valkey-store` backend persists vectors in a [Valkey Search](https://valkey.io/) server, so
|
|
the data survives a LocalAI restart — unlike the in-memory default. It requires a reachable server
|
|
that ships the Valkey Search module (for example the `valkey/valkey-bundle` image).
|
|
|
|
Select it by passing `"backend": "valkey-store"` (or the `"valkey"` alias) on any `/stores/*` request:
|
|
|
|
```
|
|
curl -X POST http://localhost:8080/stores/set \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"backend": "valkey-store", "store": "my-vectors", "keys": [[0.1, 0.2], [0.3, 0.4]], "values": ["foo", "bar"]}'
|
|
```
|
|
|
|
The connection and index are configured through a **model config** named after the store (the
|
|
`store` field on the request, which is the store's model ID). Create a YAML in your models
|
|
directory whose `name` matches the store, set `backend: valkey-store`, and put the connection /
|
|
index settings in the `options:` list as `key:value` strings. Because each store resolves its own
|
|
config, different stores can point at different Valkey servers or use different index settings
|
|
within one LocalAI process:
|
|
|
|
```yaml
|
|
name: my-vectors
|
|
backend: valkey-store
|
|
options:
|
|
- addr:valkey.internal:6379
|
|
- username_env:MY_VALKEY_USER
|
|
- password_env:MY_VALKEY_PASSWORD
|
|
- index_algo:HNSW
|
|
- distance_metric:COSINE
|
|
```
|
|
|
|
The `username_env` / `password_env` options name an environment variable that holds the actual
|
|
credential rather than putting the secret directly in the YAML. This mirrors `cloud-proxy`'s
|
|
`api_key_env` pattern and lets distinct store configs each reference their own credentials.
|
|
The direct `username` / `password` options still work for backward compatibility, and take
|
|
precedence when both are set.
|
|
|
|
When no config exists for a store, the backend connects to `localhost:6379` with the defaults
|
|
below (so the zero-config experience still works).
|
|
|
|
| Option | Default | Description |
|
|
|--------|---------|-------------|
|
|
| `addr` | `localhost:6379` | Valkey server address (`host:port`). |
|
|
| `username` | *(empty)* | Optional ACL username (plaintext in config). |
|
|
| `password` | *(empty)* | Optional password / ACL secret (plaintext in config). |
|
|
| `username_env` | *(empty)* | Name of an env var that holds the username. Preferred over `username` for secrets — keeps credentials out of the model YAML. |
|
|
| `password_env` | *(empty)* | Name of an env var that holds the password. Preferred over `password` for secrets. |
|
|
| `tls` | `false` | Enable TLS (required by many managed deployments). |
|
|
| `tls_ca_cert` | *(empty)* | Path to a PEM CA bundle used to verify the server certificate (self-signed / private CA). |
|
|
| `tls_skip_verify` | `false` | Skip TLS certificate verification. Insecure — for testing only. |
|
|
| `client_name` | `localai-valkey-store` | Connection name reported by `CLIENT LIST`. Always set. |
|
|
| `db` | `0` | Logical Valkey DB index (`SELECT n`). Namespace prefixing already isolates keyspaces on a shared DB. |
|
|
| `index_algo` | `FLAT` | `FLAT` (exact, default) or `HNSW` (approximate ANN for large corpora). |
|
|
| `hnsw_m` | `16` | HNSW graph degree (only when `index_algo:HNSW`). |
|
|
| `hnsw_ef_construction` | `200` | HNSW build-time candidate list (HNSW only). |
|
|
| `hnsw_ef_runtime` | `10` | HNSW query-time candidate list (HNSW only). |
|
|
| `distance_metric` | `COSINE` | `COSINE` (default), `L2` or `IP`. |
|
|
| `request_timeout_ms` | `5000` | Per-command timeout in milliseconds. |
|
|
|
|
For `COSINE` the returned `similarities` follow the same convention as the local store
|
|
(`1.0` = identical, `-1.0` = opposite); internally Valkey returns a cosine *distance* which the
|
|
backend converts with `similarity = 1 - distance`. For `L2` and `IP` the raw Valkey score is
|
|
returned in `similarities` (for `L2`, smaller means closer — the opposite ordering of `COSINE`);
|
|
nearest-first ordering of the results is preserved in all cases.
|
|
|
|
{{% notice note %}}
|
|
Valkey Search updates its vector index **asynchronously** after a write. A `find` issued
|
|
immediately after `set` may not yet see the new vectors — poll `find` briefly (or retry) until the
|
|
expected results appear. `get` and `delete` are synchronous and unaffected.
|
|
{{% /notice %}}
|
|
|
|
{{% notice note %}}
|
|
This backend targets a **standalone** Valkey Search server (one server per namespace/model). Valkey
|
|
Cluster is not a supported target yet — index coordination across shards is out of scope for this
|
|
backend.
|
|
{{% /notice %}}
|
|
|
|
{{% notice warning %}}
|
|
`tls` defaults to `false` (plaintext). Set `tls:true` whenever the Valkey server is
|
|
not on `localhost` or a `password`/`username` is configured, otherwise the credentials
|
|
and the stored vectors travel the network unencrypted. The TLS `ServerName` (SNI) is derived from
|
|
the host portion of `addr`, so certificate verification works for both hostname and
|
|
IP-addressed endpoints. For a self-signed / private CA, point `tls_ca_cert` at the PEM
|
|
bundle; `tls_skip_verify:true` disables verification entirely and should only be used for
|
|
local testing.
|
|
{{% /notice %}}
|
|
|
|
## Set
|
|
|
|
To set some keys you can do
|
|
|
|
```
|
|
curl -X POST http://localhost:8080/stores/set \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"keys": [[0.1, 0.2], [0.3, 0.4]], "values": ["foo", "bar"]}'
|
|
```
|
|
|
|
Setting the same keys again will update their values.
|
|
|
|
On success 200 OK is returned with no body.
|
|
|
|
## Get
|
|
|
|
To get some keys you can do
|
|
|
|
```
|
|
curl -X POST http://localhost:8080/stores/get \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"keys": [[0.1, 0.2]]}'
|
|
```
|
|
|
|
Both the keys and values are returned, e.g: `{"keys":[[0.1,0.2]],"values":["foo"]}`
|
|
|
|
The order of the keys is not preserved! If a key does not exist then nothing is returned.
|
|
|
|
## Delete
|
|
|
|
To delete keys and values you can do
|
|
|
|
```
|
|
curl -X POST http://localhost:8080/stores/delete \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"keys": [[0.1, 0.2]]}'
|
|
```
|
|
|
|
If a key doesn't exist then it is ignored.
|
|
|
|
On success 200 OK is returned with no body.
|
|
|
|
## Find
|
|
|
|
To do a similarity search you can do
|
|
|
|
```
|
|
curl -X POST http://localhost:8080/stores/find
|
|
-H "Content-Type: application/json" \
|
|
-d '{"topk": 2, "key": [0.2, 0.1]}'
|
|
```
|
|
|
|
`topk` limits the number of results returned. The result value is the same as `get`,
|
|
except that it also includes an array of `similarities`. Where `1.0` is the maximum similarity.
|
|
They are returned in the order of most similar to least.
|