diff --git a/llama_stack/core/datatypes.py b/llama_stack/core/datatypes.py index 15e2445e5..4ba2864ec 100644 --- a/llama_stack/core/datatypes.py +++ b/llama_stack/core/datatypes.py @@ -650,6 +650,30 @@ reference them from multiple stores. If not specified, default SQLite stores wil return Path(v) return v + @model_validator(mode="after") + def resolve_provider_backend_references(self) -> Self: + """Resolve backend references in provider kvstore configs.""" + if not self.persistence or not self.persistence.backends: + return self + + from llama_stack.core.provider_config_resolver import resolve_provider_kvstore_references + + # Convert providers to dict format for resolution + providers_dict = {} + for api, provider_list in self.providers.items(): + providers_dict[api] = [p.model_dump() for p in provider_list] + + # Resolve backend references + resolved_providers = resolve_provider_kvstore_references( + providers_dict, self.persistence.backends + ) + + # Convert back to Provider objects + for api, provider_list in resolved_providers.items(): + self.providers[api] = [Provider(**p) for p in provider_list] + + return self + class BuildConfig(BaseModel): version: int = LLAMA_STACK_BUILD_CONFIG_VERSION diff --git a/llama_stack/core/provider_config_resolver.py b/llama_stack/core/provider_config_resolver.py new file mode 100644 index 000000000..0a6f63ba4 --- /dev/null +++ b/llama_stack/core/provider_config_resolver.py @@ -0,0 +1,68 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from typing import Any + + +def resolve_provider_kvstore_references( + providers: dict[str, list[dict[str, Any]]], + persistence_backends: dict[str, Any], +) -> dict[str, list[dict[str, Any]]]: + """ + Resolve backend references in provider kvstore configs to actual backend configs. + + This allows providers to reference centralized persistence backends instead of + duplicating kvstore configs. + + Example: + # Provider config with backend reference + kvstore: + backend: kvstore + namespace: faiss + + # Gets resolved to actual backend config + kvstore: + type: sqlite + db_path: /path/to/kvstore.db + namespace: faiss + """ + for api, provider_list in providers.items(): + for provider in provider_list: + config = provider.get("config", {}) + _resolve_kvstore_in_dict(config, persistence_backends) + + return providers + + +def _resolve_kvstore_in_dict(config: dict[str, Any], backends: dict[str, Any]) -> None: + """Recursively find and resolve backend references in config dict.""" + # Store keys that typically contain backend references + store_keys = {"kvstore", "metadata_store", "persistence_store", "responses_store"} + + for key, value in list(config.items()): + if key in store_keys and isinstance(value, dict): + # Check if it's a backend reference + if "backend" in value: + backend_name = value["backend"] + namespace = value.get("namespace") + + if backend_name not in backends: + raise ValueError( + f"Provider references backend '{backend_name}' which is not defined in persistence.backends" + ) + + # Clone the backend config and apply namespace + backend_config = backends[backend_name] + resolved_config = backend_config.model_dump() if hasattr(backend_config, "model_dump") else dict(backend_config) + + if namespace: + resolved_config["namespace"] = namespace + + config[key] = resolved_config + + elif isinstance(value, dict): + # Recursively process nested dicts + _resolve_kvstore_in_dict(value, backends) diff --git a/llama_stack/distributions/ci-tests/run.yaml b/llama_stack/distributions/ci-tests/run.yaml index 1cea8b2e9..625f92ec9 100644 --- a/llama_stack/distributions/ci-tests/run.yaml +++ b/llama_stack/distributions/ci-tests/run.yaml @@ -14,7 +14,7 @@ apis: - tool_runtime - vector_io providers: - inference: + inference: - provider_id: ${env.CEREBRAS_API_KEY:+cerebras} provider_type: remote::cerebras config: @@ -95,29 +95,29 @@ providers: provider_type: inline::faiss config: kvstore: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/faiss_store.db + backend: kvstore + namespace: faiss - provider_id: sqlite-vec provider_type: inline::sqlite-vec config: db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/sqlite_vec.db kvstore: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/sqlite_vec_registry.db + backend: kvstore + namespace: sqlite_vec - provider_id: ${env.MILVUS_URL:+milvus} provider_type: inline::milvus config: db_path: ${env.MILVUS_DB_PATH:=~/.llama/distributions/ci-tests}/milvus.db kvstore: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/milvus_registry.db + backend: kvstore + namespace: milvus - provider_id: ${env.CHROMADB_URL:+chromadb} provider_type: remote::chromadb config: url: ${env.CHROMADB_URL:=} kvstore: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests/}/chroma_remote_registry.db + backend: kvstore + namespace: chromadb - provider_id: ${env.PGVECTOR_DB:+pgvector} provider_type: remote::pgvector config: @@ -127,16 +127,16 @@ providers: user: ${env.PGVECTOR_USER:=} password: ${env.PGVECTOR_PASSWORD:=} kvstore: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/pgvector_registry.db + backend: kvstore + namespace: pgvector files: - provider_id: meta-reference-files provider_type: inline::localfs config: storage_dir: ${env.FILES_STORAGE_DIR:=~/.llama/distributions/ci-tests/files} metadata_store: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/files_metadata.db + backend: kvstore + namespace: files safety: - provider_id: llama-guard provider_type: inline::llama-guard @@ -149,11 +149,11 @@ providers: provider_type: inline::meta-reference config: persistence_store: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/agents_store.db + backend: sqlstore + namespace: agents responses_store: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/responses_store.db + backend: sqlstore + namespace: responses telemetry: - provider_id: meta-reference provider_type: inline::meta-reference @@ -172,21 +172,21 @@ providers: provider_type: inline::meta-reference config: kvstore: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/meta_reference_eval.db + backend: kvstore + namespace: eval datasetio: - provider_id: huggingface provider_type: remote::huggingface config: kvstore: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/huggingface_datasetio.db + backend: kvstore + namespace: huggingface - provider_id: localfs provider_type: inline::localfs config: kvstore: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/localfs_datasetio.db + backend: kvstore + namespace: localfs_datasetio scoring: - provider_id: basic provider_type: inline::basic @@ -216,18 +216,21 @@ providers: provider_type: inline::reference config: kvstore: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/batches.db + backend: kvstore + namespace: batches persistence: backends: - default: + kvstore: type: sqlite - db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/registry.db + db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/kvstore.db + sqlstore: + type: sqlite + db_path: ${env.SQLITE_STORE_DIR:=~/.llama/distributions/ci-tests}/sqlstore.db stores: metadata: - backend: default + backend: kvstore inference: - backend: default + backend: sqlstore models: [] shields: - shield_id: llama-guard