From c4cb6aa8d99ad94afd2f18c0ba69a40cb09c0080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Fri, 8 Aug 2025 15:54:45 +0200 Subject: [PATCH 1/4] fix: prevent telemetry from leaking sensitive info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent sensitive information from being logged in telemetry output by assigning SecretStr type to sensitive fields. API keys, password from KV store are now covered. All providers have been converted. Signed-off-by: Sébastien Han --- docs/docs/providers/files/remote_s3.mdx | 2 +- .../providers/inference/remote_anthropic.mdx | 2 +- .../providers/inference/remote_bedrock.mdx | 4 ++-- .../providers/inference/remote_gemini.mdx | 2 +- docs/docs/providers/inference/remote_groq.mdx | 2 +- .../inference/remote_llama-openai-compat.mdx | 2 +- .../providers/inference/remote_openai.mdx | 2 +- .../providers/inference/remote_runpod.mdx | 2 +- docs/docs/providers/inference/remote_vllm.mdx | 2 +- docs/docs/providers/safety/remote_bedrock.mdx | 4 ++-- .../providers/scoring/inline_braintrust.mdx | 2 +- .../tool_runtime/remote_bing-search.mdx | 2 +- .../tool_runtime/remote_brave-search.mdx | 2 +- .../tool_runtime/remote_tavily-search.mdx | 2 +- .../providers/vector_io/remote_pgvector.mdx | 2 +- .../inline/scoring/braintrust/__init__.py | 4 ++-- .../inline/scoring/braintrust/braintrust.py | 6 ++--- .../inline/scoring/braintrust/config.py | 4 ++-- .../meta_reference/console_span_processor.py | 4 +++- .../providers/remote/files/s3/config.py | 4 ++-- .../providers/remote/files/s3/files.py | 2 +- .../remote/inference/anthropic/anthropic.py | 3 ++- .../remote/inference/anthropic/config.py | 6 ++--- .../remote/inference/gemini/config.py | 6 ++--- .../remote/inference/gemini/gemini.py | 3 ++- .../providers/remote/inference/groq/config.py | 6 ++--- .../inference/llama_openai_compat/config.py | 6 ++--- .../remote/inference/openai/config.py | 6 ++--- .../remote/inference/runpod/config.py | 4 ++-- .../remote/inference/runpod/runpod.py | 5 ++++- .../remote/inference/vertexai/vertexai.py | 3 ++- .../providers/remote/inference/vllm/config.py | 6 ++--- .../providers/remote/inference/vllm/vllm.py | 2 +- .../tool_runtime/bing_search/bing_search.py | 2 +- .../remote/tool_runtime/bing_search/config.py | 4 ++-- .../tool_runtime/brave_search/brave_search.py | 2 +- .../tool_runtime/brave_search/config.py | 4 ++-- .../tool_runtime/tavily_search/config.py | 4 ++-- .../tavily_search/tavily_search.py | 2 +- .../wolfram_alpha/wolfram_alpha.py | 2 +- .../remote/vector_io/pgvector/config.py | 4 ++-- .../remote/vector_io/pgvector/pgvector.py | 2 +- llama_stack/providers/utils/bedrock/client.py | 4 ++-- llama_stack/providers/utils/bedrock/config.py | 10 ++++----- .../utils/inference/litellm_openai_mixin.py | 13 ++++++----- .../providers/utils/inference/openai_mixin.py | 5 +++-- llama_stack/providers/utils/kvstore/config.py | 6 ++--- .../utils/kvstore/mongodb/mongodb.py | 2 +- .../utils/kvstore/postgres/postgres.py | 2 +- .../providers/utils/sqlstore/sqlstore.py | 6 ++--- .../test_inference_client_caching.py | 6 ++--- .../inference/test_litellm_openai_mixin.py | 22 +++++++++---------- .../inference/test_openai_base_url_config.py | 14 +++++++----- 53 files changed, 121 insertions(+), 109 deletions(-) diff --git a/docs/docs/providers/files/remote_s3.mdx b/docs/docs/providers/files/remote_s3.mdx index 353cedbfb..a065da5f8 100644 --- a/docs/docs/providers/files/remote_s3.mdx +++ b/docs/docs/providers/files/remote_s3.mdx @@ -17,7 +17,7 @@ AWS S3-based file storage provider for scalable cloud file management with metad | `bucket_name` | `` | No | | S3 bucket name to store files | | `region` | `` | No | us-east-1 | AWS region where the bucket is located | | `aws_access_key_id` | `str \| None` | No | | AWS access key ID (optional if using IAM roles) | -| `aws_secret_access_key` | `str \| None` | No | | AWS secret access key (optional if using IAM roles) | +| `aws_secret_access_key` | `pydantic.types.SecretStr \| None` | No | | AWS secret access key (optional if using IAM roles) | | `endpoint_url` | `str \| None` | No | | Custom S3 endpoint URL (for MinIO, LocalStack, etc.) | | `auto_create_bucket` | `` | No | False | Automatically create the S3 bucket if it doesn't exist | | `metadata_store` | `utils.sqlstore.sqlstore.SqliteSqlStoreConfig \| utils.sqlstore.sqlstore.PostgresSqlStoreConfig` | No | sqlite | SQL store configuration for file metadata | diff --git a/docs/docs/providers/inference/remote_anthropic.mdx b/docs/docs/providers/inference/remote_anthropic.mdx index 6bd636c92..c8115c7a8 100644 --- a/docs/docs/providers/inference/remote_anthropic.mdx +++ b/docs/docs/providers/inference/remote_anthropic.mdx @@ -14,7 +14,7 @@ Anthropic inference provider for accessing Claude models and Anthropic's AI serv | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `str \| None` | No | | API key for Anthropic models | +| `api_key` | `pydantic.types.SecretStr \| None` | No | | API key for Anthropic models | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_bedrock.mdx b/docs/docs/providers/inference/remote_bedrock.mdx index 04c2154a9..3a4c296cf 100644 --- a/docs/docs/providers/inference/remote_bedrock.mdx +++ b/docs/docs/providers/inference/remote_bedrock.mdx @@ -15,8 +15,8 @@ AWS Bedrock inference provider for accessing various AI models through AWS's man | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `aws_access_key_id` | `str \| None` | No | | The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID | -| `aws_secret_access_key` | `str \| None` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | -| `aws_session_token` | `str \| None` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | +| `aws_secret_access_key` | `pydantic.types.SecretStr \| None` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | +| `aws_session_token` | `pydantic.types.SecretStr \| None` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | | `region_name` | `str \| None` | No | | The default AWS Region to use, for example, us-west-1 or us-west-2.Default use environment variable: AWS_DEFAULT_REGION | | `profile_name` | `str \| None` | No | | The profile name that contains credentials to use.Default use environment variable: AWS_PROFILE | | `total_max_attempts` | `int \| None` | No | | An integer representing the maximum number of attempts that will be made for a single request, including the initial attempt. Default use environment variable: AWS_MAX_ATTEMPTS | diff --git a/docs/docs/providers/inference/remote_gemini.mdx b/docs/docs/providers/inference/remote_gemini.mdx index 0505c69da..ce7c797c4 100644 --- a/docs/docs/providers/inference/remote_gemini.mdx +++ b/docs/docs/providers/inference/remote_gemini.mdx @@ -14,7 +14,7 @@ Google Gemini inference provider for accessing Gemini models and Google's AI ser | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `str \| None` | No | | API key for Gemini models | +| `api_key` | `pydantic.types.SecretStr \| None` | No | | API key for Gemini models | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_groq.mdx b/docs/docs/providers/inference/remote_groq.mdx index 1797035c1..c2745ed98 100644 --- a/docs/docs/providers/inference/remote_groq.mdx +++ b/docs/docs/providers/inference/remote_groq.mdx @@ -14,7 +14,7 @@ Groq inference provider for ultra-fast inference using Groq's LPU technology. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `str \| None` | No | | The Groq API key | +| `api_key` | `pydantic.types.SecretStr \| None` | No | | The Groq API key | | `url` | `` | No | https://api.groq.com | The URL for the Groq AI server | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_llama-openai-compat.mdx b/docs/docs/providers/inference/remote_llama-openai-compat.mdx index cb624ad87..b7b0204e6 100644 --- a/docs/docs/providers/inference/remote_llama-openai-compat.mdx +++ b/docs/docs/providers/inference/remote_llama-openai-compat.mdx @@ -14,7 +14,7 @@ Llama OpenAI-compatible provider for using Llama models with OpenAI API format. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `str \| None` | No | | The Llama API key | +| `api_key` | `pydantic.types.SecretStr \| None` | No | | The Llama API key | | `openai_compat_api_base` | `` | No | https://api.llama.com/compat/v1/ | The URL for the Llama API server | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_openai.mdx b/docs/docs/providers/inference/remote_openai.mdx index 56ca94233..2c7650bb5 100644 --- a/docs/docs/providers/inference/remote_openai.mdx +++ b/docs/docs/providers/inference/remote_openai.mdx @@ -14,7 +14,7 @@ OpenAI inference provider for accessing GPT models and other OpenAI services. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `str \| None` | No | | API key for OpenAI models | +| `api_key` | `pydantic.types.SecretStr \| None` | No | | API key for OpenAI models | | `base_url` | `` | No | https://api.openai.com/v1 | Base URL for OpenAI API | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_runpod.mdx b/docs/docs/providers/inference/remote_runpod.mdx index 2e8847dc5..5d3655474 100644 --- a/docs/docs/providers/inference/remote_runpod.mdx +++ b/docs/docs/providers/inference/remote_runpod.mdx @@ -15,7 +15,7 @@ RunPod inference provider for running models on RunPod's cloud GPU platform. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `str \| None` | No | | The URL for the Runpod model serving endpoint | -| `api_token` | `str \| None` | No | | The API token | +| `api_token` | `pydantic.types.SecretStr \| None` | No | | The API token | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_vllm.mdx b/docs/docs/providers/inference/remote_vllm.mdx index 77b8e1355..1faf131b9 100644 --- a/docs/docs/providers/inference/remote_vllm.mdx +++ b/docs/docs/providers/inference/remote_vllm.mdx @@ -16,7 +16,7 @@ Remote vLLM inference provider for connecting to vLLM servers. |-------|------|----------|---------|-------------| | `url` | `str \| None` | No | | The URL for the vLLM model serving endpoint | | `max_tokens` | `` | No | 4096 | Maximum number of tokens to generate. | -| `api_token` | `str \| None` | No | fake | The API token | +| `api_token` | `pydantic.types.SecretStr \| None` | No | ********** | The API token | | `tls_verify` | `bool \| str` | No | True | Whether to verify TLS certificates. Can be a boolean or a path to a CA certificate file. | | `refresh_models` | `` | No | False | Whether to refresh models periodically | diff --git a/docs/docs/providers/safety/remote_bedrock.mdx b/docs/docs/providers/safety/remote_bedrock.mdx index 5461d7cdc..85bc662e8 100644 --- a/docs/docs/providers/safety/remote_bedrock.mdx +++ b/docs/docs/providers/safety/remote_bedrock.mdx @@ -15,8 +15,8 @@ AWS Bedrock safety provider for content moderation using AWS's safety services. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `aws_access_key_id` | `str \| None` | No | | The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID | -| `aws_secret_access_key` | `str \| None` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | -| `aws_session_token` | `str \| None` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | +| `aws_secret_access_key` | `pydantic.types.SecretStr \| None` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | +| `aws_session_token` | `pydantic.types.SecretStr \| None` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | | `region_name` | `str \| None` | No | | The default AWS Region to use, for example, us-west-1 or us-west-2.Default use environment variable: AWS_DEFAULT_REGION | | `profile_name` | `str \| None` | No | | The profile name that contains credentials to use.Default use environment variable: AWS_PROFILE | | `total_max_attempts` | `int \| None` | No | | An integer representing the maximum number of attempts that will be made for a single request, including the initial attempt. Default use environment variable: AWS_MAX_ATTEMPTS | diff --git a/docs/docs/providers/scoring/inline_braintrust.mdx b/docs/docs/providers/scoring/inline_braintrust.mdx index d12f9de25..67de0667a 100644 --- a/docs/docs/providers/scoring/inline_braintrust.mdx +++ b/docs/docs/providers/scoring/inline_braintrust.mdx @@ -14,7 +14,7 @@ Braintrust scoring provider for evaluation and scoring using the Braintrust plat | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `openai_api_key` | `str \| None` | No | | The OpenAI API Key | +| `openai_api_key` | `pydantic.types.SecretStr \| None` | No | | The OpenAI API Key | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_bing-search.mdx b/docs/docs/providers/tool_runtime/remote_bing-search.mdx index ec06bc20f..3085ab9ec 100644 --- a/docs/docs/providers/tool_runtime/remote_bing-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_bing-search.mdx @@ -14,7 +14,7 @@ Bing Search tool for web search capabilities using Microsoft's search engine. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `str \| None` | No | | | +| `api_key` | `pydantic.types.SecretStr \| None` | No | | | | `top_k` | `` | No | 3 | | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_brave-search.mdx b/docs/docs/providers/tool_runtime/remote_brave-search.mdx index 3aeed67d5..c71ac3d7a 100644 --- a/docs/docs/providers/tool_runtime/remote_brave-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_brave-search.mdx @@ -14,7 +14,7 @@ Brave Search tool for web search capabilities with privacy-focused results. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `str \| None` | No | | The Brave Search API Key | +| `api_key` | `pydantic.types.SecretStr \| None` | No | | The Brave Search API Key | | `max_results` | `` | No | 3 | The maximum number of results to return | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_tavily-search.mdx b/docs/docs/providers/tool_runtime/remote_tavily-search.mdx index fdca31bbe..0fe263a5b 100644 --- a/docs/docs/providers/tool_runtime/remote_tavily-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_tavily-search.mdx @@ -14,7 +14,7 @@ Tavily Search tool for AI-optimized web search with structured results. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `str \| None` | No | | The Tavily Search API Key | +| `api_key` | `pydantic.types.SecretStr \| None` | No | | The Tavily Search API Key | | `max_results` | `` | No | 3 | The maximum number of results to return | ## Sample Configuration diff --git a/docs/docs/providers/vector_io/remote_pgvector.mdx b/docs/docs/providers/vector_io/remote_pgvector.mdx index d21810c68..cf0c28f12 100644 --- a/docs/docs/providers/vector_io/remote_pgvector.mdx +++ b/docs/docs/providers/vector_io/remote_pgvector.mdx @@ -217,7 +217,7 @@ See [PGVector's documentation](https://github.com/pgvector/pgvector) for more de | `port` | `int \| None` | No | 5432 | | | `db` | `str \| None` | No | postgres | | | `user` | `str \| None` | No | postgres | | -| `password` | `str \| None` | No | mysecretpassword | | +| `password` | `pydantic.types.SecretStr \| None` | No | mysecretpassword | | | `kvstore` | `utils.kvstore.config.RedisKVStoreConfig \| utils.kvstore.config.SqliteKVStoreConfig \| utils.kvstore.config.PostgresKVStoreConfig \| utils.kvstore.config.MongoDBKVStoreConfig, annotation=NoneType, required=False, default='sqlite', discriminator='type'` | No | | Config for KV store backend (SQLite only for now) | ## Sample Configuration diff --git a/llama_stack/providers/inline/scoring/braintrust/__init__.py b/llama_stack/providers/inline/scoring/braintrust/__init__.py index 3b492ae3f..2f3dce966 100644 --- a/llama_stack/providers/inline/scoring/braintrust/__init__.py +++ b/llama_stack/providers/inline/scoring/braintrust/__init__.py @@ -5,7 +5,7 @@ # the root directory of this source tree. from typing import Any -from pydantic import BaseModel +from pydantic import BaseModel, SecretStr from llama_stack.core.datatypes import Api @@ -13,7 +13,7 @@ from .config import BraintrustScoringConfig class BraintrustProviderDataValidator(BaseModel): - openai_api_key: str + openai_api_key: SecretStr async def get_provider_impl( diff --git a/llama_stack/providers/inline/scoring/braintrust/braintrust.py b/llama_stack/providers/inline/scoring/braintrust/braintrust.py index 14810f706..7f2b1e205 100644 --- a/llama_stack/providers/inline/scoring/braintrust/braintrust.py +++ b/llama_stack/providers/inline/scoring/braintrust/braintrust.py @@ -17,7 +17,7 @@ from autoevals.ragas import ( ContextRelevancy, Faithfulness, ) -from pydantic import BaseModel +from pydantic import BaseModel, SecretStr from llama_stack.apis.datasetio import DatasetIO from llama_stack.apis.datasets import Datasets @@ -152,9 +152,9 @@ class BraintrustScoringImpl( raise ValueError( 'Pass OpenAI API Key in the header X-LlamaStack-Provider-Data as { "openai_api_key": }' ) - self.config.openai_api_key = provider_data.openai_api_key + self.config.openai_api_key = SecretStr(provider_data.openai_api_key) - os.environ["OPENAI_API_KEY"] = self.config.openai_api_key + os.environ["OPENAI_API_KEY"] = self.config.openai_api_key.get_secret_value() async def score_batch( self, diff --git a/llama_stack/providers/inline/scoring/braintrust/config.py b/llama_stack/providers/inline/scoring/braintrust/config.py index 057f0ba5d..088674afd 100644 --- a/llama_stack/providers/inline/scoring/braintrust/config.py +++ b/llama_stack/providers/inline/scoring/braintrust/config.py @@ -5,11 +5,11 @@ # the root directory of this source tree. from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr class BraintrustScoringConfig(BaseModel): - openai_api_key: str | None = Field( + openai_api_key: SecretStr | None = Field( default=None, description="The OpenAI API Key", ) diff --git a/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py b/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py index 78e49af94..8beb3a841 100644 --- a/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py +++ b/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py @@ -64,7 +64,9 @@ class ConsoleSpanProcessor(SpanProcessor): for key, value in event.attributes.items(): if key.startswith("__") or key in ["message", "severity"]: continue - logger.info(f"[dim]{key}[/dim]: {value}") + + str_value = str(value) + logger.info(f"[dim]{key}[/dim]: {str_value}") def shutdown(self) -> None: """Shutdown the processor.""" diff --git a/llama_stack/providers/remote/files/s3/config.py b/llama_stack/providers/remote/files/s3/config.py index da20d8668..5cddd2b38 100644 --- a/llama_stack/providers/remote/files/s3/config.py +++ b/llama_stack/providers/remote/files/s3/config.py @@ -6,7 +6,7 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr from llama_stack.providers.utils.sqlstore.sqlstore import SqliteSqlStoreConfig, SqlStoreConfig @@ -17,7 +17,7 @@ class S3FilesImplConfig(BaseModel): bucket_name: str = Field(description="S3 bucket name to store files") region: str = Field(default="us-east-1", description="AWS region where the bucket is located") aws_access_key_id: str | None = Field(default=None, description="AWS access key ID (optional if using IAM roles)") - aws_secret_access_key: str | None = Field( + aws_secret_access_key: SecretStr | None = Field( default=None, description="AWS secret access key (optional if using IAM roles)" ) endpoint_url: str | None = Field(default=None, description="Custom S3 endpoint URL (for MinIO, LocalStack, etc.)") diff --git a/llama_stack/providers/remote/files/s3/files.py b/llama_stack/providers/remote/files/s3/files.py index 8ea96af9e..61b82105e 100644 --- a/llama_stack/providers/remote/files/s3/files.py +++ b/llama_stack/providers/remote/files/s3/files.py @@ -46,7 +46,7 @@ def _create_s3_client(config: S3FilesImplConfig) -> boto3.client: s3_config.update( { "aws_access_key_id": config.aws_access_key_id, - "aws_secret_access_key": config.aws_secret_access_key, + "aws_secret_access_key": config.aws_secret_access_key.get_secret_value(), } ) diff --git a/llama_stack/providers/remote/inference/anthropic/anthropic.py b/llama_stack/providers/remote/inference/anthropic/anthropic.py index cdde4a411..41f65ade7 100644 --- a/llama_stack/providers/remote/inference/anthropic/anthropic.py +++ b/llama_stack/providers/remote/inference/anthropic/anthropic.py @@ -4,6 +4,7 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. + from llama_stack.providers.utils.inference.litellm_openai_mixin import LiteLLMOpenAIMixin from llama_stack.providers.utils.inference.openai_mixin import OpenAIMixin @@ -27,7 +28,7 @@ class AnthropicInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): LiteLLMOpenAIMixin.__init__( self, litellm_provider_name="anthropic", - api_key_from_config=config.api_key, + api_key_from_config=config.api_key.get_secret_value() if config.api_key else None, provider_data_api_key_field="anthropic_api_key", ) self.config = config diff --git a/llama_stack/providers/remote/inference/anthropic/config.py b/llama_stack/providers/remote/inference/anthropic/config.py index a74b97a9e..ae3034219 100644 --- a/llama_stack/providers/remote/inference/anthropic/config.py +++ b/llama_stack/providers/remote/inference/anthropic/config.py @@ -6,13 +6,13 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr from llama_stack.schema_utils import json_schema_type class AnthropicProviderDataValidator(BaseModel): - anthropic_api_key: str | None = Field( + anthropic_api_key: SecretStr | None = Field( default=None, description="API key for Anthropic models", ) @@ -20,7 +20,7 @@ class AnthropicProviderDataValidator(BaseModel): @json_schema_type class AnthropicConfig(BaseModel): - api_key: str | None = Field( + api_key: SecretStr | None = Field( default=None, description="API key for Anthropic models", ) diff --git a/llama_stack/providers/remote/inference/gemini/config.py b/llama_stack/providers/remote/inference/gemini/config.py index c897777f7..27897965c 100644 --- a/llama_stack/providers/remote/inference/gemini/config.py +++ b/llama_stack/providers/remote/inference/gemini/config.py @@ -6,13 +6,13 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr from llama_stack.schema_utils import json_schema_type class GeminiProviderDataValidator(BaseModel): - gemini_api_key: str | None = Field( + gemini_api_key: SecretStr | None = Field( default=None, description="API key for Gemini models", ) @@ -20,7 +20,7 @@ class GeminiProviderDataValidator(BaseModel): @json_schema_type class GeminiConfig(BaseModel): - api_key: str | None = Field( + api_key: SecretStr | None = Field( default=None, description="API key for Gemini models", ) diff --git a/llama_stack/providers/remote/inference/gemini/gemini.py b/llama_stack/providers/remote/inference/gemini/gemini.py index 30ceedff0..b02d0d611 100644 --- a/llama_stack/providers/remote/inference/gemini/gemini.py +++ b/llama_stack/providers/remote/inference/gemini/gemini.py @@ -4,6 +4,7 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. + from llama_stack.providers.utils.inference.litellm_openai_mixin import LiteLLMOpenAIMixin from llama_stack.providers.utils.inference.openai_mixin import OpenAIMixin @@ -19,7 +20,7 @@ class GeminiInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): LiteLLMOpenAIMixin.__init__( self, litellm_provider_name="gemini", - api_key_from_config=config.api_key, + api_key_from_config=config.api_key.get_secret_value() if config.api_key else None, provider_data_api_key_field="gemini_api_key", ) self.config = config diff --git a/llama_stack/providers/remote/inference/groq/config.py b/llama_stack/providers/remote/inference/groq/config.py index 67e9fa358..1011e8da9 100644 --- a/llama_stack/providers/remote/inference/groq/config.py +++ b/llama_stack/providers/remote/inference/groq/config.py @@ -6,13 +6,13 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr from llama_stack.schema_utils import json_schema_type class GroqProviderDataValidator(BaseModel): - groq_api_key: str | None = Field( + groq_api_key: SecretStr | None = Field( default=None, description="API key for Groq models", ) @@ -20,7 +20,7 @@ class GroqProviderDataValidator(BaseModel): @json_schema_type class GroqConfig(BaseModel): - api_key: str | None = Field( + api_key: SecretStr | None = Field( # The Groq client library loads the GROQ_API_KEY environment variable by default default=None, description="The Groq API key", diff --git a/llama_stack/providers/remote/inference/llama_openai_compat/config.py b/llama_stack/providers/remote/inference/llama_openai_compat/config.py index 57bc7240d..064cc860d 100644 --- a/llama_stack/providers/remote/inference/llama_openai_compat/config.py +++ b/llama_stack/providers/remote/inference/llama_openai_compat/config.py @@ -6,13 +6,13 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr from llama_stack.schema_utils import json_schema_type class LlamaProviderDataValidator(BaseModel): - llama_api_key: str | None = Field( + llama_api_key: SecretStr | None = Field( default=None, description="API key for api.llama models", ) @@ -20,7 +20,7 @@ class LlamaProviderDataValidator(BaseModel): @json_schema_type class LlamaCompatConfig(BaseModel): - api_key: str | None = Field( + api_key: SecretStr | None = Field( default=None, description="The Llama API key", ) diff --git a/llama_stack/providers/remote/inference/openai/config.py b/llama_stack/providers/remote/inference/openai/config.py index ad25cdfa5..7de7bcc32 100644 --- a/llama_stack/providers/remote/inference/openai/config.py +++ b/llama_stack/providers/remote/inference/openai/config.py @@ -6,13 +6,13 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr from llama_stack.schema_utils import json_schema_type class OpenAIProviderDataValidator(BaseModel): - openai_api_key: str | None = Field( + openai_api_key: SecretStr | None = Field( default=None, description="API key for OpenAI models", ) @@ -20,7 +20,7 @@ class OpenAIProviderDataValidator(BaseModel): @json_schema_type class OpenAIConfig(BaseModel): - api_key: str | None = Field( + api_key: SecretStr | None = Field( default=None, description="API key for OpenAI models", ) diff --git a/llama_stack/providers/remote/inference/runpod/config.py b/llama_stack/providers/remote/inference/runpod/config.py index 7bc9e8485..60086acef 100644 --- a/llama_stack/providers/remote/inference/runpod/config.py +++ b/llama_stack/providers/remote/inference/runpod/config.py @@ -6,7 +6,7 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr from llama_stack.schema_utils import json_schema_type @@ -17,7 +17,7 @@ class RunpodImplConfig(BaseModel): default=None, description="The URL for the Runpod model serving endpoint", ) - api_token: str | None = Field( + api_token: SecretStr | None = Field( default=None, description="The API token", ) diff --git a/llama_stack/providers/remote/inference/runpod/runpod.py b/llama_stack/providers/remote/inference/runpod/runpod.py index ff2fe6401..5cfa05fca 100644 --- a/llama_stack/providers/remote/inference/runpod/runpod.py +++ b/llama_stack/providers/remote/inference/runpod/runpod.py @@ -103,7 +103,10 @@ class RunpodInferenceAdapter( tool_config=tool_config, ) - client = OpenAI(base_url=self.config.url, api_key=self.config.api_token) + client = OpenAI( + base_url=self.config.url, + api_key=self.config.api_token.get_secret_value() if self.config.api_token else None, + ) if stream: return self._stream_chat_completion(request, client) else: diff --git a/llama_stack/providers/remote/inference/vertexai/vertexai.py b/llama_stack/providers/remote/inference/vertexai/vertexai.py index 770d21a2a..733f2ab4a 100644 --- a/llama_stack/providers/remote/inference/vertexai/vertexai.py +++ b/llama_stack/providers/remote/inference/vertexai/vertexai.py @@ -8,6 +8,7 @@ from typing import Any import google.auth.transport.requests from google.auth import default +from pydantic import SecretStr from llama_stack.apis.inference import ChatCompletionRequest from llama_stack.providers.utils.inference.litellm_openai_mixin import ( @@ -43,7 +44,7 @@ class VertexAIInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): except Exception: # If we can't get credentials, return empty string to let LiteLLM handle it # This allows the LiteLLM mixin to work with ADC directly - return "" + return SecretStr("") def get_base_url(self) -> str: """ diff --git a/llama_stack/providers/remote/inference/vllm/config.py b/llama_stack/providers/remote/inference/vllm/config.py index a5bf0e4bc..978427930 100644 --- a/llama_stack/providers/remote/inference/vllm/config.py +++ b/llama_stack/providers/remote/inference/vllm/config.py @@ -6,7 +6,7 @@ from pathlib import Path -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field, SecretStr, field_validator from llama_stack.schema_utils import json_schema_type @@ -21,8 +21,8 @@ class VLLMInferenceAdapterConfig(BaseModel): default=4096, description="Maximum number of tokens to generate.", ) - api_token: str | None = Field( - default="fake", + api_token: SecretStr | None = Field( + default=SecretStr("fake"), description="The API token", ) tls_verify: bool | str = Field( diff --git a/llama_stack/providers/remote/inference/vllm/vllm.py b/llama_stack/providers/remote/inference/vllm/vllm.py index 8fbb4b815..2e2e1ea5c 100644 --- a/llama_stack/providers/remote/inference/vllm/vllm.py +++ b/llama_stack/providers/remote/inference/vllm/vllm.py @@ -294,7 +294,7 @@ class VLLMInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin, Inference, ModelsPro self, model_entries=build_hf_repo_model_entries(), litellm_provider_name="vllm", - api_key_from_config=config.api_token, + api_key_from_config=config.api_token.get_secret_value(), provider_data_api_key_field="vllm_api_token", openai_compat_api_base=config.url, ) diff --git a/llama_stack/providers/remote/tool_runtime/bing_search/bing_search.py b/llama_stack/providers/remote/tool_runtime/bing_search/bing_search.py index e40903969..227deaecd 100644 --- a/llama_stack/providers/remote/tool_runtime/bing_search/bing_search.py +++ b/llama_stack/providers/remote/tool_runtime/bing_search/bing_search.py @@ -40,7 +40,7 @@ class BingSearchToolRuntimeImpl(ToolGroupsProtocolPrivate, ToolRuntime, NeedsReq def _get_api_key(self) -> str: if self.config.api_key: - return self.config.api_key + return self.config.api_key.get_secret_value() provider_data = self.get_request_provider_data() if provider_data is None or not provider_data.bing_search_api_key: diff --git a/llama_stack/providers/remote/tool_runtime/bing_search/config.py b/llama_stack/providers/remote/tool_runtime/bing_search/config.py index 30269dbc1..3c4852fba 100644 --- a/llama_stack/providers/remote/tool_runtime/bing_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/bing_search/config.py @@ -6,13 +6,13 @@ from typing import Any -from pydantic import BaseModel +from pydantic import BaseModel, SecretStr class BingSearchToolConfig(BaseModel): """Configuration for Bing Search Tool Runtime""" - api_key: str | None = None + api_key: SecretStr | None = None top_k: int = 3 @classmethod diff --git a/llama_stack/providers/remote/tool_runtime/brave_search/brave_search.py b/llama_stack/providers/remote/tool_runtime/brave_search/brave_search.py index ba3b910d5..a34e94ad3 100644 --- a/llama_stack/providers/remote/tool_runtime/brave_search/brave_search.py +++ b/llama_stack/providers/remote/tool_runtime/brave_search/brave_search.py @@ -39,7 +39,7 @@ class BraveSearchToolRuntimeImpl(ToolGroupsProtocolPrivate, ToolRuntime, NeedsRe def _get_api_key(self) -> str: if self.config.api_key: - return self.config.api_key + return self.config.api_key.get_secret_value() provider_data = self.get_request_provider_data() if provider_data is None or not provider_data.brave_search_api_key: diff --git a/llama_stack/providers/remote/tool_runtime/brave_search/config.py b/llama_stack/providers/remote/tool_runtime/brave_search/config.py index f02967ce8..41f6c9bbc 100644 --- a/llama_stack/providers/remote/tool_runtime/brave_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/brave_search/config.py @@ -6,11 +6,11 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr class BraveSearchToolConfig(BaseModel): - api_key: str | None = Field( + api_key: SecretStr | None = Field( default=None, description="The Brave Search API Key", ) diff --git a/llama_stack/providers/remote/tool_runtime/tavily_search/config.py b/llama_stack/providers/remote/tool_runtime/tavily_search/config.py index ca4e615db..f81b3455a 100644 --- a/llama_stack/providers/remote/tool_runtime/tavily_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/tavily_search/config.py @@ -6,11 +6,11 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr class TavilySearchToolConfig(BaseModel): - api_key: str | None = Field( + api_key: SecretStr | None = Field( default=None, description="The Tavily Search API Key", ) diff --git a/llama_stack/providers/remote/tool_runtime/tavily_search/tavily_search.py b/llama_stack/providers/remote/tool_runtime/tavily_search/tavily_search.py index 976ec9c57..9048cd864 100644 --- a/llama_stack/providers/remote/tool_runtime/tavily_search/tavily_search.py +++ b/llama_stack/providers/remote/tool_runtime/tavily_search/tavily_search.py @@ -39,7 +39,7 @@ class TavilySearchToolRuntimeImpl(ToolGroupsProtocolPrivate, ToolRuntime, NeedsR def _get_api_key(self) -> str: if self.config.api_key: - return self.config.api_key + return self.config.api_key.get_secret_value() provider_data = self.get_request_provider_data() if provider_data is None or not provider_data.tavily_search_api_key: diff --git a/llama_stack/providers/remote/tool_runtime/wolfram_alpha/wolfram_alpha.py b/llama_stack/providers/remote/tool_runtime/wolfram_alpha/wolfram_alpha.py index f12a44958..1ba7639d5 100644 --- a/llama_stack/providers/remote/tool_runtime/wolfram_alpha/wolfram_alpha.py +++ b/llama_stack/providers/remote/tool_runtime/wolfram_alpha/wolfram_alpha.py @@ -40,7 +40,7 @@ class WolframAlphaToolRuntimeImpl(ToolGroupsProtocolPrivate, ToolRuntime, NeedsR def _get_api_key(self) -> str: if self.config.api_key: - return self.config.api_key + return self.config.api_key.get_secret_value() provider_data = self.get_request_provider_data() if provider_data is None or not provider_data.wolfram_alpha_api_key: diff --git a/llama_stack/providers/remote/vector_io/pgvector/config.py b/llama_stack/providers/remote/vector_io/pgvector/config.py index 334cbe5be..abfa0eacf 100644 --- a/llama_stack/providers/remote/vector_io/pgvector/config.py +++ b/llama_stack/providers/remote/vector_io/pgvector/config.py @@ -6,7 +6,7 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr from llama_stack.providers.utils.kvstore.config import ( KVStoreConfig, @@ -21,7 +21,7 @@ class PGVectorVectorIOConfig(BaseModel): port: int | None = Field(default=5432) db: str | None = Field(default="postgres") user: str | None = Field(default="postgres") - password: str | None = Field(default="mysecretpassword") + password: SecretStr | None = Field(default="mysecretpassword") kvstore: KVStoreConfig | None = Field(description="Config for KV store backend (SQLite only for now)", default=None) @classmethod diff --git a/llama_stack/providers/remote/vector_io/pgvector/pgvector.py b/llama_stack/providers/remote/vector_io/pgvector/pgvector.py index 1c140e782..fae3c7200 100644 --- a/llama_stack/providers/remote/vector_io/pgvector/pgvector.py +++ b/llama_stack/providers/remote/vector_io/pgvector/pgvector.py @@ -366,7 +366,7 @@ class PGVectorVectorIOAdapter(OpenAIVectorStoreMixin, VectorIO, VectorDBsProtoco port=self.config.port, database=self.config.db, user=self.config.user, - password=self.config.password, + password=self.config.password.get_secret_value(), ) self.conn.autocommit = True with self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur: diff --git a/llama_stack/providers/utils/bedrock/client.py b/llama_stack/providers/utils/bedrock/client.py index b3c8629e0..16f630326 100644 --- a/llama_stack/providers/utils/bedrock/client.py +++ b/llama_stack/providers/utils/bedrock/client.py @@ -50,8 +50,8 @@ def create_bedrock_client(config: BedrockBaseConfig, service_name: str = "bedroc session_args = { "aws_access_key_id": config.aws_access_key_id, - "aws_secret_access_key": config.aws_secret_access_key, - "aws_session_token": config.aws_session_token, + "aws_secret_access_key": config.aws_secret_access_key.get_secret_value(), + "aws_session_token": config.aws_session_token.get_secret_value(), "region_name": config.region_name, "profile_name": config.profile_name, "session_ttl": config.session_ttl, diff --git a/llama_stack/providers/utils/bedrock/config.py b/llama_stack/providers/utils/bedrock/config.py index 2745c88cb..b48f0ea69 100644 --- a/llama_stack/providers/utils/bedrock/config.py +++ b/llama_stack/providers/utils/bedrock/config.py @@ -6,7 +6,7 @@ import os -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr class BedrockBaseConfig(BaseModel): @@ -14,12 +14,12 @@ class BedrockBaseConfig(BaseModel): default_factory=lambda: os.getenv("AWS_ACCESS_KEY_ID"), description="The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID", ) - aws_secret_access_key: str | None = Field( - default_factory=lambda: os.getenv("AWS_SECRET_ACCESS_KEY"), + aws_secret_access_key: SecretStr | None = Field( + default_factory=lambda: SecretStr(val) if (val := os.getenv("AWS_SECRET_ACCESS_KEY")) else None, description="The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY", ) - aws_session_token: str | None = Field( - default_factory=lambda: os.getenv("AWS_SESSION_TOKEN"), + aws_session_token: SecretStr | None = Field( + default_factory=lambda: SecretStr(val) if (val := os.getenv("AWS_SESSION_TOKEN")) else None, description="The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN", ) region_name: str | None = Field( diff --git a/llama_stack/providers/utils/inference/litellm_openai_mixin.py b/llama_stack/providers/utils/inference/litellm_openai_mixin.py index b1e38f323..fc39852ae 100644 --- a/llama_stack/providers/utils/inference/litellm_openai_mixin.py +++ b/llama_stack/providers/utils/inference/litellm_openai_mixin.py @@ -8,6 +8,7 @@ from collections.abc import AsyncGenerator, AsyncIterator from typing import Any import litellm +from pydantic import SecretStr from llama_stack.apis.common.content_types import ( InterleavedContent, @@ -68,7 +69,7 @@ class LiteLLMOpenAIMixin( def __init__( self, litellm_provider_name: str, - api_key_from_config: str | None, + api_key_from_config: SecretStr | None, provider_data_api_key_field: str, model_entries: list[ProviderModelEntry] | None = None, openai_compat_api_base: str | None = None, @@ -247,14 +248,14 @@ class LiteLLMOpenAIMixin( return { "model": request.model, - "api_key": self.get_api_key(), + "api_key": self.get_api_key().get_secret_value(), "api_base": self.api_base, **input_dict, "stream": request.stream, **get_sampling_options(request.sampling_params), } - def get_api_key(self) -> str: + def get_api_key(self) -> SecretStr: provider_data = self.get_request_provider_data() key_field = self.provider_data_api_key_field if provider_data and getattr(provider_data, key_field, None): @@ -305,7 +306,7 @@ class LiteLLMOpenAIMixin( response = litellm.embedding( model=self.get_litellm_model_name(model_obj.provider_resource_id), input=input_list, - api_key=self.get_api_key(), + api_key=self.get_api_key().get_secret_value(), api_base=self.api_base, dimensions=dimensions, ) @@ -368,7 +369,7 @@ class LiteLLMOpenAIMixin( user=user, guided_choice=guided_choice, prompt_logprobs=prompt_logprobs, - api_key=self.get_api_key(), + api_key=self.get_api_key().get_secret_value(), api_base=self.api_base, ) return await litellm.atext_completion(**params) @@ -424,7 +425,7 @@ class LiteLLMOpenAIMixin( top_logprobs=top_logprobs, top_p=top_p, user=user, - api_key=self.get_api_key(), + api_key=self.get_api_key().get_secret_value(), api_base=self.api_base, ) return await litellm.acompletion(**params) diff --git a/llama_stack/providers/utils/inference/openai_mixin.py b/llama_stack/providers/utils/inference/openai_mixin.py index 7da97e6b1..7fbd62ef6 100644 --- a/llama_stack/providers/utils/inference/openai_mixin.py +++ b/llama_stack/providers/utils/inference/openai_mixin.py @@ -11,6 +11,7 @@ from collections.abc import AsyncIterator from typing import Any from openai import NOT_GIVEN, AsyncOpenAI +from pydantic import SecretStr from llama_stack.apis.inference import ( Model, @@ -70,14 +71,14 @@ class OpenAIMixin(ModelRegistryHelper, ABC): allowed_models: list[str] = [] @abstractmethod - def get_api_key(self) -> str: + def get_api_key(self) -> SecretStr: """ Get the API key. This method must be implemented by child classes to provide the API key for authenticating with the OpenAI API or compatible endpoints. - :return: The API key as a string + :return: The API key as a SecretStr """ pass diff --git a/llama_stack/providers/utils/kvstore/config.py b/llama_stack/providers/utils/kvstore/config.py index 7b6a79350..1cd90d308 100644 --- a/llama_stack/providers/utils/kvstore/config.py +++ b/llama_stack/providers/utils/kvstore/config.py @@ -8,7 +8,7 @@ import re from enum import Enum from typing import Annotated, Literal -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field, SecretStr, field_validator from llama_stack.core.utils.config_dirs import RUNTIME_BASE_DIR @@ -74,7 +74,7 @@ class PostgresKVStoreConfig(CommonConfig): port: int = 5432 db: str = "llamastack" user: str - password: str | None = None + password: SecretStr | None = None ssl_mode: str | None = None ca_cert_path: str | None = None table_name: str = "llamastack_kvstore" @@ -118,7 +118,7 @@ class MongoDBKVStoreConfig(CommonConfig): port: int = 27017 db: str = "llamastack" user: str | None = None - password: str | None = None + password: SecretStr | None = None collection_name: str = "llamastack_kvstore" @classmethod diff --git a/llama_stack/providers/utils/kvstore/mongodb/mongodb.py b/llama_stack/providers/utils/kvstore/mongodb/mongodb.py index 4d60949c1..83ce6da60 100644 --- a/llama_stack/providers/utils/kvstore/mongodb/mongodb.py +++ b/llama_stack/providers/utils/kvstore/mongodb/mongodb.py @@ -34,7 +34,7 @@ class MongoDBKVStoreImpl(KVStore): "host": self.config.host, "port": self.config.port, "username": self.config.user, - "password": self.config.password, + "password": self.config.password.get_secret_value(), } conn_creds = {k: v for k, v in conn_creds.items() if v is not None} self.conn = AsyncMongoClient(**conn_creds) diff --git a/llama_stack/providers/utils/kvstore/postgres/postgres.py b/llama_stack/providers/utils/kvstore/postgres/postgres.py index 56d6dbb48..778aa04be 100644 --- a/llama_stack/providers/utils/kvstore/postgres/postgres.py +++ b/llama_stack/providers/utils/kvstore/postgres/postgres.py @@ -30,7 +30,7 @@ class PostgresKVStoreImpl(KVStore): port=self.config.port, database=self.config.db, user=self.config.user, - password=self.config.password, + password=self.config.password.get_secret_value(), sslmode=self.config.ssl_mode, sslrootcert=self.config.ca_cert_path, ) diff --git a/llama_stack/providers/utils/sqlstore/sqlstore.py b/llama_stack/providers/utils/sqlstore/sqlstore.py index fc44402ae..3f6bedc7e 100644 --- a/llama_stack/providers/utils/sqlstore/sqlstore.py +++ b/llama_stack/providers/utils/sqlstore/sqlstore.py @@ -9,7 +9,7 @@ from enum import StrEnum from pathlib import Path from typing import Annotated, Literal -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr from llama_stack.core.utils.config_dirs import RUNTIME_BASE_DIR @@ -63,11 +63,11 @@ class PostgresSqlStoreConfig(SqlAlchemySqlStoreConfig): port: int = 5432 db: str = "llamastack" user: str - password: str | None = None + password: SecretStr | None = None @property def engine_str(self) -> str: - return f"postgresql+asyncpg://{self.user}:{self.password}@{self.host}:{self.port}/{self.db}" + return f"postgresql+asyncpg://{self.user}:{self.password.get_secret_value() if self.password else ''}@{self.host}:{self.port}/{self.db}" @classmethod def pip_packages(cls) -> list[str]: diff --git a/tests/unit/providers/inference/test_inference_client_caching.py b/tests/unit/providers/inference/test_inference_client_caching.py index f4b3201e9..974d55ade 100644 --- a/tests/unit/providers/inference/test_inference_client_caching.py +++ b/tests/unit/providers/inference/test_inference_client_caching.py @@ -33,7 +33,7 @@ def test_groq_provider_openai_client_caching(): with request_provider_data_context( {"x-llamastack-provider-data": json.dumps({inference_adapter.provider_data_api_key_field: api_key})} ): - assert inference_adapter.client.api_key == api_key + assert inference_adapter.client.api_key.get_secret_value() == api_key def test_openai_provider_openai_client_caching(): @@ -52,7 +52,7 @@ def test_openai_provider_openai_client_caching(): {"x-llamastack-provider-data": json.dumps({inference_adapter.provider_data_api_key_field: api_key})} ): openai_client = inference_adapter.client - assert openai_client.api_key == api_key + assert openai_client.api_key.get_secret_value() == api_key def test_together_provider_openai_client_caching(): @@ -86,4 +86,4 @@ def test_llama_compat_provider_openai_client_caching(): for api_key in ["test1", "test2"]: with request_provider_data_context({"x-llamastack-provider-data": json.dumps({"llama_api_key": api_key})}): - assert inference_adapter.client.api_key == api_key + assert inference_adapter.client.api_key.get_secret_value() == api_key diff --git a/tests/unit/providers/inference/test_litellm_openai_mixin.py b/tests/unit/providers/inference/test_litellm_openai_mixin.py index dc17e6abf..cf7623dd1 100644 --- a/tests/unit/providers/inference/test_litellm_openai_mixin.py +++ b/tests/unit/providers/inference/test_litellm_openai_mixin.py @@ -8,7 +8,7 @@ import json from unittest.mock import MagicMock import pytest -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr from llama_stack.core.request_headers import request_provider_data_context from llama_stack.providers.utils.inference.litellm_openai_mixin import LiteLLMOpenAIMixin @@ -16,11 +16,11 @@ from llama_stack.providers.utils.inference.litellm_openai_mixin import LiteLLMOp # Test fixtures and helper classes class TestConfig(BaseModel): - api_key: str | None = Field(default=None) + api_key: SecretStr | None = Field(default=None) class TestProviderDataValidator(BaseModel): - test_api_key: str | None = Field(default=None) + test_api_key: SecretStr | None = Field(default=None) class TestLiteLLMAdapter(LiteLLMOpenAIMixin): @@ -36,7 +36,7 @@ class TestLiteLLMAdapter(LiteLLMOpenAIMixin): @pytest.fixture def adapter_with_config_key(): """Fixture to create adapter with API key in config""" - config = TestConfig(api_key="config-api-key") + config = TestConfig(api_key=SecretStr("config-api-key")) adapter = TestLiteLLMAdapter(config) adapter.__provider_spec__ = MagicMock() adapter.__provider_spec__.provider_data_validator = ( @@ -59,7 +59,7 @@ def adapter_without_config_key(): def test_api_key_from_config_when_no_provider_data(adapter_with_config_key): """Test that adapter uses config API key when no provider data is available""" - api_key = adapter_with_config_key.get_api_key() + api_key = adapter_with_config_key.get_api_key().get_secret_value() assert api_key == "config-api-key" @@ -68,28 +68,28 @@ def test_provider_data_takes_priority_over_config(adapter_with_config_key): with request_provider_data_context( {"x-llamastack-provider-data": json.dumps({"test_api_key": "provider-data-key"})} ): - api_key = adapter_with_config_key.get_api_key() + api_key = adapter_with_config_key.get_api_key().get_secret_value() assert api_key == "provider-data-key" def test_fallback_to_config_when_provider_data_missing_key(adapter_with_config_key): """Test fallback to config when provider data doesn't have the required key""" with request_provider_data_context({"x-llamastack-provider-data": json.dumps({"wrong_key": "some-value"})}): - api_key = adapter_with_config_key.get_api_key() + api_key = adapter_with_config_key.get_api_key().get_secret_value() assert api_key == "config-api-key" def test_error_when_no_api_key_available(adapter_without_config_key): """Test that ValueError is raised when neither config nor provider data have API key""" with pytest.raises(ValueError, match="API key is not set"): - adapter_without_config_key.get_api_key() + adapter_without_config_key.get_api_key().get_secret_value() def test_error_when_provider_data_has_wrong_key(adapter_without_config_key): """Test that ValueError is raised when provider data exists but doesn't have required key""" with request_provider_data_context({"x-llamastack-provider-data": json.dumps({"wrong_key": "some-value"})}): with pytest.raises(ValueError, match="API key is not set"): - adapter_without_config_key.get_api_key() + adapter_without_config_key.get_api_key().get_secret_value() def test_provider_data_works_when_config_is_none(adapter_without_config_key): @@ -97,14 +97,14 @@ def test_provider_data_works_when_config_is_none(adapter_without_config_key): with request_provider_data_context( {"x-llamastack-provider-data": json.dumps({"test_api_key": "provider-only-key"})} ): - api_key = adapter_without_config_key.get_api_key() + api_key = adapter_without_config_key.get_api_key().get_secret_value() assert api_key == "provider-only-key" def test_error_message_includes_correct_field_names(adapter_without_config_key): """Test that error message includes correct field name and header information""" try: - adapter_without_config_key.get_api_key() + adapter_without_config_key.get_api_key().get_secret_value() raise AssertionError("Should have raised ValueError") except ValueError as e: assert "test_api_key" in str(e) # Should mention the correct field name diff --git a/tests/unit/providers/inference/test_openai_base_url_config.py b/tests/unit/providers/inference/test_openai_base_url_config.py index 903772f0c..7bc908069 100644 --- a/tests/unit/providers/inference/test_openai_base_url_config.py +++ b/tests/unit/providers/inference/test_openai_base_url_config.py @@ -7,6 +7,8 @@ import os from unittest.mock import MagicMock, patch +from pydantic import SecretStr + from llama_stack.core.stack import replace_env_vars from llama_stack.providers.remote.inference.openai.config import OpenAIConfig from llama_stack.providers.remote.inference.openai.openai import OpenAIInferenceAdapter @@ -59,14 +61,14 @@ class TestOpenAIBaseURLConfig: adapter = OpenAIInferenceAdapter(config) # Mock the get_api_key method since it's delegated to LiteLLMOpenAIMixin - adapter.get_api_key = MagicMock(return_value="test-key") + adapter.get_api_key = MagicMock(return_value=SecretStr("test-key")) # Access the client property to trigger AsyncOpenAI initialization _ = adapter.client # Verify AsyncOpenAI was called with the correct base_url mock_openai_class.assert_called_once_with( - api_key="test-key", + api_key=SecretStr("test-key"), base_url=custom_url, ) @@ -78,7 +80,7 @@ class TestOpenAIBaseURLConfig: adapter = OpenAIInferenceAdapter(config) # Mock the get_api_key method - adapter.get_api_key = MagicMock(return_value="test-key") + adapter.get_api_key = MagicMock(return_value=SecretStr("test-key")) # Mock a model object that will be returned by models.list() mock_model = MagicMock() @@ -101,7 +103,7 @@ class TestOpenAIBaseURLConfig: # Verify the client was created with the custom URL mock_openai_class.assert_called_with( - api_key="test-key", + api_key=SecretStr("test-key"), base_url=custom_url, ) @@ -119,7 +121,7 @@ class TestOpenAIBaseURLConfig: adapter = OpenAIInferenceAdapter(config) # Mock the get_api_key method - adapter.get_api_key = MagicMock(return_value="test-key") + adapter.get_api_key = MagicMock(return_value=SecretStr("test-key")) # Mock a model object that will be returned by models.list() mock_model = MagicMock() @@ -142,6 +144,6 @@ class TestOpenAIBaseURLConfig: # Verify the client was created with the environment variable URL mock_openai_class.assert_called_with( - api_key="test-key", + api_key=SecretStr("test-key"), base_url="https://proxy.openai.com/v1", ) From 4af141292fe96f349f27fb4b757b600dca59ce45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Mon, 22 Sep 2025 14:21:10 +0200 Subject: [PATCH 2/4] chore: use empty SecretStr values as default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Better than using SecretStr | None so we centralize the null handling. Signed-off-by: Sébastien Han --- docs/docs/providers/files/remote_s3.mdx | 2 +- docs/docs/providers/inference/remote_anthropic.mdx | 2 +- docs/docs/providers/inference/remote_bedrock.mdx | 4 ++-- docs/docs/providers/inference/remote_fireworks.mdx | 2 +- docs/docs/providers/inference/remote_gemini.mdx | 2 +- docs/docs/providers/inference/remote_groq.mdx | 2 +- .../providers/inference/remote_llama-openai-compat.mdx | 2 +- docs/docs/providers/inference/remote_nvidia.mdx | 2 +- docs/docs/providers/inference/remote_openai.mdx | 2 +- docs/docs/providers/inference/remote_passthrough.mdx | 2 +- docs/docs/providers/inference/remote_runpod.mdx | 2 +- docs/docs/providers/inference/remote_sambanova.mdx | 2 +- docs/docs/providers/inference/remote_together.mdx | 2 +- docs/docs/providers/inference/remote_vllm.mdx | 2 +- docs/docs/providers/inference/remote_watsonx.mdx | 2 +- docs/docs/providers/safety/remote_bedrock.mdx | 4 ++-- docs/docs/providers/safety/remote_sambanova.mdx | 2 +- docs/docs/providers/scoring/inline_braintrust.mdx | 2 +- docs/docs/providers/tool_runtime/remote_bing-search.mdx | 2 +- docs/docs/providers/tool_runtime/remote_brave-search.mdx | 2 +- docs/docs/providers/tool_runtime/remote_tavily-search.mdx | 2 +- docs/docs/providers/vector_io/remote_pgvector.mdx | 2 +- llama_stack/providers/inline/scoring/braintrust/config.py | 4 ++-- llama_stack/providers/remote/files/s3/config.py | 4 ++-- .../providers/remote/inference/anthropic/config.py | 8 ++++---- llama_stack/providers/remote/inference/azure/azure.py | 2 +- llama_stack/providers/remote/inference/cerebras/config.py | 4 ++++ .../providers/remote/inference/fireworks/config.py | 4 ++-- llama_stack/providers/remote/inference/gemini/config.py | 8 ++++---- llama_stack/providers/remote/inference/groq/config.py | 8 ++++---- .../remote/inference/llama_openai_compat/config.py | 8 ++++---- llama_stack/providers/remote/inference/nvidia/config.py | 4 ++-- llama_stack/providers/remote/inference/openai/config.py | 8 ++++---- .../providers/remote/inference/passthrough/config.py | 4 ++-- llama_stack/providers/remote/inference/runpod/config.py | 4 ++-- .../providers/remote/inference/sambanova/config.py | 8 ++++---- llama_stack/providers/remote/inference/together/config.py | 4 ++-- .../providers/remote/inference/vertexai/vertexai.py | 6 +++--- llama_stack/providers/remote/inference/vllm/__init__.py | 7 +++++-- llama_stack/providers/remote/inference/vllm/config.py | 4 ++-- llama_stack/providers/remote/inference/vllm/vllm.py | 2 +- llama_stack/providers/remote/inference/watsonx/config.py | 4 ++-- llama_stack/providers/remote/safety/sambanova/config.py | 8 ++++---- .../providers/remote/tool_runtime/bing_search/config.py | 7 +++++-- .../providers/remote/tool_runtime/brave_search/config.py | 4 ++-- .../providers/remote/tool_runtime/tavily_search/config.py | 4 ++-- llama_stack/providers/remote/vector_io/pgvector/config.py | 2 +- llama_stack/providers/utils/bedrock/config.py | 8 ++++---- .../providers/utils/inference/litellm_openai_mixin.py | 2 +- llama_stack/providers/utils/kvstore/config.py | 4 ++-- llama_stack/providers/utils/sqlstore/sqlstore.py | 4 ++-- 51 files changed, 103 insertions(+), 93 deletions(-) diff --git a/docs/docs/providers/files/remote_s3.mdx b/docs/docs/providers/files/remote_s3.mdx index a065da5f8..adf4bced0 100644 --- a/docs/docs/providers/files/remote_s3.mdx +++ b/docs/docs/providers/files/remote_s3.mdx @@ -17,7 +17,7 @@ AWS S3-based file storage provider for scalable cloud file management with metad | `bucket_name` | `` | No | | S3 bucket name to store files | | `region` | `` | No | us-east-1 | AWS region where the bucket is located | | `aws_access_key_id` | `str \| None` | No | | AWS access key ID (optional if using IAM roles) | -| `aws_secret_access_key` | `pydantic.types.SecretStr \| None` | No | | AWS secret access key (optional if using IAM roles) | +| `aws_secret_access_key` | `` | No | | AWS secret access key (optional if using IAM roles) | | `endpoint_url` | `str \| None` | No | | Custom S3 endpoint URL (for MinIO, LocalStack, etc.) | | `auto_create_bucket` | `` | No | False | Automatically create the S3 bucket if it doesn't exist | | `metadata_store` | `utils.sqlstore.sqlstore.SqliteSqlStoreConfig \| utils.sqlstore.sqlstore.PostgresSqlStoreConfig` | No | sqlite | SQL store configuration for file metadata | diff --git a/docs/docs/providers/inference/remote_anthropic.mdx b/docs/docs/providers/inference/remote_anthropic.mdx index c8115c7a8..f795ad3f1 100644 --- a/docs/docs/providers/inference/remote_anthropic.mdx +++ b/docs/docs/providers/inference/remote_anthropic.mdx @@ -14,7 +14,7 @@ Anthropic inference provider for accessing Claude models and Anthropic's AI serv | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `pydantic.types.SecretStr \| None` | No | | API key for Anthropic models | +| `api_key` | `` | No | | API key for Anthropic models | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_bedrock.mdx b/docs/docs/providers/inference/remote_bedrock.mdx index 3a4c296cf..ff1ed5ad8 100644 --- a/docs/docs/providers/inference/remote_bedrock.mdx +++ b/docs/docs/providers/inference/remote_bedrock.mdx @@ -15,8 +15,8 @@ AWS Bedrock inference provider for accessing various AI models through AWS's man | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `aws_access_key_id` | `str \| None` | No | | The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID | -| `aws_secret_access_key` | `pydantic.types.SecretStr \| None` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | -| `aws_session_token` | `pydantic.types.SecretStr \| None` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | +| `aws_secret_access_key` | `` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | +| `aws_session_token` | `` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | | `region_name` | `str \| None` | No | | The default AWS Region to use, for example, us-west-1 or us-west-2.Default use environment variable: AWS_DEFAULT_REGION | | `profile_name` | `str \| None` | No | | The profile name that contains credentials to use.Default use environment variable: AWS_PROFILE | | `total_max_attempts` | `int \| None` | No | | An integer representing the maximum number of attempts that will be made for a single request, including the initial attempt. Default use environment variable: AWS_MAX_ATTEMPTS | diff --git a/docs/docs/providers/inference/remote_fireworks.mdx b/docs/docs/providers/inference/remote_fireworks.mdx index d2c3a664e..0f37ccbc2 100644 --- a/docs/docs/providers/inference/remote_fireworks.mdx +++ b/docs/docs/providers/inference/remote_fireworks.mdx @@ -16,7 +16,7 @@ Fireworks AI inference provider for Llama models and other AI models on the Fire |-------|------|----------|---------|-------------| | `allowed_models` | `list[str \| None` | No | | List of models that should be registered with the model registry. If None, all models are allowed. | | `url` | `` | No | https://api.fireworks.ai/inference/v1 | The URL for the Fireworks server | -| `api_key` | `pydantic.types.SecretStr \| None` | No | | The Fireworks.ai API Key | +| `api_key` | `` | No | | The Fireworks.ai API Key | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_gemini.mdx b/docs/docs/providers/inference/remote_gemini.mdx index ce7c797c4..d9a2f3e8d 100644 --- a/docs/docs/providers/inference/remote_gemini.mdx +++ b/docs/docs/providers/inference/remote_gemini.mdx @@ -14,7 +14,7 @@ Google Gemini inference provider for accessing Gemini models and Google's AI ser | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `pydantic.types.SecretStr \| None` | No | | API key for Gemini models | +| `api_key` | `` | No | | API key for Gemini models | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_groq.mdx b/docs/docs/providers/inference/remote_groq.mdx index c2745ed98..b6d29496e 100644 --- a/docs/docs/providers/inference/remote_groq.mdx +++ b/docs/docs/providers/inference/remote_groq.mdx @@ -14,7 +14,7 @@ Groq inference provider for ultra-fast inference using Groq's LPU technology. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `pydantic.types.SecretStr \| None` | No | | The Groq API key | +| `api_key` | `` | No | | The Groq API key | | `url` | `` | No | https://api.groq.com | The URL for the Groq AI server | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_llama-openai-compat.mdx b/docs/docs/providers/inference/remote_llama-openai-compat.mdx index b7b0204e6..491774844 100644 --- a/docs/docs/providers/inference/remote_llama-openai-compat.mdx +++ b/docs/docs/providers/inference/remote_llama-openai-compat.mdx @@ -14,7 +14,7 @@ Llama OpenAI-compatible provider for using Llama models with OpenAI API format. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `pydantic.types.SecretStr \| None` | No | | The Llama API key | +| `api_key` | `` | No | | The Llama API key | | `openai_compat_api_base` | `` | No | https://api.llama.com/compat/v1/ | The URL for the Llama API server | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_nvidia.mdx b/docs/docs/providers/inference/remote_nvidia.mdx index 4a8be5d03..9aee22e9a 100644 --- a/docs/docs/providers/inference/remote_nvidia.mdx +++ b/docs/docs/providers/inference/remote_nvidia.mdx @@ -15,7 +15,7 @@ NVIDIA inference provider for accessing NVIDIA NIM models and AI services. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://integrate.api.nvidia.com | A base url for accessing the NVIDIA NIM | -| `api_key` | `pydantic.types.SecretStr \| None` | No | | The NVIDIA API key, only needed of using the hosted service | +| `api_key` | `` | No | | The NVIDIA API key, only needed of using the hosted service | | `timeout` | `` | No | 60 | Timeout for the HTTP requests | | `append_api_version` | `` | No | True | When set to false, the API version will not be appended to the base_url. By default, it is true. | diff --git a/docs/docs/providers/inference/remote_openai.mdx b/docs/docs/providers/inference/remote_openai.mdx index 2c7650bb5..f82bea154 100644 --- a/docs/docs/providers/inference/remote_openai.mdx +++ b/docs/docs/providers/inference/remote_openai.mdx @@ -14,7 +14,7 @@ OpenAI inference provider for accessing GPT models and other OpenAI services. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `pydantic.types.SecretStr \| None` | No | | API key for OpenAI models | +| `api_key` | `` | No | | API key for OpenAI models | | `base_url` | `` | No | https://api.openai.com/v1 | Base URL for OpenAI API | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_passthrough.mdx b/docs/docs/providers/inference/remote_passthrough.mdx index 972cc2a08..efaa01d04 100644 --- a/docs/docs/providers/inference/remote_passthrough.mdx +++ b/docs/docs/providers/inference/remote_passthrough.mdx @@ -15,7 +15,7 @@ Passthrough inference provider for connecting to any external inference service | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | | The URL for the passthrough endpoint | -| `api_key` | `pydantic.types.SecretStr \| None` | No | | API Key for the passthrouth endpoint | +| `api_key` | `` | No | | API Key for the passthrouth endpoint | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_runpod.mdx b/docs/docs/providers/inference/remote_runpod.mdx index 5d3655474..1def462f6 100644 --- a/docs/docs/providers/inference/remote_runpod.mdx +++ b/docs/docs/providers/inference/remote_runpod.mdx @@ -15,7 +15,7 @@ RunPod inference provider for running models on RunPod's cloud GPU platform. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `str \| None` | No | | The URL for the Runpod model serving endpoint | -| `api_token` | `pydantic.types.SecretStr \| None` | No | | The API token | +| `api_token` | `` | No | | The API token | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_sambanova.mdx b/docs/docs/providers/inference/remote_sambanova.mdx index 6ee28b400..b5d64e6d9 100644 --- a/docs/docs/providers/inference/remote_sambanova.mdx +++ b/docs/docs/providers/inference/remote_sambanova.mdx @@ -15,7 +15,7 @@ SambaNova inference provider for running models on SambaNova's dataflow architec | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://api.sambanova.ai/v1 | The URL for the SambaNova AI server | -| `api_key` | `pydantic.types.SecretStr \| None` | No | | The SambaNova cloud API Key | +| `api_key` | `` | No | | The SambaNova cloud API Key | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_together.mdx b/docs/docs/providers/inference/remote_together.mdx index da232a45b..0669a1112 100644 --- a/docs/docs/providers/inference/remote_together.mdx +++ b/docs/docs/providers/inference/remote_together.mdx @@ -16,7 +16,7 @@ Together AI inference provider for open-source models and collaborative AI devel |-------|------|----------|---------|-------------| | `allowed_models` | `list[str \| None` | No | | List of models that should be registered with the model registry. If None, all models are allowed. | | `url` | `` | No | https://api.together.xyz/v1 | The URL for the Together AI server | -| `api_key` | `pydantic.types.SecretStr \| None` | No | | The Together AI API Key | +| `api_key` | `` | No | | The Together AI API Key | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_vllm.mdx b/docs/docs/providers/inference/remote_vllm.mdx index 1faf131b9..5f9af4db4 100644 --- a/docs/docs/providers/inference/remote_vllm.mdx +++ b/docs/docs/providers/inference/remote_vllm.mdx @@ -16,7 +16,7 @@ Remote vLLM inference provider for connecting to vLLM servers. |-------|------|----------|---------|-------------| | `url` | `str \| None` | No | | The URL for the vLLM model serving endpoint | | `max_tokens` | `` | No | 4096 | Maximum number of tokens to generate. | -| `api_token` | `pydantic.types.SecretStr \| None` | No | ********** | The API token | +| `api_token` | `` | No | | The API token | | `tls_verify` | `bool \| str` | No | True | Whether to verify TLS certificates. Can be a boolean or a path to a CA certificate file. | | `refresh_models` | `` | No | False | Whether to refresh models periodically | diff --git a/docs/docs/providers/inference/remote_watsonx.mdx b/docs/docs/providers/inference/remote_watsonx.mdx index 1ceccc3ed..c1ab57a7b 100644 --- a/docs/docs/providers/inference/remote_watsonx.mdx +++ b/docs/docs/providers/inference/remote_watsonx.mdx @@ -15,7 +15,7 @@ IBM WatsonX inference provider for accessing AI models on IBM's WatsonX platform | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://us-south.ml.cloud.ibm.com | A base url for accessing the watsonx.ai | -| `api_key` | `pydantic.types.SecretStr \| None` | No | | The watsonx API key | +| `api_key` | `` | No | | The watsonx API key | | `project_id` | `str \| None` | No | | The Project ID key | | `timeout` | `` | No | 60 | Timeout for the HTTP requests | diff --git a/docs/docs/providers/safety/remote_bedrock.mdx b/docs/docs/providers/safety/remote_bedrock.mdx index 85bc662e8..e068f1fed 100644 --- a/docs/docs/providers/safety/remote_bedrock.mdx +++ b/docs/docs/providers/safety/remote_bedrock.mdx @@ -15,8 +15,8 @@ AWS Bedrock safety provider for content moderation using AWS's safety services. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `aws_access_key_id` | `str \| None` | No | | The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID | -| `aws_secret_access_key` | `pydantic.types.SecretStr \| None` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | -| `aws_session_token` | `pydantic.types.SecretStr \| None` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | +| `aws_secret_access_key` | `` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | +| `aws_session_token` | `` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | | `region_name` | `str \| None` | No | | The default AWS Region to use, for example, us-west-1 or us-west-2.Default use environment variable: AWS_DEFAULT_REGION | | `profile_name` | `str \| None` | No | | The profile name that contains credentials to use.Default use environment variable: AWS_PROFILE | | `total_max_attempts` | `int \| None` | No | | An integer representing the maximum number of attempts that will be made for a single request, including the initial attempt. Default use environment variable: AWS_MAX_ATTEMPTS | diff --git a/docs/docs/providers/safety/remote_sambanova.mdx b/docs/docs/providers/safety/remote_sambanova.mdx index da70fce6c..3a5a0db7d 100644 --- a/docs/docs/providers/safety/remote_sambanova.mdx +++ b/docs/docs/providers/safety/remote_sambanova.mdx @@ -15,7 +15,7 @@ SambaNova's safety provider for content moderation and safety filtering. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://api.sambanova.ai/v1 | The URL for the SambaNova AI server | -| `api_key` | `pydantic.types.SecretStr \| None` | No | | The SambaNova cloud API Key | +| `api_key` | `` | No | | The SambaNova cloud API Key | ## Sample Configuration diff --git a/docs/docs/providers/scoring/inline_braintrust.mdx b/docs/docs/providers/scoring/inline_braintrust.mdx index 67de0667a..93779b7ac 100644 --- a/docs/docs/providers/scoring/inline_braintrust.mdx +++ b/docs/docs/providers/scoring/inline_braintrust.mdx @@ -14,7 +14,7 @@ Braintrust scoring provider for evaluation and scoring using the Braintrust plat | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `openai_api_key` | `pydantic.types.SecretStr \| None` | No | | The OpenAI API Key | +| `openai_api_key` | `` | No | | The OpenAI API Key | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_bing-search.mdx b/docs/docs/providers/tool_runtime/remote_bing-search.mdx index 3085ab9ec..b7dabdd47 100644 --- a/docs/docs/providers/tool_runtime/remote_bing-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_bing-search.mdx @@ -14,7 +14,7 @@ Bing Search tool for web search capabilities using Microsoft's search engine. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `pydantic.types.SecretStr \| None` | No | | | +| `api_key` | `` | No | | The Bing API key | | `top_k` | `` | No | 3 | | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_brave-search.mdx b/docs/docs/providers/tool_runtime/remote_brave-search.mdx index c71ac3d7a..084ae20f5 100644 --- a/docs/docs/providers/tool_runtime/remote_brave-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_brave-search.mdx @@ -14,7 +14,7 @@ Brave Search tool for web search capabilities with privacy-focused results. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `pydantic.types.SecretStr \| None` | No | | The Brave Search API Key | +| `api_key` | `` | No | | The Brave Search API Key | | `max_results` | `` | No | 3 | The maximum number of results to return | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_tavily-search.mdx b/docs/docs/providers/tool_runtime/remote_tavily-search.mdx index 0fe263a5b..1c3429983 100644 --- a/docs/docs/providers/tool_runtime/remote_tavily-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_tavily-search.mdx @@ -14,7 +14,7 @@ Tavily Search tool for AI-optimized web search with structured results. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `pydantic.types.SecretStr \| None` | No | | The Tavily Search API Key | +| `api_key` | `` | No | | The Tavily Search API Key | | `max_results` | `` | No | 3 | The maximum number of results to return | ## Sample Configuration diff --git a/docs/docs/providers/vector_io/remote_pgvector.mdx b/docs/docs/providers/vector_io/remote_pgvector.mdx index cf0c28f12..6d3157753 100644 --- a/docs/docs/providers/vector_io/remote_pgvector.mdx +++ b/docs/docs/providers/vector_io/remote_pgvector.mdx @@ -217,7 +217,7 @@ See [PGVector's documentation](https://github.com/pgvector/pgvector) for more de | `port` | `int \| None` | No | 5432 | | | `db` | `str \| None` | No | postgres | | | `user` | `str \| None` | No | postgres | | -| `password` | `pydantic.types.SecretStr \| None` | No | mysecretpassword | | +| `password` | `` | No | ********** | | | `kvstore` | `utils.kvstore.config.RedisKVStoreConfig \| utils.kvstore.config.SqliteKVStoreConfig \| utils.kvstore.config.PostgresKVStoreConfig \| utils.kvstore.config.MongoDBKVStoreConfig, annotation=NoneType, required=False, default='sqlite', discriminator='type'` | No | | Config for KV store backend (SQLite only for now) | ## Sample Configuration diff --git a/llama_stack/providers/inline/scoring/braintrust/config.py b/llama_stack/providers/inline/scoring/braintrust/config.py index 088674afd..a2a52d610 100644 --- a/llama_stack/providers/inline/scoring/braintrust/config.py +++ b/llama_stack/providers/inline/scoring/braintrust/config.py @@ -9,8 +9,8 @@ from pydantic import BaseModel, Field, SecretStr class BraintrustScoringConfig(BaseModel): - openai_api_key: SecretStr | None = Field( - default=None, + openai_api_key: SecretStr = Field( + default=SecretStr(""), description="The OpenAI API Key", ) diff --git a/llama_stack/providers/remote/files/s3/config.py b/llama_stack/providers/remote/files/s3/config.py index 5cddd2b38..b7935902d 100644 --- a/llama_stack/providers/remote/files/s3/config.py +++ b/llama_stack/providers/remote/files/s3/config.py @@ -17,8 +17,8 @@ class S3FilesImplConfig(BaseModel): bucket_name: str = Field(description="S3 bucket name to store files") region: str = Field(default="us-east-1", description="AWS region where the bucket is located") aws_access_key_id: str | None = Field(default=None, description="AWS access key ID (optional if using IAM roles)") - aws_secret_access_key: SecretStr | None = Field( - default=None, description="AWS secret access key (optional if using IAM roles)" + aws_secret_access_key: SecretStr = Field( + default=SecretStr(""), description="AWS secret access key (optional if using IAM roles)" ) endpoint_url: str | None = Field(default=None, description="Custom S3 endpoint URL (for MinIO, LocalStack, etc.)") auto_create_bucket: bool = Field( diff --git a/llama_stack/providers/remote/inference/anthropic/config.py b/llama_stack/providers/remote/inference/anthropic/config.py index ae3034219..c28f05d24 100644 --- a/llama_stack/providers/remote/inference/anthropic/config.py +++ b/llama_stack/providers/remote/inference/anthropic/config.py @@ -12,16 +12,16 @@ from llama_stack.schema_utils import json_schema_type class AnthropicProviderDataValidator(BaseModel): - anthropic_api_key: SecretStr | None = Field( - default=None, + anthropic_api_key: SecretStr = Field( + default=SecretStr(""), description="API key for Anthropic models", ) @json_schema_type class AnthropicConfig(BaseModel): - api_key: SecretStr | None = Field( - default=None, + api_key: SecretStr = Field( + default=SecretStr(""), description="API key for Anthropic models", ) diff --git a/llama_stack/providers/remote/inference/azure/azure.py b/llama_stack/providers/remote/inference/azure/azure.py index a2c69b69c..5d650cc29 100644 --- a/llama_stack/providers/remote/inference/azure/azure.py +++ b/llama_stack/providers/remote/inference/azure/azure.py @@ -21,7 +21,7 @@ class AzureInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): LiteLLMOpenAIMixin.__init__( self, litellm_provider_name="azure", - api_key_from_config=config.api_key.get_secret_value(), + api_key_from_config=config.api_key, provider_data_api_key_field="azure_api_key", openai_compat_api_base=str(config.api_base), ) diff --git a/llama_stack/providers/remote/inference/cerebras/config.py b/llama_stack/providers/remote/inference/cerebras/config.py index 519bd9119..0b737ea6c 100644 --- a/llama_stack/providers/remote/inference/cerebras/config.py +++ b/llama_stack/providers/remote/inference/cerebras/config.py @@ -21,7 +21,11 @@ class CerebrasImplConfig(BaseModel): description="Base URL for the Cerebras API", ) api_key: SecretStr = Field( +<<<<<<< HEAD default=SecretStr(os.environ.get("CEREBRAS_API_KEY")), +======= + default=SecretStr(os.environ.get("CEREBRAS_API_KEY", "")), +>>>>>>> a48f2009 (chore: use empty SecretStr values as default) description="Cerebras API Key", ) diff --git a/llama_stack/providers/remote/inference/fireworks/config.py b/llama_stack/providers/remote/inference/fireworks/config.py index cd28096a5..5bf0fcd88 100644 --- a/llama_stack/providers/remote/inference/fireworks/config.py +++ b/llama_stack/providers/remote/inference/fireworks/config.py @@ -18,8 +18,8 @@ class FireworksImplConfig(RemoteInferenceProviderConfig): default="https://api.fireworks.ai/inference/v1", description="The URL for the Fireworks server", ) - api_key: SecretStr | None = Field( - default=None, + api_key: SecretStr = Field( + default=SecretStr(""), description="The Fireworks.ai API Key", ) diff --git a/llama_stack/providers/remote/inference/gemini/config.py b/llama_stack/providers/remote/inference/gemini/config.py index 27897965c..4a69bd064 100644 --- a/llama_stack/providers/remote/inference/gemini/config.py +++ b/llama_stack/providers/remote/inference/gemini/config.py @@ -12,16 +12,16 @@ from llama_stack.schema_utils import json_schema_type class GeminiProviderDataValidator(BaseModel): - gemini_api_key: SecretStr | None = Field( - default=None, + gemini_api_key: SecretStr = Field( + default=SecretStr(""), description="API key for Gemini models", ) @json_schema_type class GeminiConfig(BaseModel): - api_key: SecretStr | None = Field( - default=None, + api_key: SecretStr = Field( + default=SecretStr(""), description="API key for Gemini models", ) diff --git a/llama_stack/providers/remote/inference/groq/config.py b/llama_stack/providers/remote/inference/groq/config.py index 1011e8da9..efc1f437b 100644 --- a/llama_stack/providers/remote/inference/groq/config.py +++ b/llama_stack/providers/remote/inference/groq/config.py @@ -12,17 +12,17 @@ from llama_stack.schema_utils import json_schema_type class GroqProviderDataValidator(BaseModel): - groq_api_key: SecretStr | None = Field( - default=None, + groq_api_key: SecretStr = Field( + default=SecretStr(""), description="API key for Groq models", ) @json_schema_type class GroqConfig(BaseModel): - api_key: SecretStr | None = Field( + api_key: SecretStr = Field( # The Groq client library loads the GROQ_API_KEY environment variable by default - default=None, + default=SecretStr(""), description="The Groq API key", ) diff --git a/llama_stack/providers/remote/inference/llama_openai_compat/config.py b/llama_stack/providers/remote/inference/llama_openai_compat/config.py index 064cc860d..d20fed8e0 100644 --- a/llama_stack/providers/remote/inference/llama_openai_compat/config.py +++ b/llama_stack/providers/remote/inference/llama_openai_compat/config.py @@ -12,16 +12,16 @@ from llama_stack.schema_utils import json_schema_type class LlamaProviderDataValidator(BaseModel): - llama_api_key: SecretStr | None = Field( - default=None, + llama_api_key: SecretStr = Field( + default=SecretStr(""), description="API key for api.llama models", ) @json_schema_type class LlamaCompatConfig(BaseModel): - api_key: SecretStr | None = Field( - default=None, + api_key: SecretStr = Field( + default=SecretStr(""), description="The Llama API key", ) diff --git a/llama_stack/providers/remote/inference/nvidia/config.py b/llama_stack/providers/remote/inference/nvidia/config.py index e1b791719..9ca2ffdd6 100644 --- a/llama_stack/providers/remote/inference/nvidia/config.py +++ b/llama_stack/providers/remote/inference/nvidia/config.py @@ -39,8 +39,8 @@ class NVIDIAConfig(BaseModel): default_factory=lambda: os.getenv("NVIDIA_BASE_URL", "https://integrate.api.nvidia.com"), description="A base url for accessing the NVIDIA NIM", ) - api_key: SecretStr | None = Field( - default_factory=lambda: SecretStr(os.getenv("NVIDIA_API_KEY")), + api_key: SecretStr = Field( + default_factory=lambda: SecretStr(os.getenv("NVIDIA_API_KEY", "")), description="The NVIDIA API key, only needed of using the hosted service", ) timeout: int = Field( diff --git a/llama_stack/providers/remote/inference/openai/config.py b/llama_stack/providers/remote/inference/openai/config.py index 7de7bcc32..b6f5f1a55 100644 --- a/llama_stack/providers/remote/inference/openai/config.py +++ b/llama_stack/providers/remote/inference/openai/config.py @@ -12,16 +12,16 @@ from llama_stack.schema_utils import json_schema_type class OpenAIProviderDataValidator(BaseModel): - openai_api_key: SecretStr | None = Field( - default=None, + openai_api_key: SecretStr = Field( + default=SecretStr(""), description="API key for OpenAI models", ) @json_schema_type class OpenAIConfig(BaseModel): - api_key: SecretStr | None = Field( - default=None, + api_key: SecretStr = Field( + default=SecretStr(""), description="API key for OpenAI models", ) base_url: str = Field( diff --git a/llama_stack/providers/remote/inference/passthrough/config.py b/llama_stack/providers/remote/inference/passthrough/config.py index 647b2db46..f57ce0e8a 100644 --- a/llama_stack/providers/remote/inference/passthrough/config.py +++ b/llama_stack/providers/remote/inference/passthrough/config.py @@ -18,8 +18,8 @@ class PassthroughImplConfig(BaseModel): description="The URL for the passthrough endpoint", ) - api_key: SecretStr | None = Field( - default=None, + api_key: SecretStr = Field( + default=SecretStr(""), description="API Key for the passthrouth endpoint", ) diff --git a/llama_stack/providers/remote/inference/runpod/config.py b/llama_stack/providers/remote/inference/runpod/config.py index 60086acef..3c6c5afe9 100644 --- a/llama_stack/providers/remote/inference/runpod/config.py +++ b/llama_stack/providers/remote/inference/runpod/config.py @@ -17,8 +17,8 @@ class RunpodImplConfig(BaseModel): default=None, description="The URL for the Runpod model serving endpoint", ) - api_token: SecretStr | None = Field( - default=None, + api_token: SecretStr = Field( + default=SecretStr(""), description="The API token", ) diff --git a/llama_stack/providers/remote/inference/sambanova/config.py b/llama_stack/providers/remote/inference/sambanova/config.py index 50ad53d06..4637cb49e 100644 --- a/llama_stack/providers/remote/inference/sambanova/config.py +++ b/llama_stack/providers/remote/inference/sambanova/config.py @@ -12,8 +12,8 @@ from llama_stack.schema_utils import json_schema_type class SambaNovaProviderDataValidator(BaseModel): - sambanova_api_key: str | None = Field( - default=None, + sambanova_api_key: SecretStr = Field( + default=SecretStr(""), description="Sambanova Cloud API key", ) @@ -24,8 +24,8 @@ class SambaNovaImplConfig(BaseModel): default="https://api.sambanova.ai/v1", description="The URL for the SambaNova AI server", ) - api_key: SecretStr | None = Field( - default=None, + api_key: SecretStr = Field( + default=SecretStr(""), description="The SambaNova cloud API Key", ) diff --git a/llama_stack/providers/remote/inference/together/config.py b/llama_stack/providers/remote/inference/together/config.py index f6725333c..c15d42140 100644 --- a/llama_stack/providers/remote/inference/together/config.py +++ b/llama_stack/providers/remote/inference/together/config.py @@ -18,8 +18,8 @@ class TogetherImplConfig(RemoteInferenceProviderConfig): default="https://api.together.xyz/v1", description="The URL for the Together AI server", ) - api_key: SecretStr | None = Field( - default=None, + api_key: SecretStr = Field( + default=SecretStr(""), description="The Together AI API Key", ) diff --git a/llama_stack/providers/remote/inference/vertexai/vertexai.py b/llama_stack/providers/remote/inference/vertexai/vertexai.py index 733f2ab4a..581a00f29 100644 --- a/llama_stack/providers/remote/inference/vertexai/vertexai.py +++ b/llama_stack/providers/remote/inference/vertexai/vertexai.py @@ -24,12 +24,12 @@ class VertexAIInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): LiteLLMOpenAIMixin.__init__( self, litellm_provider_name="vertex_ai", - api_key_from_config=None, # Vertex AI uses ADC, not API keys + api_key_from_config=SecretStr(""), # Vertex AI uses ADC, not API keys provider_data_api_key_field="vertex_project", # Use project for validation ) self.config = config - def get_api_key(self) -> str: + def get_api_key(self) -> SecretStr: """ Get an access token for Vertex AI using Application Default Credentials. @@ -40,7 +40,7 @@ class VertexAIInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): # Get default credentials - will read from GOOGLE_APPLICATION_CREDENTIALS credentials, _ = default(scopes=["https://www.googleapis.com/auth/cloud-platform"]) credentials.refresh(google.auth.transport.requests.Request()) - return str(credentials.token) + return SecretStr(credentials.token) except Exception: # If we can't get credentials, return empty string to let LiteLLM handle it # This allows the LiteLLM mixin to work with ADC directly diff --git a/llama_stack/providers/remote/inference/vllm/__init__.py b/llama_stack/providers/remote/inference/vllm/__init__.py index 1f196e507..5b3ec6d5b 100644 --- a/llama_stack/providers/remote/inference/vllm/__init__.py +++ b/llama_stack/providers/remote/inference/vllm/__init__.py @@ -4,13 +4,16 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -from pydantic import BaseModel +from pydantic import BaseModel, Field, SecretStr from .config import VLLMInferenceAdapterConfig class VLLMProviderDataValidator(BaseModel): - vllm_api_token: str | None = None + vllm_api_token: SecretStr = Field( + default=SecretStr(""), + description="API token for vLLM models", + ) async def get_adapter_impl(config: VLLMInferenceAdapterConfig, _deps): diff --git a/llama_stack/providers/remote/inference/vllm/config.py b/llama_stack/providers/remote/inference/vllm/config.py index 978427930..708a39be1 100644 --- a/llama_stack/providers/remote/inference/vllm/config.py +++ b/llama_stack/providers/remote/inference/vllm/config.py @@ -21,8 +21,8 @@ class VLLMInferenceAdapterConfig(BaseModel): default=4096, description="Maximum number of tokens to generate.", ) - api_token: SecretStr | None = Field( - default=SecretStr("fake"), + api_token: SecretStr = Field( + default=SecretStr(""), description="The API token", ) tls_verify: bool | str = Field( diff --git a/llama_stack/providers/remote/inference/vllm/vllm.py b/llama_stack/providers/remote/inference/vllm/vllm.py index 2e2e1ea5c..8fbb4b815 100644 --- a/llama_stack/providers/remote/inference/vllm/vllm.py +++ b/llama_stack/providers/remote/inference/vllm/vllm.py @@ -294,7 +294,7 @@ class VLLMInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin, Inference, ModelsPro self, model_entries=build_hf_repo_model_entries(), litellm_provider_name="vllm", - api_key_from_config=config.api_token.get_secret_value(), + api_key_from_config=config.api_token, provider_data_api_key_field="vllm_api_token", openai_compat_api_base=config.url, ) diff --git a/llama_stack/providers/remote/inference/watsonx/config.py b/llama_stack/providers/remote/inference/watsonx/config.py index 42c25d93e..a28de1226 100644 --- a/llama_stack/providers/remote/inference/watsonx/config.py +++ b/llama_stack/providers/remote/inference/watsonx/config.py @@ -24,8 +24,8 @@ class WatsonXConfig(BaseModel): default_factory=lambda: os.getenv("WATSONX_BASE_URL", "https://us-south.ml.cloud.ibm.com"), description="A base url for accessing the watsonx.ai", ) - api_key: SecretStr | None = Field( - default_factory=lambda: os.getenv("WATSONX_API_KEY"), + api_key: SecretStr = Field( + default_factory=lambda: SecretStr(os.getenv("WATSONX_API_KEY", "")), description="The watsonx API key", ) project_id: str | None = Field( diff --git a/llama_stack/providers/remote/safety/sambanova/config.py b/llama_stack/providers/remote/safety/sambanova/config.py index 2cde97098..814e5b1e5 100644 --- a/llama_stack/providers/remote/safety/sambanova/config.py +++ b/llama_stack/providers/remote/safety/sambanova/config.py @@ -12,8 +12,8 @@ from llama_stack.schema_utils import json_schema_type class SambaNovaProviderDataValidator(BaseModel): - sambanova_api_key: str | None = Field( - default=None, + sambanova_api_key: SecretStr = Field( + default=SecretStr(""), description="Sambanova Cloud API key", ) @@ -24,8 +24,8 @@ class SambaNovaSafetyConfig(BaseModel): default="https://api.sambanova.ai/v1", description="The URL for the SambaNova AI server", ) - api_key: SecretStr | None = Field( - default=None, + api_key: SecretStr = Field( + default=SecretStr(""), description="The SambaNova cloud API Key", ) diff --git a/llama_stack/providers/remote/tool_runtime/bing_search/config.py b/llama_stack/providers/remote/tool_runtime/bing_search/config.py index 3c4852fba..8ac4358ba 100644 --- a/llama_stack/providers/remote/tool_runtime/bing_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/bing_search/config.py @@ -6,13 +6,16 @@ from typing import Any -from pydantic import BaseModel, SecretStr +from pydantic import BaseModel, Field, SecretStr class BingSearchToolConfig(BaseModel): """Configuration for Bing Search Tool Runtime""" - api_key: SecretStr | None = None + api_key: SecretStr = Field( + default=SecretStr(""), + description="The Bing API key", + ) top_k: int = 3 @classmethod diff --git a/llama_stack/providers/remote/tool_runtime/brave_search/config.py b/llama_stack/providers/remote/tool_runtime/brave_search/config.py index 41f6c9bbc..ddc711c12 100644 --- a/llama_stack/providers/remote/tool_runtime/brave_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/brave_search/config.py @@ -10,8 +10,8 @@ from pydantic import BaseModel, Field, SecretStr class BraveSearchToolConfig(BaseModel): - api_key: SecretStr | None = Field( - default=None, + api_key: SecretStr = Field( + default=SecretStr(""), description="The Brave Search API Key", ) max_results: int = Field( diff --git a/llama_stack/providers/remote/tool_runtime/tavily_search/config.py b/llama_stack/providers/remote/tool_runtime/tavily_search/config.py index f81b3455a..c0de93114 100644 --- a/llama_stack/providers/remote/tool_runtime/tavily_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/tavily_search/config.py @@ -10,8 +10,8 @@ from pydantic import BaseModel, Field, SecretStr class TavilySearchToolConfig(BaseModel): - api_key: SecretStr | None = Field( - default=None, + api_key: SecretStr = Field( + default=SecretStr(""), description="The Tavily Search API Key", ) max_results: int = Field( diff --git a/llama_stack/providers/remote/vector_io/pgvector/config.py b/llama_stack/providers/remote/vector_io/pgvector/config.py index abfa0eacf..1c6d0ed52 100644 --- a/llama_stack/providers/remote/vector_io/pgvector/config.py +++ b/llama_stack/providers/remote/vector_io/pgvector/config.py @@ -21,7 +21,7 @@ class PGVectorVectorIOConfig(BaseModel): port: int | None = Field(default=5432) db: str | None = Field(default="postgres") user: str | None = Field(default="postgres") - password: SecretStr | None = Field(default="mysecretpassword") + password: SecretStr = Field(default=SecretStr("mysecretpassword")) kvstore: KVStoreConfig | None = Field(description="Config for KV store backend (SQLite only for now)", default=None) @classmethod diff --git a/llama_stack/providers/utils/bedrock/config.py b/llama_stack/providers/utils/bedrock/config.py index b48f0ea69..2a5c8e882 100644 --- a/llama_stack/providers/utils/bedrock/config.py +++ b/llama_stack/providers/utils/bedrock/config.py @@ -14,12 +14,12 @@ class BedrockBaseConfig(BaseModel): default_factory=lambda: os.getenv("AWS_ACCESS_KEY_ID"), description="The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID", ) - aws_secret_access_key: SecretStr | None = Field( - default_factory=lambda: SecretStr(val) if (val := os.getenv("AWS_SECRET_ACCESS_KEY")) else None, + aws_secret_access_key: SecretStr = Field( + default_factory=lambda: SecretStr(os.getenv("AWS_SECRET_ACCESS_KEY", "")), description="The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY", ) - aws_session_token: SecretStr | None = Field( - default_factory=lambda: SecretStr(val) if (val := os.getenv("AWS_SESSION_TOKEN")) else None, + aws_session_token: SecretStr = Field( + default_factory=lambda: SecretStr(os.getenv("AWS_SESSION_TOKEN", "")), description="The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN", ) region_name: str | None = Field( diff --git a/llama_stack/providers/utils/inference/litellm_openai_mixin.py b/llama_stack/providers/utils/inference/litellm_openai_mixin.py index fc39852ae..8bdfbbbc9 100644 --- a/llama_stack/providers/utils/inference/litellm_openai_mixin.py +++ b/llama_stack/providers/utils/inference/litellm_openai_mixin.py @@ -69,7 +69,7 @@ class LiteLLMOpenAIMixin( def __init__( self, litellm_provider_name: str, - api_key_from_config: SecretStr | None, + api_key_from_config: SecretStr, provider_data_api_key_field: str, model_entries: list[ProviderModelEntry] | None = None, openai_compat_api_base: str | None = None, diff --git a/llama_stack/providers/utils/kvstore/config.py b/llama_stack/providers/utils/kvstore/config.py index 1cd90d308..baab4e372 100644 --- a/llama_stack/providers/utils/kvstore/config.py +++ b/llama_stack/providers/utils/kvstore/config.py @@ -74,7 +74,7 @@ class PostgresKVStoreConfig(CommonConfig): port: int = 5432 db: str = "llamastack" user: str - password: SecretStr | None = None + password: SecretStr = SecretStr("") ssl_mode: str | None = None ca_cert_path: str | None = None table_name: str = "llamastack_kvstore" @@ -118,7 +118,7 @@ class MongoDBKVStoreConfig(CommonConfig): port: int = 27017 db: str = "llamastack" user: str | None = None - password: SecretStr | None = None + password: SecretStr = SecretStr("") collection_name: str = "llamastack_kvstore" @classmethod diff --git a/llama_stack/providers/utils/sqlstore/sqlstore.py b/llama_stack/providers/utils/sqlstore/sqlstore.py index 3f6bedc7e..6eaafccfe 100644 --- a/llama_stack/providers/utils/sqlstore/sqlstore.py +++ b/llama_stack/providers/utils/sqlstore/sqlstore.py @@ -63,11 +63,11 @@ class PostgresSqlStoreConfig(SqlAlchemySqlStoreConfig): port: int = 5432 db: str = "llamastack" user: str - password: SecretStr | None = None + password: SecretStr = SecretStr("") @property def engine_str(self) -> str: - return f"postgresql+asyncpg://{self.user}:{self.password.get_secret_value() if self.password else ''}@{self.host}:{self.port}/{self.db}" + return f"postgresql+asyncpg://{self.user}:{self.password.get_secret_value()}@{self.host}:{self.port}/{self.db}" @classmethod def pip_packages(cls) -> list[str]: From bc64635835ab047cc3bf968d69329de9d83f2f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 25 Sep 2025 10:27:41 +0200 Subject: [PATCH 3/4] feat: load config class when doing variable substitution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using bash style substitution env variable in distribution template, we are processing the string and convert it to the type associated with the provider's config class. This allows us to return the proper type. This is crucial for api key since they are not strings anymore but SecretStr. If the key is unset we will get an empty string which will result in a Pydantic error like: ``` ERROR 2025-09-25 21:40:44,565 __main__:527 core::server: Error creating app: 1 validation error for AnthropicConfig api_key Input should be a valid string For further information visit https://errors.pydantic.dev/2.11/v/string_type ``` Signed-off-by: Sébastien Han --- .../providers/datasetio/remote_nvidia.mdx | 2 +- docs/docs/providers/files/remote_s3.mdx | 2 +- .../providers/inference/remote_anthropic.mdx | 2 +- .../docs/providers/inference/remote_azure.mdx | 2 +- .../providers/inference/remote_bedrock.mdx | 4 +- .../providers/inference/remote_cerebras.mdx | 2 +- .../providers/inference/remote_databricks.mdx | 2 +- .../providers/inference/remote_fireworks.mdx | 2 +- .../providers/inference/remote_gemini.mdx | 2 +- docs/docs/providers/inference/remote_groq.mdx | 2 +- .../inference/remote_hf_endpoint.mdx | 2 +- .../inference/remote_hf_serverless.mdx | 2 +- .../inference/remote_llama-openai-compat.mdx | 2 +- .../providers/inference/remote_nvidia.mdx | 2 +- .../providers/inference/remote_openai.mdx | 2 +- .../inference/remote_passthrough.mdx | 2 +- .../providers/inference/remote_runpod.mdx | 2 +- .../providers/inference/remote_sambanova.mdx | 2 +- .../providers/inference/remote_together.mdx | 2 +- docs/docs/providers/inference/remote_vllm.mdx | 2 +- .../providers/inference/remote_watsonx.mdx | 2 +- .../providers/post_training/remote_nvidia.mdx | 2 +- docs/docs/providers/safety/remote_bedrock.mdx | 4 +- .../providers/safety/remote_sambanova.mdx | 2 +- .../providers/scoring/inline_braintrust.mdx | 2 +- .../tool_runtime/remote_bing-search.mdx | 2 +- .../tool_runtime/remote_brave-search.mdx | 2 +- .../tool_runtime/remote_tavily-search.mdx | 2 +- .../tool_runtime/remote_wolfram-alpha.mdx | 2 +- .../providers/vector_io/remote_pgvector.mdx | 2 +- .../providers/vector_io/remote_qdrant.mdx | 2 +- llama_stack/cli/stack/_build.py | 2 +- llama_stack/core/configure.py | 4 +- llama_stack/core/library_client.py | 5 +- llama_stack/core/secret_types.py | 21 +++ llama_stack/core/server/server.py | 11 +- llama_stack/core/stack.py | 136 ++++++++++++++++-- .../inline/scoring/braintrust/__init__.py | 5 +- .../inline/scoring/braintrust/braintrust.py | 5 +- .../inline/scoring/braintrust/config.py | 7 +- .../meta_reference/console_span_processor.py | 3 +- .../remote/datasetio/nvidia/config.py | 6 +- .../providers/remote/files/s3/config.py | 7 +- .../remote/inference/anthropic/config.py | 9 +- .../remote/inference/azure/config.py | 7 +- .../remote/inference/cerebras/config.py | 11 +- .../remote/inference/databricks/config.py | 6 +- .../remote/inference/fireworks/config.py | 6 +- .../remote/inference/gemini/config.py | 9 +- .../providers/remote/inference/groq/config.py | 9 +- .../inference/llama_openai_compat/config.py | 9 +- .../remote/inference/nvidia/config.py | 7 +- .../remote/inference/openai/config.py | 9 +- .../remote/inference/passthrough/config.py | 6 +- .../remote/inference/runpod/config.py | 6 +- .../remote/inference/sambanova/config.py | 9 +- .../providers/remote/inference/tgi/config.py | 9 +- .../providers/remote/inference/tgi/tgi.py | 6 +- .../remote/inference/together/config.py | 6 +- .../remote/inference/vertexai/vertexai.py | 10 +- .../remote/inference/vllm/__init__.py | 7 +- .../providers/remote/inference/vllm/config.py | 6 +- .../remote/inference/watsonx/config.py | 7 +- .../remote/post_training/nvidia/config.py | 6 +- .../remote/safety/sambanova/config.py | 9 +- .../remote/tool_runtime/bing_search/config.py | 7 +- .../tool_runtime/brave_search/config.py | 7 +- .../tool_runtime/tavily_search/config.py | 7 +- .../tool_runtime/wolfram_alpha/config.py | 8 +- .../remote/vector_io/pgvector/config.py | 5 +- .../remote/vector_io/qdrant/config.py | 7 +- llama_stack/providers/utils/bedrock/config.py | 12 +- .../utils/inference/litellm_openai_mixin.py | 6 +- .../providers/utils/inference/openai_mixin.py | 8 +- llama_stack/providers/utils/kvstore/config.py | 7 +- .../providers/utils/sqlstore/sqlstore.py | 5 +- .../inference/test_litellm_openai_mixin.py | 9 +- .../inference/test_openai_base_url_config.py | 25 ++-- tests/unit/server/test_replace_env_vars.py | 37 ++--- 79 files changed, 381 insertions(+), 216 deletions(-) create mode 100644 llama_stack/core/secret_types.py diff --git a/docs/docs/providers/datasetio/remote_nvidia.mdx b/docs/docs/providers/datasetio/remote_nvidia.mdx index 35a7dacee..ba5522fce 100644 --- a/docs/docs/providers/datasetio/remote_nvidia.mdx +++ b/docs/docs/providers/datasetio/remote_nvidia.mdx @@ -14,7 +14,7 @@ NVIDIA's dataset I/O provider for accessing datasets from NVIDIA's data platform | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `str \| None` | No | | The NVIDIA API key. | +| `api_key` | `` | No | | The NVIDIA API key. | | `dataset_namespace` | `str \| None` | No | default | The NVIDIA dataset namespace. | | `project_id` | `str \| None` | No | test-project | The NVIDIA project ID. | | `datasets_url` | `` | No | http://nemo.test | Base URL for the NeMo Dataset API | diff --git a/docs/docs/providers/files/remote_s3.mdx b/docs/docs/providers/files/remote_s3.mdx index adf4bced0..9de31df44 100644 --- a/docs/docs/providers/files/remote_s3.mdx +++ b/docs/docs/providers/files/remote_s3.mdx @@ -17,7 +17,7 @@ AWS S3-based file storage provider for scalable cloud file management with metad | `bucket_name` | `` | No | | S3 bucket name to store files | | `region` | `` | No | us-east-1 | AWS region where the bucket is located | | `aws_access_key_id` | `str \| None` | No | | AWS access key ID (optional if using IAM roles) | -| `aws_secret_access_key` | `` | No | | AWS secret access key (optional if using IAM roles) | +| `aws_secret_access_key` | `` | No | | AWS secret access key (optional if using IAM roles) | | `endpoint_url` | `str \| None` | No | | Custom S3 endpoint URL (for MinIO, LocalStack, etc.) | | `auto_create_bucket` | `` | No | False | Automatically create the S3 bucket if it doesn't exist | | `metadata_store` | `utils.sqlstore.sqlstore.SqliteSqlStoreConfig \| utils.sqlstore.sqlstore.PostgresSqlStoreConfig` | No | sqlite | SQL store configuration for file metadata | diff --git a/docs/docs/providers/inference/remote_anthropic.mdx b/docs/docs/providers/inference/remote_anthropic.mdx index f795ad3f1..72deb298c 100644 --- a/docs/docs/providers/inference/remote_anthropic.mdx +++ b/docs/docs/providers/inference/remote_anthropic.mdx @@ -14,7 +14,7 @@ Anthropic inference provider for accessing Claude models and Anthropic's AI serv | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | API key for Anthropic models | +| `api_key` | `` | No | | API key for Anthropic models | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_azure.mdx b/docs/docs/providers/inference/remote_azure.mdx index 0eb0ea755..1085e4ad4 100644 --- a/docs/docs/providers/inference/remote_azure.mdx +++ b/docs/docs/providers/inference/remote_azure.mdx @@ -21,7 +21,7 @@ https://learn.microsoft.com/en-us/azure/ai-foundry/openai/overview | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | Azure API key for Azure | +| `api_key` | `` | No | | Azure API key for Azure | | `api_base` | `` | No | | Azure API base for Azure (e.g., https://your-resource-name.openai.azure.com) | | `api_version` | `str \| None` | No | | Azure API version for Azure (e.g., 2024-12-01-preview) | | `api_type` | `str \| None` | No | azure | Azure API type for Azure (e.g., azure) | diff --git a/docs/docs/providers/inference/remote_bedrock.mdx b/docs/docs/providers/inference/remote_bedrock.mdx index ff1ed5ad8..627be48e5 100644 --- a/docs/docs/providers/inference/remote_bedrock.mdx +++ b/docs/docs/providers/inference/remote_bedrock.mdx @@ -15,8 +15,8 @@ AWS Bedrock inference provider for accessing various AI models through AWS's man | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `aws_access_key_id` | `str \| None` | No | | The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID | -| `aws_secret_access_key` | `` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | -| `aws_session_token` | `` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | +| `aws_secret_access_key` | `` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | +| `aws_session_token` | `` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | | `region_name` | `str \| None` | No | | The default AWS Region to use, for example, us-west-1 or us-west-2.Default use environment variable: AWS_DEFAULT_REGION | | `profile_name` | `str \| None` | No | | The profile name that contains credentials to use.Default use environment variable: AWS_PROFILE | | `total_max_attempts` | `int \| None` | No | | An integer representing the maximum number of attempts that will be made for a single request, including the initial attempt. Default use environment variable: AWS_MAX_ATTEMPTS | diff --git a/docs/docs/providers/inference/remote_cerebras.mdx b/docs/docs/providers/inference/remote_cerebras.mdx index d9cc93aef..7c96e5115 100644 --- a/docs/docs/providers/inference/remote_cerebras.mdx +++ b/docs/docs/providers/inference/remote_cerebras.mdx @@ -15,7 +15,7 @@ Cerebras inference provider for running models on Cerebras Cloud platform. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `base_url` | `` | No | https://api.cerebras.ai | Base URL for the Cerebras API | -| `api_key` | `` | No | | Cerebras API Key | +| `api_key` | `` | No | | Cerebras API Key | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_databricks.mdx b/docs/docs/providers/inference/remote_databricks.mdx index 7f736db9d..fb3783720 100644 --- a/docs/docs/providers/inference/remote_databricks.mdx +++ b/docs/docs/providers/inference/remote_databricks.mdx @@ -15,7 +15,7 @@ Databricks inference provider for running models on Databricks' unified analytic | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | | The URL for the Databricks model serving endpoint | -| `api_token` | `` | No | | The Databricks API token | +| `api_token` | `` | No | | The Databricks API token | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_fireworks.mdx b/docs/docs/providers/inference/remote_fireworks.mdx index 0f37ccbc2..73698f28c 100644 --- a/docs/docs/providers/inference/remote_fireworks.mdx +++ b/docs/docs/providers/inference/remote_fireworks.mdx @@ -16,7 +16,7 @@ Fireworks AI inference provider for Llama models and other AI models on the Fire |-------|------|----------|---------|-------------| | `allowed_models` | `list[str \| None` | No | | List of models that should be registered with the model registry. If None, all models are allowed. | | `url` | `` | No | https://api.fireworks.ai/inference/v1 | The URL for the Fireworks server | -| `api_key` | `` | No | | The Fireworks.ai API Key | +| `api_key` | `` | No | | The Fireworks.ai API Key | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_gemini.mdx b/docs/docs/providers/inference/remote_gemini.mdx index d9a2f3e8d..538a9f29b 100644 --- a/docs/docs/providers/inference/remote_gemini.mdx +++ b/docs/docs/providers/inference/remote_gemini.mdx @@ -14,7 +14,7 @@ Google Gemini inference provider for accessing Gemini models and Google's AI ser | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | API key for Gemini models | +| `api_key` | `` | No | | API key for Gemini models | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_groq.mdx b/docs/docs/providers/inference/remote_groq.mdx index b6d29496e..2f80ab7ca 100644 --- a/docs/docs/providers/inference/remote_groq.mdx +++ b/docs/docs/providers/inference/remote_groq.mdx @@ -14,7 +14,7 @@ Groq inference provider for ultra-fast inference using Groq's LPU technology. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The Groq API key | +| `api_key` | `` | No | | The Groq API key | | `url` | `` | No | https://api.groq.com | The URL for the Groq AI server | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_hf_endpoint.mdx b/docs/docs/providers/inference/remote_hf_endpoint.mdx index 771b24f8d..93468fdbc 100644 --- a/docs/docs/providers/inference/remote_hf_endpoint.mdx +++ b/docs/docs/providers/inference/remote_hf_endpoint.mdx @@ -15,7 +15,7 @@ HuggingFace Inference Endpoints provider for dedicated model serving. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `endpoint_name` | `` | No | | The name of the Hugging Face Inference Endpoint in the format of '{namespace}/{endpoint_name}' (e.g. 'my-cool-org/meta-llama-3-1-8b-instruct-rce'). Namespace is optional and will default to the user account if not provided. | -| `api_token` | `pydantic.types.SecretStr \| None` | No | | Your Hugging Face user access token (will default to locally saved token if not provided) | +| `api_token` | `` | No | | Your Hugging Face user access token (will default to locally saved token if not provided) | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_hf_serverless.mdx b/docs/docs/providers/inference/remote_hf_serverless.mdx index 1a89b8e3e..80753ed05 100644 --- a/docs/docs/providers/inference/remote_hf_serverless.mdx +++ b/docs/docs/providers/inference/remote_hf_serverless.mdx @@ -15,7 +15,7 @@ HuggingFace Inference API serverless provider for on-demand model inference. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `huggingface_repo` | `` | No | | The model ID of the model on the Hugging Face Hub (e.g. 'meta-llama/Meta-Llama-3.1-70B-Instruct') | -| `api_token` | `pydantic.types.SecretStr \| None` | No | | Your Hugging Face user access token (will default to locally saved token if not provided) | +| `api_token` | `` | No | | Your Hugging Face user access token (will default to locally saved token if not provided) | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_llama-openai-compat.mdx b/docs/docs/providers/inference/remote_llama-openai-compat.mdx index 491774844..478b7dadb 100644 --- a/docs/docs/providers/inference/remote_llama-openai-compat.mdx +++ b/docs/docs/providers/inference/remote_llama-openai-compat.mdx @@ -14,7 +14,7 @@ Llama OpenAI-compatible provider for using Llama models with OpenAI API format. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The Llama API key | +| `api_key` | `` | No | | The Llama API key | | `openai_compat_api_base` | `` | No | https://api.llama.com/compat/v1/ | The URL for the Llama API server | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_nvidia.mdx b/docs/docs/providers/inference/remote_nvidia.mdx index 9aee22e9a..c51da9547 100644 --- a/docs/docs/providers/inference/remote_nvidia.mdx +++ b/docs/docs/providers/inference/remote_nvidia.mdx @@ -15,7 +15,7 @@ NVIDIA inference provider for accessing NVIDIA NIM models and AI services. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://integrate.api.nvidia.com | A base url for accessing the NVIDIA NIM | -| `api_key` | `` | No | | The NVIDIA API key, only needed of using the hosted service | +| `api_key` | `` | No | | The NVIDIA API key, only needed of using the hosted service | | `timeout` | `` | No | 60 | Timeout for the HTTP requests | | `append_api_version` | `` | No | True | When set to false, the API version will not be appended to the base_url. By default, it is true. | diff --git a/docs/docs/providers/inference/remote_openai.mdx b/docs/docs/providers/inference/remote_openai.mdx index f82bea154..79e9d9ccc 100644 --- a/docs/docs/providers/inference/remote_openai.mdx +++ b/docs/docs/providers/inference/remote_openai.mdx @@ -14,7 +14,7 @@ OpenAI inference provider for accessing GPT models and other OpenAI services. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | API key for OpenAI models | +| `api_key` | `` | No | | API key for OpenAI models | | `base_url` | `` | No | https://api.openai.com/v1 | Base URL for OpenAI API | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_passthrough.mdx b/docs/docs/providers/inference/remote_passthrough.mdx index efaa01d04..f35f746db 100644 --- a/docs/docs/providers/inference/remote_passthrough.mdx +++ b/docs/docs/providers/inference/remote_passthrough.mdx @@ -15,7 +15,7 @@ Passthrough inference provider for connecting to any external inference service | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | | The URL for the passthrough endpoint | -| `api_key` | `` | No | | API Key for the passthrouth endpoint | +| `api_key` | `` | No | | API Key for the passthrouth endpoint | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_runpod.mdx b/docs/docs/providers/inference/remote_runpod.mdx index 1def462f6..8028f0228 100644 --- a/docs/docs/providers/inference/remote_runpod.mdx +++ b/docs/docs/providers/inference/remote_runpod.mdx @@ -15,7 +15,7 @@ RunPod inference provider for running models on RunPod's cloud GPU platform. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `str \| None` | No | | The URL for the Runpod model serving endpoint | -| `api_token` | `` | No | | The API token | +| `api_token` | `` | No | | The API token | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_sambanova.mdx b/docs/docs/providers/inference/remote_sambanova.mdx index b5d64e6d9..2a63cab16 100644 --- a/docs/docs/providers/inference/remote_sambanova.mdx +++ b/docs/docs/providers/inference/remote_sambanova.mdx @@ -15,7 +15,7 @@ SambaNova inference provider for running models on SambaNova's dataflow architec | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://api.sambanova.ai/v1 | The URL for the SambaNova AI server | -| `api_key` | `` | No | | The SambaNova cloud API Key | +| `api_key` | `` | No | | The SambaNova cloud API Key | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_together.mdx b/docs/docs/providers/inference/remote_together.mdx index 0669a1112..912cf0291 100644 --- a/docs/docs/providers/inference/remote_together.mdx +++ b/docs/docs/providers/inference/remote_together.mdx @@ -16,7 +16,7 @@ Together AI inference provider for open-source models and collaborative AI devel |-------|------|----------|---------|-------------| | `allowed_models` | `list[str \| None` | No | | List of models that should be registered with the model registry. If None, all models are allowed. | | `url` | `` | No | https://api.together.xyz/v1 | The URL for the Together AI server | -| `api_key` | `` | No | | The Together AI API Key | +| `api_key` | `` | No | | The Together AI API Key | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_vllm.mdx b/docs/docs/providers/inference/remote_vllm.mdx index 5f9af4db4..4db289376 100644 --- a/docs/docs/providers/inference/remote_vllm.mdx +++ b/docs/docs/providers/inference/remote_vllm.mdx @@ -16,7 +16,7 @@ Remote vLLM inference provider for connecting to vLLM servers. |-------|------|----------|---------|-------------| | `url` | `str \| None` | No | | The URL for the vLLM model serving endpoint | | `max_tokens` | `` | No | 4096 | Maximum number of tokens to generate. | -| `api_token` | `` | No | | The API token | +| `api_token` | `` | No | | The API token | | `tls_verify` | `bool \| str` | No | True | Whether to verify TLS certificates. Can be a boolean or a path to a CA certificate file. | | `refresh_models` | `` | No | False | Whether to refresh models periodically | diff --git a/docs/docs/providers/inference/remote_watsonx.mdx b/docs/docs/providers/inference/remote_watsonx.mdx index c1ab57a7b..2584a78ac 100644 --- a/docs/docs/providers/inference/remote_watsonx.mdx +++ b/docs/docs/providers/inference/remote_watsonx.mdx @@ -15,7 +15,7 @@ IBM WatsonX inference provider for accessing AI models on IBM's WatsonX platform | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://us-south.ml.cloud.ibm.com | A base url for accessing the watsonx.ai | -| `api_key` | `` | No | | The watsonx API key | +| `api_key` | `` | No | | The watsonx API key | | `project_id` | `str \| None` | No | | The Project ID key | | `timeout` | `` | No | 60 | Timeout for the HTTP requests | diff --git a/docs/docs/providers/post_training/remote_nvidia.mdx b/docs/docs/providers/post_training/remote_nvidia.mdx index 448ac4c75..451b1b258 100644 --- a/docs/docs/providers/post_training/remote_nvidia.mdx +++ b/docs/docs/providers/post_training/remote_nvidia.mdx @@ -14,7 +14,7 @@ NVIDIA's post-training provider for fine-tuning models on NVIDIA's platform. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `str \| None` | No | | The NVIDIA API key. | +| `api_key` | `` | No | | The NVIDIA API key. | | `dataset_namespace` | `str \| None` | No | default | The NVIDIA dataset namespace. | | `project_id` | `str \| None` | No | test-example-model@v1 | The NVIDIA project ID. | | `customizer_url` | `str \| None` | No | | Base URL for the NeMo Customizer API | diff --git a/docs/docs/providers/safety/remote_bedrock.mdx b/docs/docs/providers/safety/remote_bedrock.mdx index e068f1fed..d5362ccd9 100644 --- a/docs/docs/providers/safety/remote_bedrock.mdx +++ b/docs/docs/providers/safety/remote_bedrock.mdx @@ -15,8 +15,8 @@ AWS Bedrock safety provider for content moderation using AWS's safety services. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `aws_access_key_id` | `str \| None` | No | | The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID | -| `aws_secret_access_key` | `` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | -| `aws_session_token` | `` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | +| `aws_secret_access_key` | `` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | +| `aws_session_token` | `` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | | `region_name` | `str \| None` | No | | The default AWS Region to use, for example, us-west-1 or us-west-2.Default use environment variable: AWS_DEFAULT_REGION | | `profile_name` | `str \| None` | No | | The profile name that contains credentials to use.Default use environment variable: AWS_PROFILE | | `total_max_attempts` | `int \| None` | No | | An integer representing the maximum number of attempts that will be made for a single request, including the initial attempt. Default use environment variable: AWS_MAX_ATTEMPTS | diff --git a/docs/docs/providers/safety/remote_sambanova.mdx b/docs/docs/providers/safety/remote_sambanova.mdx index 3a5a0db7d..c63e2160c 100644 --- a/docs/docs/providers/safety/remote_sambanova.mdx +++ b/docs/docs/providers/safety/remote_sambanova.mdx @@ -15,7 +15,7 @@ SambaNova's safety provider for content moderation and safety filtering. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://api.sambanova.ai/v1 | The URL for the SambaNova AI server | -| `api_key` | `` | No | | The SambaNova cloud API Key | +| `api_key` | `` | No | | The SambaNova cloud API Key | ## Sample Configuration diff --git a/docs/docs/providers/scoring/inline_braintrust.mdx b/docs/docs/providers/scoring/inline_braintrust.mdx index 93779b7ac..b035c259a 100644 --- a/docs/docs/providers/scoring/inline_braintrust.mdx +++ b/docs/docs/providers/scoring/inline_braintrust.mdx @@ -14,7 +14,7 @@ Braintrust scoring provider for evaluation and scoring using the Braintrust plat | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `openai_api_key` | `` | No | | The OpenAI API Key | +| `openai_api_key` | `` | No | | The OpenAI API Key | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_bing-search.mdx b/docs/docs/providers/tool_runtime/remote_bing-search.mdx index b7dabdd47..02ed6af2c 100644 --- a/docs/docs/providers/tool_runtime/remote_bing-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_bing-search.mdx @@ -14,7 +14,7 @@ Bing Search tool for web search capabilities using Microsoft's search engine. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The Bing API key | +| `api_key` | `` | No | | The Bing API key | | `top_k` | `` | No | 3 | | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_brave-search.mdx b/docs/docs/providers/tool_runtime/remote_brave-search.mdx index 084ae20f5..ccb7f3e23 100644 --- a/docs/docs/providers/tool_runtime/remote_brave-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_brave-search.mdx @@ -14,7 +14,7 @@ Brave Search tool for web search capabilities with privacy-focused results. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The Brave Search API Key | +| `api_key` | `` | No | | The Brave Search API Key | | `max_results` | `` | No | 3 | The maximum number of results to return | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_tavily-search.mdx b/docs/docs/providers/tool_runtime/remote_tavily-search.mdx index 1c3429983..7947c42c8 100644 --- a/docs/docs/providers/tool_runtime/remote_tavily-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_tavily-search.mdx @@ -14,7 +14,7 @@ Tavily Search tool for AI-optimized web search with structured results. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The Tavily Search API Key | +| `api_key` | `` | No | | The Tavily Search API Key | | `max_results` | `` | No | 3 | The maximum number of results to return | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_wolfram-alpha.mdx b/docs/docs/providers/tool_runtime/remote_wolfram-alpha.mdx index 96bc41789..2dd58f043 100644 --- a/docs/docs/providers/tool_runtime/remote_wolfram-alpha.mdx +++ b/docs/docs/providers/tool_runtime/remote_wolfram-alpha.mdx @@ -14,7 +14,7 @@ Wolfram Alpha tool for computational knowledge and mathematical calculations. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `str \| None` | No | | | +| `api_key` | `` | No | | The WolframAlpha API Key | ## Sample Configuration diff --git a/docs/docs/providers/vector_io/remote_pgvector.mdx b/docs/docs/providers/vector_io/remote_pgvector.mdx index 6d3157753..2853cddeb 100644 --- a/docs/docs/providers/vector_io/remote_pgvector.mdx +++ b/docs/docs/providers/vector_io/remote_pgvector.mdx @@ -217,7 +217,7 @@ See [PGVector's documentation](https://github.com/pgvector/pgvector) for more de | `port` | `int \| None` | No | 5432 | | | `db` | `str \| None` | No | postgres | | | `user` | `str \| None` | No | postgres | | -| `password` | `` | No | ********** | | +| `password` | `` | No | ********** | | | `kvstore` | `utils.kvstore.config.RedisKVStoreConfig \| utils.kvstore.config.SqliteKVStoreConfig \| utils.kvstore.config.PostgresKVStoreConfig \| utils.kvstore.config.MongoDBKVStoreConfig, annotation=NoneType, required=False, default='sqlite', discriminator='type'` | No | | Config for KV store backend (SQLite only for now) | ## Sample Configuration diff --git a/docs/docs/providers/vector_io/remote_qdrant.mdx b/docs/docs/providers/vector_io/remote_qdrant.mdx index c44a2b937..c344dfe2e 100644 --- a/docs/docs/providers/vector_io/remote_qdrant.mdx +++ b/docs/docs/providers/vector_io/remote_qdrant.mdx @@ -22,7 +22,7 @@ Please refer to the inline provider documentation. | `grpc_port` | `` | No | 6334 | | | `prefer_grpc` | `` | No | False | | | `https` | `bool \| None` | No | | | -| `api_key` | `str \| None` | No | | | +| `api_key` | `` | No | | The API key for the Qdrant instance | | `prefix` | `str \| None` | No | | | | `timeout` | `int \| None` | No | | | | `host` | `str \| None` | No | | | diff --git a/llama_stack/cli/stack/_build.py b/llama_stack/cli/stack/_build.py index b14e6fe55..1992ba292 100644 --- a/llama_stack/cli/stack/_build.py +++ b/llama_stack/cli/stack/_build.py @@ -216,7 +216,7 @@ def run_stack_build_command(args: argparse.Namespace) -> None: with open(args.config) as f: try: contents = yaml.safe_load(f) - contents = replace_env_vars(contents) + contents = replace_env_vars(contents, provider_registry=get_provider_registry()) build_config = BuildConfig(**contents) if args.image_type: build_config.image_type = args.image_type diff --git a/llama_stack/core/configure.py b/llama_stack/core/configure.py index 64473c053..92ec66c49 100644 --- a/llama_stack/core/configure.py +++ b/llama_stack/core/configure.py @@ -165,7 +165,7 @@ def upgrade_from_routing_table( def parse_and_maybe_upgrade_config(config_dict: dict[str, Any]) -> StackRunConfig: version = config_dict.get("version", None) if version == LLAMA_STACK_RUN_CONFIG_VERSION: - processed_config_dict = replace_env_vars(config_dict) + processed_config_dict = replace_env_vars(config_dict, provider_registry=get_provider_registry()) return StackRunConfig(**cast_image_name_to_string(processed_config_dict)) if "routing_table" in config_dict: @@ -177,5 +177,5 @@ def parse_and_maybe_upgrade_config(config_dict: dict[str, Any]) -> StackRunConfi if not config_dict.get("external_providers_dir", None): config_dict["external_providers_dir"] = EXTERNAL_PROVIDERS_DIR - processed_config_dict = replace_env_vars(config_dict) + processed_config_dict = replace_env_vars(config_dict, provider_registry=get_provider_registry()) return StackRunConfig(**cast_image_name_to_string(processed_config_dict)) diff --git a/llama_stack/core/library_client.py b/llama_stack/core/library_client.py index e722e4de6..6f0f3fb2e 100644 --- a/llama_stack/core/library_client.py +++ b/llama_stack/core/library_client.py @@ -33,6 +33,7 @@ from termcolor import cprint from llama_stack.core.build import print_pip_install_help from llama_stack.core.configure import parse_and_maybe_upgrade_config from llama_stack.core.datatypes import Api, BuildConfig, BuildProvider, DistributionSpec +from llama_stack.core.distribution import get_provider_registry from llama_stack.core.request_headers import ( PROVIDER_DATA_VAR, request_provider_data_context, @@ -220,7 +221,9 @@ class AsyncLlamaStackAsLibraryClient(AsyncLlamaStackClient): config_path = Path(config_path_or_distro_name) if not config_path.exists(): raise ValueError(f"Config file {config_path} does not exist") - config_dict = replace_env_vars(yaml.safe_load(config_path.read_text())) + config_dict = replace_env_vars( + yaml.safe_load(config_path.read_text()), provider_registry=get_provider_registry() + ) config = parse_and_maybe_upgrade_config(config_dict) else: # distribution diff --git a/llama_stack/core/secret_types.py b/llama_stack/core/secret_types.py new file mode 100644 index 000000000..e1700d783 --- /dev/null +++ b/llama_stack/core/secret_types.py @@ -0,0 +1,21 @@ +# 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 pydantic.types import SecretStr + + +class MySecretStr(SecretStr): + """A SecretStr that can accept None values to avoid mypy type errors. + + This is useful for optional secret fields where you want to avoid + explicit None checks in consuming code. + + We chose to not use the SecretStr from pydantic because it does not allow None values and will + let the provider's library fail if the secret is not provided. + """ + + def __init__(self, secret_value: str | None = None) -> None: + SecretStr.__init__(self, secret_value) # type: ignore[arg-type] diff --git a/llama_stack/core/server/server.py b/llama_stack/core/server/server.py index 7d119c139..1c5a79f1c 100644 --- a/llama_stack/core/server/server.py +++ b/llama_stack/core/server/server.py @@ -43,7 +43,7 @@ from llama_stack.core.datatypes import ( StackRunConfig, process_cors_config, ) -from llama_stack.core.distribution import builtin_automatically_routed_apis +from llama_stack.core.distribution import builtin_automatically_routed_apis, get_provider_registry from llama_stack.core.external import load_external_apis from llama_stack.core.request_headers import ( PROVIDER_DATA_VAR, @@ -371,7 +371,7 @@ def create_app( logger.error(f"Error: {str(e)}") raise ValueError(f"Invalid environment variable format: {env_pair}") from e - config = replace_env_vars(config_contents) + config = replace_env_vars(config_contents, provider_registry=get_provider_registry()) config = StackRunConfig(**cast_image_name_to_string(config)) _log_run_config(run_config=config) @@ -524,7 +524,10 @@ def main(args: argparse.Namespace | None = None): env_vars=args.env, ) except Exception as e: + import traceback + logger.error(f"Error creating app: {str(e)}") + logger.error(f"Stack trace:\n{traceback.format_exc()}") sys.exit(1) config_file = resolve_config_or_distro(config_or_distro, Mode.RUN) @@ -534,7 +537,9 @@ def main(args: argparse.Namespace | None = None): logger_config = LoggingConfig(**cfg) else: logger_config = None - config = StackRunConfig(**cast_image_name_to_string(replace_env_vars(config_contents))) + config = StackRunConfig( + **cast_image_name_to_string(replace_env_vars(config_contents, provider_registry=get_provider_registry())) + ) import uvicorn diff --git a/llama_stack/core/stack.py b/llama_stack/core/stack.py index 3e14328a3..3b93f9913 100644 --- a/llama_stack/core/stack.py +++ b/llama_stack/core/stack.py @@ -141,12 +141,19 @@ class EnvVarError(Exception): ) -def replace_env_vars(config: Any, path: str = "") -> Any: +def replace_env_vars( + config: Any, + path: str = "", + provider_registry: dict[Api, dict[str, Any]] | None = None, + current_provider_context: dict[str, Any] | None = None, +) -> Any: if isinstance(config, dict): result = {} for k, v in config.items(): try: - result[k] = replace_env_vars(v, f"{path}.{k}" if path else k) + result[k] = replace_env_vars( + v, f"{path}.{k}" if path else k, provider_registry, current_provider_context + ) except EnvVarError as e: raise EnvVarError(e.var_name, e.path) from None return result @@ -159,7 +166,9 @@ def replace_env_vars(config: Any, path: str = "") -> Any: # is disabled so that we can skip config env variable expansion and avoid validation errors if isinstance(v, dict) and "provider_id" in v: try: - resolved_provider_id = replace_env_vars(v["provider_id"], f"{path}[{i}].provider_id") + resolved_provider_id = replace_env_vars( + v["provider_id"], f"{path}[{i}].provider_id", provider_registry, current_provider_context + ) if resolved_provider_id == "__disabled__": logger.debug( f"Skipping config env variable expansion for disabled provider: {v.get('provider_id', '')}" @@ -167,13 +176,19 @@ def replace_env_vars(config: Any, path: str = "") -> Any: # Create a copy with resolved provider_id but original config disabled_provider = v.copy() disabled_provider["provider_id"] = resolved_provider_id + result.append(disabled_provider) continue except EnvVarError: # If we can't resolve the provider_id, continue with normal processing pass + # Set up provider context for config processing + provider_context = current_provider_context + if isinstance(v, dict) and "provider_id" in v and "provider_type" in v and provider_registry: + provider_context = _get_provider_context(v, provider_registry) + # Normal processing for non-disabled providers - result.append(replace_env_vars(v, f"{path}[{i}]")) + result.append(replace_env_vars(v, f"{path}[{i}]", provider_registry, provider_context)) except EnvVarError as e: raise EnvVarError(e.var_name, e.path) from None return result @@ -228,7 +243,7 @@ def replace_env_vars(config: Any, path: str = "") -> Any: result = re.sub(pattern, get_env_var, config) # Only apply type conversion if substitution actually happened if result != config: - return _convert_string_to_proper_type(result) + return _convert_string_to_proper_type_with_config(result, path, current_provider_context) return result except EnvVarError as e: raise EnvVarError(e.var_name, e.path) from None @@ -236,12 +251,107 @@ def replace_env_vars(config: Any, path: str = "") -> Any: return config +def _get_provider_context( + provider_dict: dict[str, Any], provider_registry: dict[Api, dict[str, Any]] +) -> dict[str, Any] | None: + """Get provider context information including config class for type conversion.""" + try: + provider_type = provider_dict.get("provider_type") + if not provider_type: + return None + + for api, providers in provider_registry.items(): + if provider_type in providers: + provider_spec = providers[provider_type] + + config_class = instantiate_class_type(provider_spec.config_class) + + return { + "api": api, + "provider_type": provider_type, + "config_class": config_class, + "provider_spec": provider_spec, + } + except Exception as e: + logger.debug(f"Failed to get provider context: {e}") + return None + + +def _convert_string_to_proper_type_with_config(value: str, path: str, provider_context: dict[str, Any] | None) -> Any: + """Convert string to proper type using provider config class field information.""" + if not provider_context or not provider_context.get("config_class"): + # best effort conversion if we don't have the config class + return _convert_string_to_proper_type(value) + + try: + # Extract field name from path (e.g., "providers.inference[0].config.api_key" -> "api_key") + field_name = path.split(".")[-1] if "." in path else path + + config_class = provider_context["config_class"] + + if hasattr(config_class, "model_fields") and field_name in config_class.model_fields: + field_info = config_class.model_fields[field_name] + field_type = field_info.annotation + return _convert_value_by_field_type(value, field_type) + else: + return _convert_string_to_proper_type(value) + + except Exception as e: + logger.debug(f"Failed to convert using config class: {e}") + return _convert_string_to_proper_type(value) + + +def _convert_value_by_field_type(value: str, field_type: Any) -> Any: + """Convert string value based on Pydantic field type annotation.""" + import typing + from typing import get_args, get_origin + + if value == "": + if field_type is None or (hasattr(typing, "get_origin") and get_origin(field_type) is type(None)): + return None + if hasattr(typing, "get_origin") and get_origin(field_type) is typing.Union: + args = get_args(field_type) + if type(None) in args: + return None + return "" + + if field_type is bool or (hasattr(typing, "get_origin") and get_origin(field_type) is bool): + lowered = value.lower() + if lowered == "true": + return True + elif lowered == "false": + return False + else: + return value + + if field_type is int or (hasattr(typing, "get_origin") and get_origin(field_type) is int): + try: + return int(value) + except ValueError: + return value + + if field_type is float or (hasattr(typing, "get_origin") and get_origin(field_type) is float): + try: + return float(value) + except ValueError: + return value + + if hasattr(typing, "get_origin") and get_origin(field_type) is typing.Union: + args = get_args(field_type) + # Try to convert to the first non-None type + for arg in args: + if arg is not type(None): + try: + return _convert_value_by_field_type(value, arg) + except Exception: + continue + + return value + + def _convert_string_to_proper_type(value: str) -> Any: - # This might be tricky depending on what the config type is, if 'str | None' we are - # good, if 'str' we need to keep the empty string... 'str | None' is more common and - # providers config should be typed this way. - # TODO: we could try to load the config class and see if the config has a field with type 'str | None' - # and then convert the empty string to None or not + # Fallback function for when provider config class is not available + # The main type conversion logic is now in _convert_string_to_proper_type_with_config if value == "": return None @@ -416,7 +526,7 @@ def get_stack_run_config_from_distro(distro: str) -> StackRunConfig: raise ValueError(f"Distribution '{distro}' not found at {distro_path}") run_config = yaml.safe_load(path.open()) - return StackRunConfig(**replace_env_vars(run_config)) + return StackRunConfig(**replace_env_vars(run_config, provider_registry=get_provider_registry())) def run_config_from_adhoc_config_spec( @@ -452,7 +562,9 @@ def run_config_from_adhoc_config_spec( # call method "sample_run_config" on the provider spec config class provider_config_type = instantiate_class_type(provider_spec.config_class) - provider_config = replace_env_vars(provider_config_type.sample_run_config(__distro_dir__=distro_dir)) + provider_config = replace_env_vars( + provider_config_type.sample_run_config(__distro_dir__=distro_dir), provider_registry=provider_registry + ) provider_configs_by_api[api_str] = [ Provider( diff --git a/llama_stack/providers/inline/scoring/braintrust/__init__.py b/llama_stack/providers/inline/scoring/braintrust/__init__.py index 2f3dce966..77c09c615 100644 --- a/llama_stack/providers/inline/scoring/braintrust/__init__.py +++ b/llama_stack/providers/inline/scoring/braintrust/__init__.py @@ -5,15 +5,16 @@ # the root directory of this source tree. from typing import Any -from pydantic import BaseModel, SecretStr +from pydantic import BaseModel from llama_stack.core.datatypes import Api +from llama_stack.core.secret_types import MySecretStr from .config import BraintrustScoringConfig class BraintrustProviderDataValidator(BaseModel): - openai_api_key: SecretStr + openai_api_key: MySecretStr async def get_provider_impl( diff --git a/llama_stack/providers/inline/scoring/braintrust/braintrust.py b/llama_stack/providers/inline/scoring/braintrust/braintrust.py index 7f2b1e205..8d80b5920 100644 --- a/llama_stack/providers/inline/scoring/braintrust/braintrust.py +++ b/llama_stack/providers/inline/scoring/braintrust/braintrust.py @@ -17,7 +17,7 @@ from autoevals.ragas import ( ContextRelevancy, Faithfulness, ) -from pydantic import BaseModel, SecretStr +from pydantic import BaseModel from llama_stack.apis.datasetio import DatasetIO from llama_stack.apis.datasets import Datasets @@ -31,6 +31,7 @@ from llama_stack.apis.scoring import ( from llama_stack.apis.scoring_functions import ScoringFn, ScoringFnParams from llama_stack.core.datatypes import Api from llama_stack.core.request_headers import NeedsRequestProviderData +from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.datatypes import ScoringFunctionsProtocolPrivate from llama_stack.providers.utils.common.data_schema_validator import ( get_valid_schemas, @@ -152,7 +153,7 @@ class BraintrustScoringImpl( raise ValueError( 'Pass OpenAI API Key in the header X-LlamaStack-Provider-Data as { "openai_api_key": }' ) - self.config.openai_api_key = SecretStr(provider_data.openai_api_key) + self.config.openai_api_key = MySecretStr(provider_data.openai_api_key) os.environ["OPENAI_API_KEY"] = self.config.openai_api_key.get_secret_value() diff --git a/llama_stack/providers/inline/scoring/braintrust/config.py b/llama_stack/providers/inline/scoring/braintrust/config.py index a2a52d610..3520ffb08 100644 --- a/llama_stack/providers/inline/scoring/braintrust/config.py +++ b/llama_stack/providers/inline/scoring/braintrust/config.py @@ -5,12 +5,13 @@ # the root directory of this source tree. from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field + +from llama_stack.core.secret_types import MySecretStr class BraintrustScoringConfig(BaseModel): - openai_api_key: SecretStr = Field( - default=SecretStr(""), + openai_api_key: MySecretStr = Field( description="The OpenAI API Key", ) diff --git a/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py b/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py index 8beb3a841..a2eb896ea 100644 --- a/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py +++ b/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py @@ -65,8 +65,7 @@ class ConsoleSpanProcessor(SpanProcessor): if key.startswith("__") or key in ["message", "severity"]: continue - str_value = str(value) - logger.info(f"[dim]{key}[/dim]: {str_value}") + logger.info(f"[dim]{key}[/dim]: {value}") def shutdown(self) -> None: """Shutdown the processor.""" diff --git a/llama_stack/providers/remote/datasetio/nvidia/config.py b/llama_stack/providers/remote/datasetio/nvidia/config.py index addce6c1f..aa1ac163d 100644 --- a/llama_stack/providers/remote/datasetio/nvidia/config.py +++ b/llama_stack/providers/remote/datasetio/nvidia/config.py @@ -10,12 +10,14 @@ from typing import Any from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr + class NvidiaDatasetIOConfig(BaseModel): """Configuration for NVIDIA DatasetIO implementation.""" - api_key: str | None = Field( - default_factory=lambda: os.getenv("NVIDIA_API_KEY"), + api_key: MySecretStr = Field( + default_factory=lambda: MySecretStr(os.getenv("NVIDIA_API_KEY", "")), description="The NVIDIA API key.", ) diff --git a/llama_stack/providers/remote/files/s3/config.py b/llama_stack/providers/remote/files/s3/config.py index b7935902d..e8fc452d8 100644 --- a/llama_stack/providers/remote/files/s3/config.py +++ b/llama_stack/providers/remote/files/s3/config.py @@ -6,8 +6,9 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.sqlstore.sqlstore import SqliteSqlStoreConfig, SqlStoreConfig @@ -17,9 +18,7 @@ class S3FilesImplConfig(BaseModel): bucket_name: str = Field(description="S3 bucket name to store files") region: str = Field(default="us-east-1", description="AWS region where the bucket is located") aws_access_key_id: str | None = Field(default=None, description="AWS access key ID (optional if using IAM roles)") - aws_secret_access_key: SecretStr = Field( - default=SecretStr(""), description="AWS secret access key (optional if using IAM roles)" - ) + aws_secret_access_key: MySecretStr = Field(description="AWS secret access key (optional if using IAM roles)") endpoint_url: str | None = Field(default=None, description="Custom S3 endpoint URL (for MinIO, LocalStack, etc.)") auto_create_bucket: bool = Field( default=False, description="Automatically create the S3 bucket if it doesn't exist" diff --git a/llama_stack/providers/remote/inference/anthropic/config.py b/llama_stack/providers/remote/inference/anthropic/config.py index c28f05d24..eb77b328f 100644 --- a/llama_stack/providers/remote/inference/anthropic/config.py +++ b/llama_stack/providers/remote/inference/anthropic/config.py @@ -6,22 +6,21 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class AnthropicProviderDataValidator(BaseModel): - anthropic_api_key: SecretStr = Field( - default=SecretStr(""), + anthropic_api_key: MySecretStr = Field( description="API key for Anthropic models", ) @json_schema_type class AnthropicConfig(BaseModel): - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="API key for Anthropic models", ) diff --git a/llama_stack/providers/remote/inference/azure/config.py b/llama_stack/providers/remote/inference/azure/config.py index fe9d61d53..ab53e85c2 100644 --- a/llama_stack/providers/remote/inference/azure/config.py +++ b/llama_stack/providers/remote/inference/azure/config.py @@ -7,13 +7,14 @@ import os from typing import Any -from pydantic import BaseModel, Field, HttpUrl, SecretStr +from pydantic import BaseModel, Field, HttpUrl +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class AzureProviderDataValidator(BaseModel): - azure_api_key: SecretStr = Field( + azure_api_key: MySecretStr = Field( description="Azure API key for Azure", ) azure_api_base: HttpUrl = Field( @@ -31,7 +32,7 @@ class AzureProviderDataValidator(BaseModel): @json_schema_type class AzureConfig(BaseModel): - api_key: SecretStr = Field( + api_key: MySecretStr = Field( description="Azure API key for Azure", ) api_base: HttpUrl = Field( diff --git a/llama_stack/providers/remote/inference/cerebras/config.py b/llama_stack/providers/remote/inference/cerebras/config.py index 0b737ea6c..5c5de27a5 100644 --- a/llama_stack/providers/remote/inference/cerebras/config.py +++ b/llama_stack/providers/remote/inference/cerebras/config.py @@ -7,8 +7,9 @@ import os from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type DEFAULT_BASE_URL = "https://api.cerebras.ai" @@ -20,12 +21,8 @@ class CerebrasImplConfig(BaseModel): default=os.environ.get("CEREBRAS_BASE_URL", DEFAULT_BASE_URL), description="Base URL for the Cerebras API", ) - api_key: SecretStr = Field( -<<<<<<< HEAD - default=SecretStr(os.environ.get("CEREBRAS_API_KEY")), -======= - default=SecretStr(os.environ.get("CEREBRAS_API_KEY", "")), ->>>>>>> a48f2009 (chore: use empty SecretStr values as default) + api_key: MySecretStr = Field( + default=MySecretStr(os.environ.get("CEREBRAS_API_KEY")), description="Cerebras API Key", ) diff --git a/llama_stack/providers/remote/inference/databricks/config.py b/llama_stack/providers/remote/inference/databricks/config.py index 67cd0480c..92b250034 100644 --- a/llama_stack/providers/remote/inference/databricks/config.py +++ b/llama_stack/providers/remote/inference/databricks/config.py @@ -6,8 +6,9 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -17,8 +18,7 @@ class DatabricksImplConfig(BaseModel): default=None, description="The URL for the Databricks model serving endpoint", ) - api_token: SecretStr = Field( - default=SecretStr(None), + api_token: MySecretStr = Field( description="The Databricks API token", ) diff --git a/llama_stack/providers/remote/inference/fireworks/config.py b/llama_stack/providers/remote/inference/fireworks/config.py index 5bf0fcd88..14f2dffc1 100644 --- a/llama_stack/providers/remote/inference/fireworks/config.py +++ b/llama_stack/providers/remote/inference/fireworks/config.py @@ -6,8 +6,9 @@ from typing import Any -from pydantic import Field, SecretStr +from pydantic import Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.inference.model_registry import RemoteInferenceProviderConfig from llama_stack.schema_utils import json_schema_type @@ -18,8 +19,7 @@ class FireworksImplConfig(RemoteInferenceProviderConfig): default="https://api.fireworks.ai/inference/v1", description="The URL for the Fireworks server", ) - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="The Fireworks.ai API Key", ) diff --git a/llama_stack/providers/remote/inference/gemini/config.py b/llama_stack/providers/remote/inference/gemini/config.py index 4a69bd064..a90fb93cb 100644 --- a/llama_stack/providers/remote/inference/gemini/config.py +++ b/llama_stack/providers/remote/inference/gemini/config.py @@ -6,22 +6,21 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class GeminiProviderDataValidator(BaseModel): - gemini_api_key: SecretStr = Field( - default=SecretStr(""), + gemini_api_key: MySecretStr = Field( description="API key for Gemini models", ) @json_schema_type class GeminiConfig(BaseModel): - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="API key for Gemini models", ) diff --git a/llama_stack/providers/remote/inference/groq/config.py b/llama_stack/providers/remote/inference/groq/config.py index efc1f437b..c3c3fd580 100644 --- a/llama_stack/providers/remote/inference/groq/config.py +++ b/llama_stack/providers/remote/inference/groq/config.py @@ -6,23 +6,22 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class GroqProviderDataValidator(BaseModel): - groq_api_key: SecretStr = Field( - default=SecretStr(""), + groq_api_key: MySecretStr = Field( description="API key for Groq models", ) @json_schema_type class GroqConfig(BaseModel): - api_key: SecretStr = Field( + api_key: MySecretStr = Field( # The Groq client library loads the GROQ_API_KEY environment variable by default - default=SecretStr(""), description="The Groq API key", ) diff --git a/llama_stack/providers/remote/inference/llama_openai_compat/config.py b/llama_stack/providers/remote/inference/llama_openai_compat/config.py index d20fed8e0..863988dd6 100644 --- a/llama_stack/providers/remote/inference/llama_openai_compat/config.py +++ b/llama_stack/providers/remote/inference/llama_openai_compat/config.py @@ -6,22 +6,21 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class LlamaProviderDataValidator(BaseModel): - llama_api_key: SecretStr = Field( - default=SecretStr(""), + llama_api_key: MySecretStr = Field( description="API key for api.llama models", ) @json_schema_type class LlamaCompatConfig(BaseModel): - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="The Llama API key", ) diff --git a/llama_stack/providers/remote/inference/nvidia/config.py b/llama_stack/providers/remote/inference/nvidia/config.py index 9ca2ffdd6..2f6dfe9f3 100644 --- a/llama_stack/providers/remote/inference/nvidia/config.py +++ b/llama_stack/providers/remote/inference/nvidia/config.py @@ -7,8 +7,9 @@ import os from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -39,8 +40,8 @@ class NVIDIAConfig(BaseModel): default_factory=lambda: os.getenv("NVIDIA_BASE_URL", "https://integrate.api.nvidia.com"), description="A base url for accessing the NVIDIA NIM", ) - api_key: SecretStr = Field( - default_factory=lambda: SecretStr(os.getenv("NVIDIA_API_KEY", "")), + api_key: MySecretStr = Field( + default_factory=lambda: MySecretStr(os.getenv("NVIDIA_API_KEY", "")), description="The NVIDIA API key, only needed of using the hosted service", ) timeout: int = Field( diff --git a/llama_stack/providers/remote/inference/openai/config.py b/llama_stack/providers/remote/inference/openai/config.py index b6f5f1a55..c8d6be9c2 100644 --- a/llama_stack/providers/remote/inference/openai/config.py +++ b/llama_stack/providers/remote/inference/openai/config.py @@ -6,22 +6,21 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class OpenAIProviderDataValidator(BaseModel): - openai_api_key: SecretStr = Field( - default=SecretStr(""), + openai_api_key: MySecretStr = Field( description="API key for OpenAI models", ) @json_schema_type class OpenAIConfig(BaseModel): - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="API key for OpenAI models", ) base_url: str = Field( diff --git a/llama_stack/providers/remote/inference/passthrough/config.py b/llama_stack/providers/remote/inference/passthrough/config.py index f57ce0e8a..dee3b9173 100644 --- a/llama_stack/providers/remote/inference/passthrough/config.py +++ b/llama_stack/providers/remote/inference/passthrough/config.py @@ -6,8 +6,9 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -18,8 +19,7 @@ class PassthroughImplConfig(BaseModel): description="The URL for the passthrough endpoint", ) - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="API Key for the passthrouth endpoint", ) diff --git a/llama_stack/providers/remote/inference/runpod/config.py b/llama_stack/providers/remote/inference/runpod/config.py index 3c6c5afe9..eaabe4e33 100644 --- a/llama_stack/providers/remote/inference/runpod/config.py +++ b/llama_stack/providers/remote/inference/runpod/config.py @@ -6,8 +6,9 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -17,8 +18,7 @@ class RunpodImplConfig(BaseModel): default=None, description="The URL for the Runpod model serving endpoint", ) - api_token: SecretStr = Field( - default=SecretStr(""), + api_token: MySecretStr = Field( description="The API token", ) diff --git a/llama_stack/providers/remote/inference/sambanova/config.py b/llama_stack/providers/remote/inference/sambanova/config.py index 4637cb49e..10e28c3ac 100644 --- a/llama_stack/providers/remote/inference/sambanova/config.py +++ b/llama_stack/providers/remote/inference/sambanova/config.py @@ -6,14 +6,14 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class SambaNovaProviderDataValidator(BaseModel): - sambanova_api_key: SecretStr = Field( - default=SecretStr(""), + sambanova_api_key: MySecretStr = Field( description="Sambanova Cloud API key", ) @@ -24,8 +24,7 @@ class SambaNovaImplConfig(BaseModel): default="https://api.sambanova.ai/v1", description="The URL for the SambaNova AI server", ) - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="The SambaNova cloud API Key", ) diff --git a/llama_stack/providers/remote/inference/tgi/config.py b/llama_stack/providers/remote/inference/tgi/config.py index 55136c8ba..a1f4c19f3 100644 --- a/llama_stack/providers/remote/inference/tgi/config.py +++ b/llama_stack/providers/remote/inference/tgi/config.py @@ -5,8 +5,9 @@ # the root directory of this source tree. -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -32,8 +33,7 @@ class InferenceEndpointImplConfig(BaseModel): endpoint_name: str = Field( description="The name of the Hugging Face Inference Endpoint in the format of '{namespace}/{endpoint_name}' (e.g. 'my-cool-org/meta-llama-3-1-8b-instruct-rce'). Namespace is optional and will default to the user account if not provided.", ) - api_token: SecretStr | None = Field( - default=None, + api_token: MySecretStr = Field( description="Your Hugging Face user access token (will default to locally saved token if not provided)", ) @@ -55,8 +55,7 @@ class InferenceAPIImplConfig(BaseModel): huggingface_repo: str = Field( description="The model ID of the model on the Hugging Face Hub (e.g. 'meta-llama/Meta-Llama-3.1-70B-Instruct')", ) - api_token: SecretStr | None = Field( - default=None, + api_token: MySecretStr = Field( description="Your Hugging Face user access token (will default to locally saved token if not provided)", ) diff --git a/llama_stack/providers/remote/inference/tgi/tgi.py b/llama_stack/providers/remote/inference/tgi/tgi.py index 27597900f..239fdce4e 100644 --- a/llama_stack/providers/remote/inference/tgi/tgi.py +++ b/llama_stack/providers/remote/inference/tgi/tgi.py @@ -8,7 +8,6 @@ from collections.abc import AsyncGenerator from huggingface_hub import AsyncInferenceClient, HfApi -from pydantic import SecretStr from llama_stack.apis.common.content_types import ( InterleavedContent, @@ -35,6 +34,7 @@ from llama_stack.apis.inference import ( ) from llama_stack.apis.models import Model from llama_stack.apis.models.models import ModelType +from llama_stack.core.secret_types import MySecretStr from llama_stack.log import get_logger from llama_stack.models.llama.sku_list import all_registered_models from llama_stack.providers.datatypes import ModelsProtocolPrivate @@ -79,7 +79,7 @@ class _HfAdapter( ModelsProtocolPrivate, ): url: str - api_key: SecretStr + api_key: MySecretStr hf_client: AsyncInferenceClient max_tokens: int @@ -337,7 +337,7 @@ class TGIAdapter(_HfAdapter): self.max_tokens = endpoint_info["max_total_tokens"] self.model_id = endpoint_info["model_id"] self.url = f"{config.url.rstrip('/')}/v1" - self.api_key = SecretStr("NO_KEY") + self.api_key = MySecretStr("NO_KEY") class InferenceAPIAdapter(_HfAdapter): diff --git a/llama_stack/providers/remote/inference/together/config.py b/llama_stack/providers/remote/inference/together/config.py index c15d42140..a2fa8f76e 100644 --- a/llama_stack/providers/remote/inference/together/config.py +++ b/llama_stack/providers/remote/inference/together/config.py @@ -6,8 +6,9 @@ from typing import Any -from pydantic import Field, SecretStr +from pydantic import Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.inference.model_registry import RemoteInferenceProviderConfig from llama_stack.schema_utils import json_schema_type @@ -18,8 +19,7 @@ class TogetherImplConfig(RemoteInferenceProviderConfig): default="https://api.together.xyz/v1", description="The URL for the Together AI server", ) - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="The Together AI API Key", ) diff --git a/llama_stack/providers/remote/inference/vertexai/vertexai.py b/llama_stack/providers/remote/inference/vertexai/vertexai.py index 581a00f29..dd1971df4 100644 --- a/llama_stack/providers/remote/inference/vertexai/vertexai.py +++ b/llama_stack/providers/remote/inference/vertexai/vertexai.py @@ -8,9 +8,9 @@ from typing import Any import google.auth.transport.requests from google.auth import default -from pydantic import SecretStr from llama_stack.apis.inference import ChatCompletionRequest +from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.inference.litellm_openai_mixin import ( LiteLLMOpenAIMixin, ) @@ -24,12 +24,12 @@ class VertexAIInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): LiteLLMOpenAIMixin.__init__( self, litellm_provider_name="vertex_ai", - api_key_from_config=SecretStr(""), # Vertex AI uses ADC, not API keys + api_key_from_config=MySecretStr(None), # Vertex AI uses ADC, not API keys provider_data_api_key_field="vertex_project", # Use project for validation ) self.config = config - def get_api_key(self) -> SecretStr: + def get_api_key(self) -> MySecretStr: """ Get an access token for Vertex AI using Application Default Credentials. @@ -40,11 +40,11 @@ class VertexAIInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): # Get default credentials - will read from GOOGLE_APPLICATION_CREDENTIALS credentials, _ = default(scopes=["https://www.googleapis.com/auth/cloud-platform"]) credentials.refresh(google.auth.transport.requests.Request()) - return SecretStr(credentials.token) + return MySecretStr(credentials.token) except Exception: # If we can't get credentials, return empty string to let LiteLLM handle it # This allows the LiteLLM mixin to work with ADC directly - return SecretStr("") + return MySecretStr("") def get_base_url(self) -> str: """ diff --git a/llama_stack/providers/remote/inference/vllm/__init__.py b/llama_stack/providers/remote/inference/vllm/__init__.py index 5b3ec6d5b..e47597c8f 100644 --- a/llama_stack/providers/remote/inference/vllm/__init__.py +++ b/llama_stack/providers/remote/inference/vllm/__init__.py @@ -4,14 +4,15 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field + +from llama_stack.core.secret_types import MySecretStr from .config import VLLMInferenceAdapterConfig class VLLMProviderDataValidator(BaseModel): - vllm_api_token: SecretStr = Field( - default=SecretStr(""), + vllm_api_token: MySecretStr = Field( description="API token for vLLM models", ) diff --git a/llama_stack/providers/remote/inference/vllm/config.py b/llama_stack/providers/remote/inference/vllm/config.py index 708a39be1..7cc3ebebb 100644 --- a/llama_stack/providers/remote/inference/vllm/config.py +++ b/llama_stack/providers/remote/inference/vllm/config.py @@ -6,8 +6,9 @@ from pathlib import Path -from pydantic import BaseModel, Field, SecretStr, field_validator +from pydantic import BaseModel, Field, field_validator +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -21,8 +22,7 @@ class VLLMInferenceAdapterConfig(BaseModel): default=4096, description="Maximum number of tokens to generate.", ) - api_token: SecretStr = Field( - default=SecretStr(""), + api_token: MySecretStr = Field( description="The API token", ) tls_verify: bool | str = Field( diff --git a/llama_stack/providers/remote/inference/watsonx/config.py b/llama_stack/providers/remote/inference/watsonx/config.py index a28de1226..64421c856 100644 --- a/llama_stack/providers/remote/inference/watsonx/config.py +++ b/llama_stack/providers/remote/inference/watsonx/config.py @@ -7,8 +7,9 @@ import os from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -24,8 +25,8 @@ class WatsonXConfig(BaseModel): default_factory=lambda: os.getenv("WATSONX_BASE_URL", "https://us-south.ml.cloud.ibm.com"), description="A base url for accessing the watsonx.ai", ) - api_key: SecretStr = Field( - default_factory=lambda: SecretStr(os.getenv("WATSONX_API_KEY", "")), + api_key: MySecretStr = Field( + default_factory=lambda: MySecretStr(os.getenv("WATSONX_API_KEY", "")), description="The watsonx API key", ) project_id: str | None = Field( diff --git a/llama_stack/providers/remote/post_training/nvidia/config.py b/llama_stack/providers/remote/post_training/nvidia/config.py index 83d7b49e6..1730b779f 100644 --- a/llama_stack/providers/remote/post_training/nvidia/config.py +++ b/llama_stack/providers/remote/post_training/nvidia/config.py @@ -9,14 +9,16 @@ from typing import Any from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr + # TODO: add default values for all fields class NvidiaPostTrainingConfig(BaseModel): """Configuration for NVIDIA Post Training implementation.""" - api_key: str | None = Field( - default_factory=lambda: os.getenv("NVIDIA_API_KEY"), + api_key: MySecretStr = Field( + default_factory=lambda: MySecretStr(os.getenv("NVIDIA_API_KEY", "")), description="The NVIDIA API key.", ) diff --git a/llama_stack/providers/remote/safety/sambanova/config.py b/llama_stack/providers/remote/safety/sambanova/config.py index 814e5b1e5..ff58decfd 100644 --- a/llama_stack/providers/remote/safety/sambanova/config.py +++ b/llama_stack/providers/remote/safety/sambanova/config.py @@ -6,14 +6,14 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class SambaNovaProviderDataValidator(BaseModel): - sambanova_api_key: SecretStr = Field( - default=SecretStr(""), + sambanova_api_key: MySecretStr = Field( description="Sambanova Cloud API key", ) @@ -24,8 +24,7 @@ class SambaNovaSafetyConfig(BaseModel): default="https://api.sambanova.ai/v1", description="The URL for the SambaNova AI server", ) - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="The SambaNova cloud API Key", ) diff --git a/llama_stack/providers/remote/tool_runtime/bing_search/config.py b/llama_stack/providers/remote/tool_runtime/bing_search/config.py index 8ac4358ba..54b3a9d52 100644 --- a/llama_stack/providers/remote/tool_runtime/bing_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/bing_search/config.py @@ -6,14 +6,15 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field + +from llama_stack.core.secret_types import MySecretStr class BingSearchToolConfig(BaseModel): """Configuration for Bing Search Tool Runtime""" - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="The Bing API key", ) top_k: int = 3 diff --git a/llama_stack/providers/remote/tool_runtime/brave_search/config.py b/llama_stack/providers/remote/tool_runtime/brave_search/config.py index ddc711c12..8fbc42154 100644 --- a/llama_stack/providers/remote/tool_runtime/brave_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/brave_search/config.py @@ -6,12 +6,13 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field + +from llama_stack.core.secret_types import MySecretStr class BraveSearchToolConfig(BaseModel): - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="The Brave Search API Key", ) max_results: int = Field( diff --git a/llama_stack/providers/remote/tool_runtime/tavily_search/config.py b/llama_stack/providers/remote/tool_runtime/tavily_search/config.py index c0de93114..c2e31e5d3 100644 --- a/llama_stack/providers/remote/tool_runtime/tavily_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/tavily_search/config.py @@ -6,12 +6,13 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field + +from llama_stack.core.secret_types import MySecretStr class TavilySearchToolConfig(BaseModel): - api_key: SecretStr = Field( - default=SecretStr(""), + api_key: MySecretStr = Field( description="The Tavily Search API Key", ) max_results: int = Field( diff --git a/llama_stack/providers/remote/tool_runtime/wolfram_alpha/config.py b/llama_stack/providers/remote/tool_runtime/wolfram_alpha/config.py index 457661c06..eea5ffdd2 100644 --- a/llama_stack/providers/remote/tool_runtime/wolfram_alpha/config.py +++ b/llama_stack/providers/remote/tool_runtime/wolfram_alpha/config.py @@ -6,13 +6,17 @@ from typing import Any -from pydantic import BaseModel +from pydantic import BaseModel, Field + +from llama_stack.core.secret_types import MySecretStr class WolframAlphaToolConfig(BaseModel): """Configuration for WolframAlpha Tool Runtime""" - api_key: str | None = None + api_key: MySecretStr = Field( + description="The WolframAlpha API Key", + ) @classmethod def sample_run_config(cls, __distro_dir__: str, **kwargs: Any) -> dict[str, Any]: diff --git a/llama_stack/providers/remote/vector_io/pgvector/config.py b/llama_stack/providers/remote/vector_io/pgvector/config.py index 1c6d0ed52..ee4ef40d0 100644 --- a/llama_stack/providers/remote/vector_io/pgvector/config.py +++ b/llama_stack/providers/remote/vector_io/pgvector/config.py @@ -6,8 +6,9 @@ from typing import Any -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.kvstore.config import ( KVStoreConfig, SqliteKVStoreConfig, @@ -21,7 +22,7 @@ class PGVectorVectorIOConfig(BaseModel): port: int | None = Field(default=5432) db: str | None = Field(default="postgres") user: str | None = Field(default="postgres") - password: SecretStr = Field(default=SecretStr("mysecretpassword")) + password: MySecretStr = Field(default=MySecretStr("mysecretpassword")) kvstore: KVStoreConfig | None = Field(description="Config for KV store backend (SQLite only for now)", default=None) @classmethod diff --git a/llama_stack/providers/remote/vector_io/qdrant/config.py b/llama_stack/providers/remote/vector_io/qdrant/config.py index ff5506236..58044e248 100644 --- a/llama_stack/providers/remote/vector_io/qdrant/config.py +++ b/llama_stack/providers/remote/vector_io/qdrant/config.py @@ -6,8 +6,9 @@ from typing import Any -from pydantic import BaseModel +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.kvstore.config import ( KVStoreConfig, SqliteKVStoreConfig, @@ -23,7 +24,9 @@ class QdrantVectorIOConfig(BaseModel): grpc_port: int = 6334 prefer_grpc: bool = False https: bool | None = None - api_key: str | None = None + api_key: MySecretStr = Field( + description="The API key for the Qdrant instance", + ) prefix: str | None = None timeout: int | None = None host: str | None = None diff --git a/llama_stack/providers/utils/bedrock/config.py b/llama_stack/providers/utils/bedrock/config.py index 2a5c8e882..ae4018b80 100644 --- a/llama_stack/providers/utils/bedrock/config.py +++ b/llama_stack/providers/utils/bedrock/config.py @@ -6,7 +6,9 @@ import os -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field + +from llama_stack.core.secret_types import MySecretStr class BedrockBaseConfig(BaseModel): @@ -14,12 +16,12 @@ class BedrockBaseConfig(BaseModel): default_factory=lambda: os.getenv("AWS_ACCESS_KEY_ID"), description="The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID", ) - aws_secret_access_key: SecretStr = Field( - default_factory=lambda: SecretStr(os.getenv("AWS_SECRET_ACCESS_KEY", "")), + aws_secret_access_key: MySecretStr = Field( + default_factory=lambda: MySecretStr(os.getenv("AWS_SECRET_ACCESS_KEY", "")), description="The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY", ) - aws_session_token: SecretStr = Field( - default_factory=lambda: SecretStr(os.getenv("AWS_SESSION_TOKEN", "")), + aws_session_token: MySecretStr = Field( + default_factory=lambda: MySecretStr(os.getenv("AWS_SESSION_TOKEN", "")), description="The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN", ) region_name: str | None = Field( diff --git a/llama_stack/providers/utils/inference/litellm_openai_mixin.py b/llama_stack/providers/utils/inference/litellm_openai_mixin.py index 8bdfbbbc9..a5d320e41 100644 --- a/llama_stack/providers/utils/inference/litellm_openai_mixin.py +++ b/llama_stack/providers/utils/inference/litellm_openai_mixin.py @@ -8,7 +8,6 @@ from collections.abc import AsyncGenerator, AsyncIterator from typing import Any import litellm -from pydantic import SecretStr from llama_stack.apis.common.content_types import ( InterleavedContent, @@ -40,6 +39,7 @@ from llama_stack.apis.inference import ( ToolPromptFormat, ) from llama_stack.core.request_headers import NeedsRequestProviderData +from llama_stack.core.secret_types import MySecretStr from llama_stack.log import get_logger from llama_stack.providers.utils.inference.model_registry import ModelRegistryHelper, ProviderModelEntry from llama_stack.providers.utils.inference.openai_compat import ( @@ -69,7 +69,7 @@ class LiteLLMOpenAIMixin( def __init__( self, litellm_provider_name: str, - api_key_from_config: SecretStr, + api_key_from_config: MySecretStr, provider_data_api_key_field: str, model_entries: list[ProviderModelEntry] | None = None, openai_compat_api_base: str | None = None, @@ -255,7 +255,7 @@ class LiteLLMOpenAIMixin( **get_sampling_options(request.sampling_params), } - def get_api_key(self) -> SecretStr: + def get_api_key(self) -> MySecretStr: provider_data = self.get_request_provider_data() key_field = self.provider_data_api_key_field if provider_data and getattr(provider_data, key_field, None): diff --git a/llama_stack/providers/utils/inference/openai_mixin.py b/llama_stack/providers/utils/inference/openai_mixin.py index 7fbd62ef6..4bb7a28c5 100644 --- a/llama_stack/providers/utils/inference/openai_mixin.py +++ b/llama_stack/providers/utils/inference/openai_mixin.py @@ -11,7 +11,6 @@ from collections.abc import AsyncIterator from typing import Any from openai import NOT_GIVEN, AsyncOpenAI -from pydantic import SecretStr from llama_stack.apis.inference import ( Model, @@ -25,6 +24,7 @@ from llama_stack.apis.inference import ( OpenAIResponseFormatParam, ) from llama_stack.apis.models import ModelType +from llama_stack.core.secret_types import MySecretStr from llama_stack.log import get_logger from llama_stack.providers.utils.inference.model_registry import ModelRegistryHelper from llama_stack.providers.utils.inference.openai_compat import prepare_openai_completion_params @@ -71,14 +71,14 @@ class OpenAIMixin(ModelRegistryHelper, ABC): allowed_models: list[str] = [] @abstractmethod - def get_api_key(self) -> SecretStr: + def get_api_key(self) -> MySecretStr: """ Get the API key. This method must be implemented by child classes to provide the API key for authenticating with the OpenAI API or compatible endpoints. - :return: The API key as a SecretStr + :return: The API key as a MySecretStr """ pass @@ -114,7 +114,7 @@ class OpenAIMixin(ModelRegistryHelper, ABC): implemented by child classes. """ return AsyncOpenAI( - api_key=self.get_api_key(), + api_key=self.get_api_key().get_secret_value(), base_url=self.get_base_url(), **self.get_extra_client_params(), ) diff --git a/llama_stack/providers/utils/kvstore/config.py b/llama_stack/providers/utils/kvstore/config.py index baab4e372..a8dd1a99a 100644 --- a/llama_stack/providers/utils/kvstore/config.py +++ b/llama_stack/providers/utils/kvstore/config.py @@ -8,8 +8,9 @@ import re from enum import Enum from typing import Annotated, Literal -from pydantic import BaseModel, Field, SecretStr, field_validator +from pydantic import BaseModel, Field, field_validator +from llama_stack.core.secret_types import MySecretStr from llama_stack.core.utils.config_dirs import RUNTIME_BASE_DIR @@ -74,7 +75,7 @@ class PostgresKVStoreConfig(CommonConfig): port: int = 5432 db: str = "llamastack" user: str - password: SecretStr = SecretStr("") + password: MySecretStr = MySecretStr("") ssl_mode: str | None = None ca_cert_path: str | None = None table_name: str = "llamastack_kvstore" @@ -118,7 +119,7 @@ class MongoDBKVStoreConfig(CommonConfig): port: int = 27017 db: str = "llamastack" user: str | None = None - password: SecretStr = SecretStr("") + password: MySecretStr = MySecretStr("") collection_name: str = "llamastack_kvstore" @classmethod diff --git a/llama_stack/providers/utils/sqlstore/sqlstore.py b/llama_stack/providers/utils/sqlstore/sqlstore.py index 6eaafccfe..3bcd8f40d 100644 --- a/llama_stack/providers/utils/sqlstore/sqlstore.py +++ b/llama_stack/providers/utils/sqlstore/sqlstore.py @@ -9,8 +9,9 @@ from enum import StrEnum from pathlib import Path from typing import Annotated, Literal -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field +from llama_stack.core.secret_types import MySecretStr from llama_stack.core.utils.config_dirs import RUNTIME_BASE_DIR from .api import SqlStore @@ -63,7 +64,7 @@ class PostgresSqlStoreConfig(SqlAlchemySqlStoreConfig): port: int = 5432 db: str = "llamastack" user: str - password: SecretStr = SecretStr("") + password: MySecretStr = MySecretStr("") @property def engine_str(self) -> str: diff --git a/tests/unit/providers/inference/test_litellm_openai_mixin.py b/tests/unit/providers/inference/test_litellm_openai_mixin.py index cf7623dd1..48bf5ce38 100644 --- a/tests/unit/providers/inference/test_litellm_openai_mixin.py +++ b/tests/unit/providers/inference/test_litellm_openai_mixin.py @@ -8,19 +8,20 @@ import json from unittest.mock import MagicMock import pytest -from pydantic import BaseModel, Field, SecretStr +from pydantic import BaseModel, Field from llama_stack.core.request_headers import request_provider_data_context +from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.inference.litellm_openai_mixin import LiteLLMOpenAIMixin # Test fixtures and helper classes class TestConfig(BaseModel): - api_key: SecretStr | None = Field(default=None) + api_key: MySecretStr | None = Field(default=None) class TestProviderDataValidator(BaseModel): - test_api_key: SecretStr | None = Field(default=None) + test_api_key: MySecretStr | None = Field(default=None) class TestLiteLLMAdapter(LiteLLMOpenAIMixin): @@ -36,7 +37,7 @@ class TestLiteLLMAdapter(LiteLLMOpenAIMixin): @pytest.fixture def adapter_with_config_key(): """Fixture to create adapter with API key in config""" - config = TestConfig(api_key=SecretStr("config-api-key")) + config = TestConfig(api_key=MySecretStr("config-api-key")) adapter = TestLiteLLMAdapter(config) adapter.__provider_spec__ = MagicMock() adapter.__provider_spec__.provider_data_validator = ( diff --git a/tests/unit/providers/inference/test_openai_base_url_config.py b/tests/unit/providers/inference/test_openai_base_url_config.py index 7bc908069..40c789073 100644 --- a/tests/unit/providers/inference/test_openai_base_url_config.py +++ b/tests/unit/providers/inference/test_openai_base_url_config.py @@ -7,9 +7,14 @@ import os from unittest.mock import MagicMock, patch -from pydantic import SecretStr +from llama_stack.core.secret_types import MySecretStr + + +# Wrapper for backward compatibility in tests +def replace_env_vars_compat(config, path=""): + return replace_env_vars_compat(config, path, None, None) + -from llama_stack.core.stack import replace_env_vars from llama_stack.providers.remote.inference.openai.config import OpenAIConfig from llama_stack.providers.remote.inference.openai.openai import OpenAIInferenceAdapter @@ -37,7 +42,7 @@ class TestOpenAIBaseURLConfig: """Test that the adapter uses base URL from OPENAI_BASE_URL environment variable.""" # Use sample_run_config which has proper environment variable syntax config_data = OpenAIConfig.sample_run_config(api_key="test-key") - processed_config = replace_env_vars(config_data) + processed_config = replace_env_vars_compat(config_data) config = OpenAIConfig.model_validate(processed_config) adapter = OpenAIInferenceAdapter(config) @@ -61,14 +66,14 @@ class TestOpenAIBaseURLConfig: adapter = OpenAIInferenceAdapter(config) # Mock the get_api_key method since it's delegated to LiteLLMOpenAIMixin - adapter.get_api_key = MagicMock(return_value=SecretStr("test-key")) + adapter.get_api_key = MagicMock(return_value=MySecretStr("test-key")) # Access the client property to trigger AsyncOpenAI initialization _ = adapter.client # Verify AsyncOpenAI was called with the correct base_url mock_openai_class.assert_called_once_with( - api_key=SecretStr("test-key"), + api_key=MySecretStr("test-key"), base_url=custom_url, ) @@ -80,7 +85,7 @@ class TestOpenAIBaseURLConfig: adapter = OpenAIInferenceAdapter(config) # Mock the get_api_key method - adapter.get_api_key = MagicMock(return_value=SecretStr("test-key")) + adapter.get_api_key = MagicMock(return_value=MySecretStr("test-key")) # Mock a model object that will be returned by models.list() mock_model = MagicMock() @@ -103,7 +108,7 @@ class TestOpenAIBaseURLConfig: # Verify the client was created with the custom URL mock_openai_class.assert_called_with( - api_key=SecretStr("test-key"), + api_key=MySecretStr("test-key"), base_url=custom_url, ) @@ -116,12 +121,12 @@ class TestOpenAIBaseURLConfig: """Test that setting OPENAI_BASE_URL environment variable affects where model availability is checked.""" # Use sample_run_config which has proper environment variable syntax config_data = OpenAIConfig.sample_run_config(api_key="test-key") - processed_config = replace_env_vars(config_data) + processed_config = replace_env_vars_compat(config_data) config = OpenAIConfig.model_validate(processed_config) adapter = OpenAIInferenceAdapter(config) # Mock the get_api_key method - adapter.get_api_key = MagicMock(return_value=SecretStr("test-key")) + adapter.get_api_key = MagicMock(return_value=MySecretStr("test-key")) # Mock a model object that will be returned by models.list() mock_model = MagicMock() @@ -144,6 +149,6 @@ class TestOpenAIBaseURLConfig: # Verify the client was created with the environment variable URL mock_openai_class.assert_called_with( - api_key=SecretStr("test-key"), + api_key=MySecretStr("test-key"), base_url="https://proxy.openai.com/v1", ) diff --git a/tests/unit/server/test_replace_env_vars.py b/tests/unit/server/test_replace_env_vars.py index 14b3b7231..0c9bec860 100644 --- a/tests/unit/server/test_replace_env_vars.py +++ b/tests/unit/server/test_replace_env_vars.py @@ -8,7 +8,10 @@ import os import pytest -from llama_stack.core.stack import replace_env_vars + +# Wrapper for backward compatibility in tests +def replace_env_vars_compat(config, path=""): + return replace_env_vars_compat(config, path, None, None) @pytest.fixture @@ -32,52 +35,54 @@ def setup_env_vars(): def test_simple_replacement(setup_env_vars): - assert replace_env_vars("${env.TEST_VAR}") == "test_value" + assert replace_env_vars_compat("${env.TEST_VAR}") == "test_value" def test_default_value_when_not_set(setup_env_vars): - assert replace_env_vars("${env.NOT_SET:=default}") == "default" + assert replace_env_vars_compat("${env.NOT_SET:=default}") == "default" def test_default_value_when_set(setup_env_vars): - assert replace_env_vars("${env.TEST_VAR:=default}") == "test_value" + assert replace_env_vars_compat("${env.TEST_VAR:=default}") == "test_value" def test_default_value_when_empty(setup_env_vars): - assert replace_env_vars("${env.EMPTY_VAR:=default}") == "default" + assert replace_env_vars_compat("${env.EMPTY_VAR:=default}") == "default" def test_none_value_when_empty(setup_env_vars): - assert replace_env_vars("${env.EMPTY_VAR:=}") is None + assert replace_env_vars_compat("${env.EMPTY_VAR:=}") is None def test_value_when_set(setup_env_vars): - assert replace_env_vars("${env.TEST_VAR:=}") == "test_value" + assert replace_env_vars_compat("${env.TEST_VAR:=}") == "test_value" def test_empty_var_no_default(setup_env_vars): - assert replace_env_vars("${env.EMPTY_VAR_NO_DEFAULT:+}") is None + assert replace_env_vars_compat("${env.EMPTY_VAR_NO_DEFAULT:+}") is None def test_conditional_value_when_set(setup_env_vars): - assert replace_env_vars("${env.TEST_VAR:+conditional}") == "conditional" + assert replace_env_vars_compat("${env.TEST_VAR:+conditional}") == "conditional" def test_conditional_value_when_not_set(setup_env_vars): - assert replace_env_vars("${env.NOT_SET:+conditional}") is None + assert replace_env_vars_compat("${env.NOT_SET:+conditional}") is None def test_conditional_value_when_empty(setup_env_vars): - assert replace_env_vars("${env.EMPTY_VAR:+conditional}") is None + assert replace_env_vars_compat("${env.EMPTY_VAR:+conditional}") is None def test_conditional_value_with_zero(setup_env_vars): - assert replace_env_vars("${env.ZERO_VAR:+conditional}") == "conditional" + assert replace_env_vars_compat("${env.ZERO_VAR:+conditional}") == "conditional" def test_mixed_syntax(setup_env_vars): - assert replace_env_vars("${env.TEST_VAR:=default} and ${env.NOT_SET:+conditional}") == "test_value and " - assert replace_env_vars("${env.NOT_SET:=default} and ${env.TEST_VAR:+conditional}") == "default and conditional" + assert replace_env_vars_compat("${env.TEST_VAR:=default} and ${env.NOT_SET:+conditional}") == "test_value and " + assert ( + replace_env_vars_compat("${env.NOT_SET:=default} and ${env.TEST_VAR:+conditional}") == "default and conditional" + ) def test_nested_structures(setup_env_vars): @@ -87,11 +92,11 @@ def test_nested_structures(setup_env_vars): "key3": {"nested": "${env.NOT_SET:+conditional}"}, } expected = {"key1": "test_value", "key2": ["default", "conditional"], "key3": {"nested": None}} - assert replace_env_vars(data) == expected + assert replace_env_vars_compat(data) == expected def test_explicit_strings_preserved(setup_env_vars): # Explicit strings that look like numbers/booleans should remain strings data = {"port": "8080", "enabled": "true", "count": "123", "ratio": "3.14"} expected = {"port": "8080", "enabled": "true", "count": "123", "ratio": "3.14"} - assert replace_env_vars(data) == expected + assert replace_env_vars_compat(data) == expected From 2a34226727fd89824fc8f4c9f59726fd881232af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Fri, 26 Sep 2025 10:33:33 +0200 Subject: [PATCH 4/4] revert: do not use MySecretStr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don't need this if we can set it to empty string. Signed-off-by: Sébastien Han --- .../providers/datasetio/remote_nvidia.mdx | 2 +- docs/docs/providers/files/remote_s3.mdx | 2 +- .../providers/inference/remote_anthropic.mdx | 2 +- .../docs/providers/inference/remote_azure.mdx | 2 +- .../providers/inference/remote_bedrock.mdx | 4 +- .../providers/inference/remote_cerebras.mdx | 2 +- .../providers/inference/remote_databricks.mdx | 2 +- .../providers/inference/remote_fireworks.mdx | 2 +- .../providers/inference/remote_gemini.mdx | 2 +- docs/docs/providers/inference/remote_groq.mdx | 2 +- .../inference/remote_hf_endpoint.mdx | 2 +- .../inference/remote_hf_serverless.mdx | 2 +- .../inference/remote_llama-openai-compat.mdx | 2 +- .../providers/inference/remote_nvidia.mdx | 2 +- .../providers/inference/remote_openai.mdx | 2 +- .../inference/remote_passthrough.mdx | 2 +- .../providers/inference/remote_runpod.mdx | 2 +- .../providers/inference/remote_sambanova.mdx | 2 +- .../providers/inference/remote_together.mdx | 2 +- docs/docs/providers/inference/remote_vllm.mdx | 2 +- .../providers/inference/remote_watsonx.mdx | 2 +- .../providers/post_training/remote_nvidia.mdx | 2 +- docs/docs/providers/safety/remote_bedrock.mdx | 4 +- .../providers/safety/remote_sambanova.mdx | 2 +- .../providers/scoring/inline_braintrust.mdx | 2 +- .../tool_runtime/remote_bing-search.mdx | 2 +- .../tool_runtime/remote_brave-search.mdx | 2 +- .../tool_runtime/remote_tavily-search.mdx | 2 +- .../tool_runtime/remote_wolfram-alpha.mdx | 2 +- .../providers/vector_io/remote_milvus.mdx | 2 +- .../providers/vector_io/remote_pgvector.mdx | 2 +- .../providers/vector_io/remote_qdrant.mdx | 2 +- llama_stack/core/secret_types.py | 21 ----------- llama_stack/core/stack.py | 10 ++++- .../inline/scoring/braintrust/__init__.py | 5 +-- .../inline/scoring/braintrust/braintrust.py | 5 +-- .../inline/scoring/braintrust/config.py | 6 +-- .../remote/datasetio/nvidia/config.py | 8 ++-- .../providers/remote/files/s3/config.py | 5 +-- .../remote/inference/anthropic/anthropic.py | 2 +- .../remote/inference/anthropic/config.py | 7 ++-- .../remote/inference/azure/config.py | 7 ++-- .../remote/inference/cerebras/config.py | 7 ++-- .../remote/inference/databricks/config.py | 5 +-- .../remote/inference/fireworks/config.py | 5 +-- .../remote/inference/gemini/config.py | 7 ++-- .../remote/inference/gemini/gemini.py | 2 +- .../providers/remote/inference/groq/config.py | 7 ++-- .../inference/llama_openai_compat/config.py | 7 ++-- .../remote/inference/nvidia/config.py | 7 ++-- .../remote/inference/openai/config.py | 7 ++-- .../remote/inference/passthrough/config.py | 5 +-- .../remote/inference/runpod/config.py | 5 +-- .../remote/inference/sambanova/config.py | 7 ++-- .../remote/inference/sambanova/sambanova.py | 2 +- .../providers/remote/inference/tgi/config.py | 7 ++-- .../providers/remote/inference/tgi/tgi.py | 6 +-- .../remote/inference/together/config.py | 5 +-- .../remote/inference/vertexai/vertexai.py | 10 ++--- .../remote/inference/vllm/__init__.py | 6 +-- .../providers/remote/inference/vllm/config.py | 6 +-- .../remote/inference/watsonx/config.py | 7 ++-- .../remote/post_training/nvidia/config.py | 8 ++-- .../remote/safety/sambanova/config.py | 7 ++-- .../remote/tool_runtime/bing_search/config.py | 6 +-- .../tool_runtime/brave_search/config.py | 6 +-- .../tool_runtime/tavily_search/config.py | 6 +-- .../tool_runtime/wolfram_alpha/config.py | 6 +-- .../remote/vector_io/milvus/config.py | 4 +- .../remote/vector_io/pgvector/config.py | 5 +-- .../remote/vector_io/qdrant/config.py | 5 +-- .../remote/vector_io/qdrant/qdrant.py | 2 +- llama_stack/providers/utils/bedrock/config.py | 12 +++--- .../utils/inference/litellm_openai_mixin.py | 6 +-- .../providers/utils/inference/openai_mixin.py | 6 +-- llama_stack/providers/utils/kvstore/config.py | 7 ++-- .../providers/utils/sqlstore/sqlstore.py | 5 +-- tests/unit/providers/files/conftest.py | 2 + .../inference/bedrock/test_config.py | 4 +- .../test_inference_client_caching.py | 16 ++++---- .../inference/test_litellm_openai_mixin.py | 9 ++--- .../inference/test_openai_base_url_config.py | 25 +++++-------- .../providers/inference/test_remote_vllm.py | 17 ++++++--- .../unit/providers/nvidia/test_parameters.py | 3 +- .../nvidia/test_supervised_fine_tuning.py | 3 +- tests/unit/server/test_replace_env_vars.py | 37 ++++++++----------- 86 files changed, 208 insertions(+), 263 deletions(-) delete mode 100644 llama_stack/core/secret_types.py diff --git a/docs/docs/providers/datasetio/remote_nvidia.mdx b/docs/docs/providers/datasetio/remote_nvidia.mdx index ba5522fce..9ffd72a58 100644 --- a/docs/docs/providers/datasetio/remote_nvidia.mdx +++ b/docs/docs/providers/datasetio/remote_nvidia.mdx @@ -14,7 +14,7 @@ NVIDIA's dataset I/O provider for accessing datasets from NVIDIA's data platform | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The NVIDIA API key. | +| `api_key` | `` | No | | The NVIDIA API key. | | `dataset_namespace` | `str \| None` | No | default | The NVIDIA dataset namespace. | | `project_id` | `str \| None` | No | test-project | The NVIDIA project ID. | | `datasets_url` | `` | No | http://nemo.test | Base URL for the NeMo Dataset API | diff --git a/docs/docs/providers/files/remote_s3.mdx b/docs/docs/providers/files/remote_s3.mdx index 9de31df44..adf4bced0 100644 --- a/docs/docs/providers/files/remote_s3.mdx +++ b/docs/docs/providers/files/remote_s3.mdx @@ -17,7 +17,7 @@ AWS S3-based file storage provider for scalable cloud file management with metad | `bucket_name` | `` | No | | S3 bucket name to store files | | `region` | `` | No | us-east-1 | AWS region where the bucket is located | | `aws_access_key_id` | `str \| None` | No | | AWS access key ID (optional if using IAM roles) | -| `aws_secret_access_key` | `` | No | | AWS secret access key (optional if using IAM roles) | +| `aws_secret_access_key` | `` | No | | AWS secret access key (optional if using IAM roles) | | `endpoint_url` | `str \| None` | No | | Custom S3 endpoint URL (for MinIO, LocalStack, etc.) | | `auto_create_bucket` | `` | No | False | Automatically create the S3 bucket if it doesn't exist | | `metadata_store` | `utils.sqlstore.sqlstore.SqliteSqlStoreConfig \| utils.sqlstore.sqlstore.PostgresSqlStoreConfig` | No | sqlite | SQL store configuration for file metadata | diff --git a/docs/docs/providers/inference/remote_anthropic.mdx b/docs/docs/providers/inference/remote_anthropic.mdx index 72deb298c..f795ad3f1 100644 --- a/docs/docs/providers/inference/remote_anthropic.mdx +++ b/docs/docs/providers/inference/remote_anthropic.mdx @@ -14,7 +14,7 @@ Anthropic inference provider for accessing Claude models and Anthropic's AI serv | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | API key for Anthropic models | +| `api_key` | `` | No | | API key for Anthropic models | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_azure.mdx b/docs/docs/providers/inference/remote_azure.mdx index 1085e4ad4..0eb0ea755 100644 --- a/docs/docs/providers/inference/remote_azure.mdx +++ b/docs/docs/providers/inference/remote_azure.mdx @@ -21,7 +21,7 @@ https://learn.microsoft.com/en-us/azure/ai-foundry/openai/overview | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | Azure API key for Azure | +| `api_key` | `` | No | | Azure API key for Azure | | `api_base` | `` | No | | Azure API base for Azure (e.g., https://your-resource-name.openai.azure.com) | | `api_version` | `str \| None` | No | | Azure API version for Azure (e.g., 2024-12-01-preview) | | `api_type` | `str \| None` | No | azure | Azure API type for Azure (e.g., azure) | diff --git a/docs/docs/providers/inference/remote_bedrock.mdx b/docs/docs/providers/inference/remote_bedrock.mdx index 627be48e5..ff1ed5ad8 100644 --- a/docs/docs/providers/inference/remote_bedrock.mdx +++ b/docs/docs/providers/inference/remote_bedrock.mdx @@ -15,8 +15,8 @@ AWS Bedrock inference provider for accessing various AI models through AWS's man | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `aws_access_key_id` | `str \| None` | No | | The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID | -| `aws_secret_access_key` | `` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | -| `aws_session_token` | `` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | +| `aws_secret_access_key` | `` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | +| `aws_session_token` | `` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | | `region_name` | `str \| None` | No | | The default AWS Region to use, for example, us-west-1 or us-west-2.Default use environment variable: AWS_DEFAULT_REGION | | `profile_name` | `str \| None` | No | | The profile name that contains credentials to use.Default use environment variable: AWS_PROFILE | | `total_max_attempts` | `int \| None` | No | | An integer representing the maximum number of attempts that will be made for a single request, including the initial attempt. Default use environment variable: AWS_MAX_ATTEMPTS | diff --git a/docs/docs/providers/inference/remote_cerebras.mdx b/docs/docs/providers/inference/remote_cerebras.mdx index 7c96e5115..d9cc93aef 100644 --- a/docs/docs/providers/inference/remote_cerebras.mdx +++ b/docs/docs/providers/inference/remote_cerebras.mdx @@ -15,7 +15,7 @@ Cerebras inference provider for running models on Cerebras Cloud platform. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `base_url` | `` | No | https://api.cerebras.ai | Base URL for the Cerebras API | -| `api_key` | `` | No | | Cerebras API Key | +| `api_key` | `` | No | | Cerebras API Key | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_databricks.mdx b/docs/docs/providers/inference/remote_databricks.mdx index fb3783720..7f736db9d 100644 --- a/docs/docs/providers/inference/remote_databricks.mdx +++ b/docs/docs/providers/inference/remote_databricks.mdx @@ -15,7 +15,7 @@ Databricks inference provider for running models on Databricks' unified analytic | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | | The URL for the Databricks model serving endpoint | -| `api_token` | `` | No | | The Databricks API token | +| `api_token` | `` | No | | The Databricks API token | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_fireworks.mdx b/docs/docs/providers/inference/remote_fireworks.mdx index 73698f28c..0f37ccbc2 100644 --- a/docs/docs/providers/inference/remote_fireworks.mdx +++ b/docs/docs/providers/inference/remote_fireworks.mdx @@ -16,7 +16,7 @@ Fireworks AI inference provider for Llama models and other AI models on the Fire |-------|------|----------|---------|-------------| | `allowed_models` | `list[str \| None` | No | | List of models that should be registered with the model registry. If None, all models are allowed. | | `url` | `` | No | https://api.fireworks.ai/inference/v1 | The URL for the Fireworks server | -| `api_key` | `` | No | | The Fireworks.ai API Key | +| `api_key` | `` | No | | The Fireworks.ai API Key | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_gemini.mdx b/docs/docs/providers/inference/remote_gemini.mdx index 538a9f29b..d9a2f3e8d 100644 --- a/docs/docs/providers/inference/remote_gemini.mdx +++ b/docs/docs/providers/inference/remote_gemini.mdx @@ -14,7 +14,7 @@ Google Gemini inference provider for accessing Gemini models and Google's AI ser | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | API key for Gemini models | +| `api_key` | `` | No | | API key for Gemini models | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_groq.mdx b/docs/docs/providers/inference/remote_groq.mdx index 2f80ab7ca..b6d29496e 100644 --- a/docs/docs/providers/inference/remote_groq.mdx +++ b/docs/docs/providers/inference/remote_groq.mdx @@ -14,7 +14,7 @@ Groq inference provider for ultra-fast inference using Groq's LPU technology. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The Groq API key | +| `api_key` | `` | No | | The Groq API key | | `url` | `` | No | https://api.groq.com | The URL for the Groq AI server | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_hf_endpoint.mdx b/docs/docs/providers/inference/remote_hf_endpoint.mdx index 93468fdbc..d036c17d7 100644 --- a/docs/docs/providers/inference/remote_hf_endpoint.mdx +++ b/docs/docs/providers/inference/remote_hf_endpoint.mdx @@ -15,7 +15,7 @@ HuggingFace Inference Endpoints provider for dedicated model serving. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `endpoint_name` | `` | No | | The name of the Hugging Face Inference Endpoint in the format of '{namespace}/{endpoint_name}' (e.g. 'my-cool-org/meta-llama-3-1-8b-instruct-rce'). Namespace is optional and will default to the user account if not provided. | -| `api_token` | `` | No | | Your Hugging Face user access token (will default to locally saved token if not provided) | +| `api_token` | `` | No | | Your Hugging Face user access token (will default to locally saved token if not provided) | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_hf_serverless.mdx b/docs/docs/providers/inference/remote_hf_serverless.mdx index 80753ed05..1a3c17715 100644 --- a/docs/docs/providers/inference/remote_hf_serverless.mdx +++ b/docs/docs/providers/inference/remote_hf_serverless.mdx @@ -15,7 +15,7 @@ HuggingFace Inference API serverless provider for on-demand model inference. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `huggingface_repo` | `` | No | | The model ID of the model on the Hugging Face Hub (e.g. 'meta-llama/Meta-Llama-3.1-70B-Instruct') | -| `api_token` | `` | No | | Your Hugging Face user access token (will default to locally saved token if not provided) | +| `api_token` | `` | No | | Your Hugging Face user access token (will default to locally saved token if not provided) | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_llama-openai-compat.mdx b/docs/docs/providers/inference/remote_llama-openai-compat.mdx index 478b7dadb..491774844 100644 --- a/docs/docs/providers/inference/remote_llama-openai-compat.mdx +++ b/docs/docs/providers/inference/remote_llama-openai-compat.mdx @@ -14,7 +14,7 @@ Llama OpenAI-compatible provider for using Llama models with OpenAI API format. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The Llama API key | +| `api_key` | `` | No | | The Llama API key | | `openai_compat_api_base` | `` | No | https://api.llama.com/compat/v1/ | The URL for the Llama API server | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_nvidia.mdx b/docs/docs/providers/inference/remote_nvidia.mdx index c51da9547..9aee22e9a 100644 --- a/docs/docs/providers/inference/remote_nvidia.mdx +++ b/docs/docs/providers/inference/remote_nvidia.mdx @@ -15,7 +15,7 @@ NVIDIA inference provider for accessing NVIDIA NIM models and AI services. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://integrate.api.nvidia.com | A base url for accessing the NVIDIA NIM | -| `api_key` | `` | No | | The NVIDIA API key, only needed of using the hosted service | +| `api_key` | `` | No | | The NVIDIA API key, only needed of using the hosted service | | `timeout` | `` | No | 60 | Timeout for the HTTP requests | | `append_api_version` | `` | No | True | When set to false, the API version will not be appended to the base_url. By default, it is true. | diff --git a/docs/docs/providers/inference/remote_openai.mdx b/docs/docs/providers/inference/remote_openai.mdx index 79e9d9ccc..f82bea154 100644 --- a/docs/docs/providers/inference/remote_openai.mdx +++ b/docs/docs/providers/inference/remote_openai.mdx @@ -14,7 +14,7 @@ OpenAI inference provider for accessing GPT models and other OpenAI services. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | API key for OpenAI models | +| `api_key` | `` | No | | API key for OpenAI models | | `base_url` | `` | No | https://api.openai.com/v1 | Base URL for OpenAI API | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_passthrough.mdx b/docs/docs/providers/inference/remote_passthrough.mdx index f35f746db..efaa01d04 100644 --- a/docs/docs/providers/inference/remote_passthrough.mdx +++ b/docs/docs/providers/inference/remote_passthrough.mdx @@ -15,7 +15,7 @@ Passthrough inference provider for connecting to any external inference service | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | | The URL for the passthrough endpoint | -| `api_key` | `` | No | | API Key for the passthrouth endpoint | +| `api_key` | `` | No | | API Key for the passthrouth endpoint | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_runpod.mdx b/docs/docs/providers/inference/remote_runpod.mdx index 8028f0228..1def462f6 100644 --- a/docs/docs/providers/inference/remote_runpod.mdx +++ b/docs/docs/providers/inference/remote_runpod.mdx @@ -15,7 +15,7 @@ RunPod inference provider for running models on RunPod's cloud GPU platform. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `str \| None` | No | | The URL for the Runpod model serving endpoint | -| `api_token` | `` | No | | The API token | +| `api_token` | `` | No | | The API token | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_sambanova.mdx b/docs/docs/providers/inference/remote_sambanova.mdx index 2a63cab16..b5d64e6d9 100644 --- a/docs/docs/providers/inference/remote_sambanova.mdx +++ b/docs/docs/providers/inference/remote_sambanova.mdx @@ -15,7 +15,7 @@ SambaNova inference provider for running models on SambaNova's dataflow architec | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://api.sambanova.ai/v1 | The URL for the SambaNova AI server | -| `api_key` | `` | No | | The SambaNova cloud API Key | +| `api_key` | `` | No | | The SambaNova cloud API Key | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_together.mdx b/docs/docs/providers/inference/remote_together.mdx index 912cf0291..0669a1112 100644 --- a/docs/docs/providers/inference/remote_together.mdx +++ b/docs/docs/providers/inference/remote_together.mdx @@ -16,7 +16,7 @@ Together AI inference provider for open-source models and collaborative AI devel |-------|------|----------|---------|-------------| | `allowed_models` | `list[str \| None` | No | | List of models that should be registered with the model registry. If None, all models are allowed. | | `url` | `` | No | https://api.together.xyz/v1 | The URL for the Together AI server | -| `api_key` | `` | No | | The Together AI API Key | +| `api_key` | `` | No | | The Together AI API Key | ## Sample Configuration diff --git a/docs/docs/providers/inference/remote_vllm.mdx b/docs/docs/providers/inference/remote_vllm.mdx index 4db289376..ed317806a 100644 --- a/docs/docs/providers/inference/remote_vllm.mdx +++ b/docs/docs/providers/inference/remote_vllm.mdx @@ -16,7 +16,7 @@ Remote vLLM inference provider for connecting to vLLM servers. |-------|------|----------|---------|-------------| | `url` | `str \| None` | No | | The URL for the vLLM model serving endpoint | | `max_tokens` | `` | No | 4096 | Maximum number of tokens to generate. | -| `api_token` | `` | No | | The API token | +| `api_token` | `` | No | ********** | The API token | | `tls_verify` | `bool \| str` | No | True | Whether to verify TLS certificates. Can be a boolean or a path to a CA certificate file. | | `refresh_models` | `` | No | False | Whether to refresh models periodically | diff --git a/docs/docs/providers/inference/remote_watsonx.mdx b/docs/docs/providers/inference/remote_watsonx.mdx index 2584a78ac..c1ab57a7b 100644 --- a/docs/docs/providers/inference/remote_watsonx.mdx +++ b/docs/docs/providers/inference/remote_watsonx.mdx @@ -15,7 +15,7 @@ IBM WatsonX inference provider for accessing AI models on IBM's WatsonX platform | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://us-south.ml.cloud.ibm.com | A base url for accessing the watsonx.ai | -| `api_key` | `` | No | | The watsonx API key | +| `api_key` | `` | No | | The watsonx API key | | `project_id` | `str \| None` | No | | The Project ID key | | `timeout` | `` | No | 60 | Timeout for the HTTP requests | diff --git a/docs/docs/providers/post_training/remote_nvidia.mdx b/docs/docs/providers/post_training/remote_nvidia.mdx index 451b1b258..64951b0be 100644 --- a/docs/docs/providers/post_training/remote_nvidia.mdx +++ b/docs/docs/providers/post_training/remote_nvidia.mdx @@ -14,7 +14,7 @@ NVIDIA's post-training provider for fine-tuning models on NVIDIA's platform. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The NVIDIA API key. | +| `api_key` | `` | No | | The NVIDIA API key. | | `dataset_namespace` | `str \| None` | No | default | The NVIDIA dataset namespace. | | `project_id` | `str \| None` | No | test-example-model@v1 | The NVIDIA project ID. | | `customizer_url` | `str \| None` | No | | Base URL for the NeMo Customizer API | diff --git a/docs/docs/providers/safety/remote_bedrock.mdx b/docs/docs/providers/safety/remote_bedrock.mdx index d5362ccd9..e068f1fed 100644 --- a/docs/docs/providers/safety/remote_bedrock.mdx +++ b/docs/docs/providers/safety/remote_bedrock.mdx @@ -15,8 +15,8 @@ AWS Bedrock safety provider for content moderation using AWS's safety services. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `aws_access_key_id` | `str \| None` | No | | The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID | -| `aws_secret_access_key` | `` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | -| `aws_session_token` | `` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | +| `aws_secret_access_key` | `` | No | | The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY | +| `aws_session_token` | `` | No | | The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN | | `region_name` | `str \| None` | No | | The default AWS Region to use, for example, us-west-1 or us-west-2.Default use environment variable: AWS_DEFAULT_REGION | | `profile_name` | `str \| None` | No | | The profile name that contains credentials to use.Default use environment variable: AWS_PROFILE | | `total_max_attempts` | `int \| None` | No | | An integer representing the maximum number of attempts that will be made for a single request, including the initial attempt. Default use environment variable: AWS_MAX_ATTEMPTS | diff --git a/docs/docs/providers/safety/remote_sambanova.mdx b/docs/docs/providers/safety/remote_sambanova.mdx index c63e2160c..3a5a0db7d 100644 --- a/docs/docs/providers/safety/remote_sambanova.mdx +++ b/docs/docs/providers/safety/remote_sambanova.mdx @@ -15,7 +15,7 @@ SambaNova's safety provider for content moderation and safety filtering. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `url` | `` | No | https://api.sambanova.ai/v1 | The URL for the SambaNova AI server | -| `api_key` | `` | No | | The SambaNova cloud API Key | +| `api_key` | `` | No | | The SambaNova cloud API Key | ## Sample Configuration diff --git a/docs/docs/providers/scoring/inline_braintrust.mdx b/docs/docs/providers/scoring/inline_braintrust.mdx index b035c259a..93779b7ac 100644 --- a/docs/docs/providers/scoring/inline_braintrust.mdx +++ b/docs/docs/providers/scoring/inline_braintrust.mdx @@ -14,7 +14,7 @@ Braintrust scoring provider for evaluation and scoring using the Braintrust plat | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `openai_api_key` | `` | No | | The OpenAI API Key | +| `openai_api_key` | `` | No | | The OpenAI API Key | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_bing-search.mdx b/docs/docs/providers/tool_runtime/remote_bing-search.mdx index 02ed6af2c..b7dabdd47 100644 --- a/docs/docs/providers/tool_runtime/remote_bing-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_bing-search.mdx @@ -14,7 +14,7 @@ Bing Search tool for web search capabilities using Microsoft's search engine. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The Bing API key | +| `api_key` | `` | No | | The Bing API key | | `top_k` | `` | No | 3 | | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_brave-search.mdx b/docs/docs/providers/tool_runtime/remote_brave-search.mdx index ccb7f3e23..084ae20f5 100644 --- a/docs/docs/providers/tool_runtime/remote_brave-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_brave-search.mdx @@ -14,7 +14,7 @@ Brave Search tool for web search capabilities with privacy-focused results. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The Brave Search API Key | +| `api_key` | `` | No | | The Brave Search API Key | | `max_results` | `` | No | 3 | The maximum number of results to return | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_tavily-search.mdx b/docs/docs/providers/tool_runtime/remote_tavily-search.mdx index 7947c42c8..1c3429983 100644 --- a/docs/docs/providers/tool_runtime/remote_tavily-search.mdx +++ b/docs/docs/providers/tool_runtime/remote_tavily-search.mdx @@ -14,7 +14,7 @@ Tavily Search tool for AI-optimized web search with structured results. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The Tavily Search API Key | +| `api_key` | `` | No | | The Tavily Search API Key | | `max_results` | `` | No | 3 | The maximum number of results to return | ## Sample Configuration diff --git a/docs/docs/providers/tool_runtime/remote_wolfram-alpha.mdx b/docs/docs/providers/tool_runtime/remote_wolfram-alpha.mdx index 2dd58f043..312d34c8a 100644 --- a/docs/docs/providers/tool_runtime/remote_wolfram-alpha.mdx +++ b/docs/docs/providers/tool_runtime/remote_wolfram-alpha.mdx @@ -14,7 +14,7 @@ Wolfram Alpha tool for computational knowledge and mathematical calculations. | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| -| `api_key` | `` | No | | The WolframAlpha API Key | +| `api_key` | `` | No | | The WolframAlpha API Key | ## Sample Configuration diff --git a/docs/docs/providers/vector_io/remote_milvus.mdx b/docs/docs/providers/vector_io/remote_milvus.mdx index 7f7c08122..b69e729fe 100644 --- a/docs/docs/providers/vector_io/remote_milvus.mdx +++ b/docs/docs/providers/vector_io/remote_milvus.mdx @@ -406,7 +406,7 @@ For more details on TLS configuration, refer to the [TLS setup guide](https://mi | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `uri` | `` | No | | The URI of the Milvus server | -| `token` | `str \| None` | No | | The token of the Milvus server | +| `token` | `` | No | | The token of the Milvus server | | `consistency_level` | `` | No | Strong | The consistency level of the Milvus server | | `kvstore` | `utils.kvstore.config.RedisKVStoreConfig \| utils.kvstore.config.SqliteKVStoreConfig \| utils.kvstore.config.PostgresKVStoreConfig \| utils.kvstore.config.MongoDBKVStoreConfig` | No | sqlite | Config for KV store backend | | `config` | `dict` | No | `{}` | This configuration allows additional fields to be passed through to the underlying Milvus client. See the [Milvus](https://milvus.io/docs/install-overview.md) documentation for more details about Milvus in general. | diff --git a/docs/docs/providers/vector_io/remote_pgvector.mdx b/docs/docs/providers/vector_io/remote_pgvector.mdx index 2853cddeb..6d3157753 100644 --- a/docs/docs/providers/vector_io/remote_pgvector.mdx +++ b/docs/docs/providers/vector_io/remote_pgvector.mdx @@ -217,7 +217,7 @@ See [PGVector's documentation](https://github.com/pgvector/pgvector) for more de | `port` | `int \| None` | No | 5432 | | | `db` | `str \| None` | No | postgres | | | `user` | `str \| None` | No | postgres | | -| `password` | `` | No | ********** | | +| `password` | `` | No | ********** | | | `kvstore` | `utils.kvstore.config.RedisKVStoreConfig \| utils.kvstore.config.SqliteKVStoreConfig \| utils.kvstore.config.PostgresKVStoreConfig \| utils.kvstore.config.MongoDBKVStoreConfig, annotation=NoneType, required=False, default='sqlite', discriminator='type'` | No | | Config for KV store backend (SQLite only for now) | ## Sample Configuration diff --git a/docs/docs/providers/vector_io/remote_qdrant.mdx b/docs/docs/providers/vector_io/remote_qdrant.mdx index c344dfe2e..330bfce2e 100644 --- a/docs/docs/providers/vector_io/remote_qdrant.mdx +++ b/docs/docs/providers/vector_io/remote_qdrant.mdx @@ -22,7 +22,7 @@ Please refer to the inline provider documentation. | `grpc_port` | `` | No | 6334 | | | `prefer_grpc` | `` | No | False | | | `https` | `bool \| None` | No | | | -| `api_key` | `` | No | | The API key for the Qdrant instance | +| `api_key` | `` | No | | The API key for the Qdrant instance | | `prefix` | `str \| None` | No | | | | `timeout` | `int \| None` | No | | | | `host` | `str \| None` | No | | | diff --git a/llama_stack/core/secret_types.py b/llama_stack/core/secret_types.py deleted file mode 100644 index e1700d783..000000000 --- a/llama_stack/core/secret_types.py +++ /dev/null @@ -1,21 +0,0 @@ -# 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 pydantic.types import SecretStr - - -class MySecretStr(SecretStr): - """A SecretStr that can accept None values to avoid mypy type errors. - - This is useful for optional secret fields where you want to avoid - explicit None checks in consuming code. - - We chose to not use the SecretStr from pydantic because it does not allow None values and will - let the provider's library fail if the secret is not provided. - """ - - def __init__(self, secret_value: str | None = None) -> None: - SecretStr.__init__(self, secret_value) # type: ignore[arg-type] diff --git a/llama_stack/core/stack.py b/llama_stack/core/stack.py index 3b93f9913..9c147eebd 100644 --- a/llama_stack/core/stack.py +++ b/llama_stack/core/stack.py @@ -288,6 +288,12 @@ def _convert_string_to_proper_type_with_config(value: str, path: str, provider_c field_name = path.split(".")[-1] if "." in path else path config_class = provider_context["config_class"] + # Only instantiate if the class hasn't been instantiated already + # This handles the case we entered replace_env_vars() with a dict, which + # could happen if we use a sample_run_config() method that returns a dict. Our unit tests do + # this on the adhoc config spec creation. + if isinstance(config_class, str): + config_class = instantiate_class_type(config_class) if hasattr(config_class, "model_fields") and field_name in config_class.model_fields: field_info = config_class.model_fields[field_name] @@ -563,7 +569,9 @@ def run_config_from_adhoc_config_spec( # call method "sample_run_config" on the provider spec config class provider_config_type = instantiate_class_type(provider_spec.config_class) provider_config = replace_env_vars( - provider_config_type.sample_run_config(__distro_dir__=distro_dir), provider_registry=provider_registry + provider_config_type.sample_run_config(__distro_dir__=distro_dir), + provider_registry=provider_registry, + current_provider_context=provider_spec.model_dump(), ) provider_configs_by_api[api_str] = [ diff --git a/llama_stack/providers/inline/scoring/braintrust/__init__.py b/llama_stack/providers/inline/scoring/braintrust/__init__.py index 77c09c615..2f3dce966 100644 --- a/llama_stack/providers/inline/scoring/braintrust/__init__.py +++ b/llama_stack/providers/inline/scoring/braintrust/__init__.py @@ -5,16 +5,15 @@ # the root directory of this source tree. from typing import Any -from pydantic import BaseModel +from pydantic import BaseModel, SecretStr from llama_stack.core.datatypes import Api -from llama_stack.core.secret_types import MySecretStr from .config import BraintrustScoringConfig class BraintrustProviderDataValidator(BaseModel): - openai_api_key: MySecretStr + openai_api_key: SecretStr async def get_provider_impl( diff --git a/llama_stack/providers/inline/scoring/braintrust/braintrust.py b/llama_stack/providers/inline/scoring/braintrust/braintrust.py index 8d80b5920..7f2b1e205 100644 --- a/llama_stack/providers/inline/scoring/braintrust/braintrust.py +++ b/llama_stack/providers/inline/scoring/braintrust/braintrust.py @@ -17,7 +17,7 @@ from autoevals.ragas import ( ContextRelevancy, Faithfulness, ) -from pydantic import BaseModel +from pydantic import BaseModel, SecretStr from llama_stack.apis.datasetio import DatasetIO from llama_stack.apis.datasets import Datasets @@ -31,7 +31,6 @@ from llama_stack.apis.scoring import ( from llama_stack.apis.scoring_functions import ScoringFn, ScoringFnParams from llama_stack.core.datatypes import Api from llama_stack.core.request_headers import NeedsRequestProviderData -from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.datatypes import ScoringFunctionsProtocolPrivate from llama_stack.providers.utils.common.data_schema_validator import ( get_valid_schemas, @@ -153,7 +152,7 @@ class BraintrustScoringImpl( raise ValueError( 'Pass OpenAI API Key in the header X-LlamaStack-Provider-Data as { "openai_api_key": }' ) - self.config.openai_api_key = MySecretStr(provider_data.openai_api_key) + self.config.openai_api_key = SecretStr(provider_data.openai_api_key) os.environ["OPENAI_API_KEY"] = self.config.openai_api_key.get_secret_value() diff --git a/llama_stack/providers/inline/scoring/braintrust/config.py b/llama_stack/providers/inline/scoring/braintrust/config.py index 3520ffb08..a3fb937f6 100644 --- a/llama_stack/providers/inline/scoring/braintrust/config.py +++ b/llama_stack/providers/inline/scoring/braintrust/config.py @@ -5,13 +5,11 @@ # the root directory of this source tree. from typing import Any -from pydantic import BaseModel, Field - -from llama_stack.core.secret_types import MySecretStr +from pydantic import BaseModel, Field, SecretStr class BraintrustScoringConfig(BaseModel): - openai_api_key: MySecretStr = Field( + openai_api_key: SecretStr = Field( description="The OpenAI API Key", ) diff --git a/llama_stack/providers/remote/datasetio/nvidia/config.py b/llama_stack/providers/remote/datasetio/nvidia/config.py index aa1ac163d..03a51f59b 100644 --- a/llama_stack/providers/remote/datasetio/nvidia/config.py +++ b/llama_stack/providers/remote/datasetio/nvidia/config.py @@ -8,16 +8,14 @@ import os import warnings from typing import Any -from pydantic import BaseModel, Field - -from llama_stack.core.secret_types import MySecretStr +from pydantic import BaseModel, Field, SecretStr class NvidiaDatasetIOConfig(BaseModel): """Configuration for NVIDIA DatasetIO implementation.""" - api_key: MySecretStr = Field( - default_factory=lambda: MySecretStr(os.getenv("NVIDIA_API_KEY", "")), + api_key: SecretStr = Field( + default_factory=lambda: SecretStr(os.getenv("NVIDIA_API_KEY", "")), description="The NVIDIA API key.", ) diff --git a/llama_stack/providers/remote/files/s3/config.py b/llama_stack/providers/remote/files/s3/config.py index e8fc452d8..83bec65d8 100644 --- a/llama_stack/providers/remote/files/s3/config.py +++ b/llama_stack/providers/remote/files/s3/config.py @@ -6,9 +6,8 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.sqlstore.sqlstore import SqliteSqlStoreConfig, SqlStoreConfig @@ -18,7 +17,7 @@ class S3FilesImplConfig(BaseModel): bucket_name: str = Field(description="S3 bucket name to store files") region: str = Field(default="us-east-1", description="AWS region where the bucket is located") aws_access_key_id: str | None = Field(default=None, description="AWS access key ID (optional if using IAM roles)") - aws_secret_access_key: MySecretStr = Field(description="AWS secret access key (optional if using IAM roles)") + aws_secret_access_key: SecretStr = Field(description="AWS secret access key (optional if using IAM roles)") endpoint_url: str | None = Field(default=None, description="Custom S3 endpoint URL (for MinIO, LocalStack, etc.)") auto_create_bucket: bool = Field( default=False, description="Automatically create the S3 bucket if it doesn't exist" diff --git a/llama_stack/providers/remote/inference/anthropic/anthropic.py b/llama_stack/providers/remote/inference/anthropic/anthropic.py index 41f65ade7..6d769879e 100644 --- a/llama_stack/providers/remote/inference/anthropic/anthropic.py +++ b/llama_stack/providers/remote/inference/anthropic/anthropic.py @@ -28,7 +28,7 @@ class AnthropicInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): LiteLLMOpenAIMixin.__init__( self, litellm_provider_name="anthropic", - api_key_from_config=config.api_key.get_secret_value() if config.api_key else None, + api_key_from_config=config.api_key, provider_data_api_key_field="anthropic_api_key", ) self.config = config diff --git a/llama_stack/providers/remote/inference/anthropic/config.py b/llama_stack/providers/remote/inference/anthropic/config.py index eb77b328f..25b2dbbcf 100644 --- a/llama_stack/providers/remote/inference/anthropic/config.py +++ b/llama_stack/providers/remote/inference/anthropic/config.py @@ -6,21 +6,20 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class AnthropicProviderDataValidator(BaseModel): - anthropic_api_key: MySecretStr = Field( + anthropic_api_key: SecretStr = Field( description="API key for Anthropic models", ) @json_schema_type class AnthropicConfig(BaseModel): - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="API key for Anthropic models", ) diff --git a/llama_stack/providers/remote/inference/azure/config.py b/llama_stack/providers/remote/inference/azure/config.py index ab53e85c2..fe9d61d53 100644 --- a/llama_stack/providers/remote/inference/azure/config.py +++ b/llama_stack/providers/remote/inference/azure/config.py @@ -7,14 +7,13 @@ import os from typing import Any -from pydantic import BaseModel, Field, HttpUrl +from pydantic import BaseModel, Field, HttpUrl, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class AzureProviderDataValidator(BaseModel): - azure_api_key: MySecretStr = Field( + azure_api_key: SecretStr = Field( description="Azure API key for Azure", ) azure_api_base: HttpUrl = Field( @@ -32,7 +31,7 @@ class AzureProviderDataValidator(BaseModel): @json_schema_type class AzureConfig(BaseModel): - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="Azure API key for Azure", ) api_base: HttpUrl = Field( diff --git a/llama_stack/providers/remote/inference/cerebras/config.py b/llama_stack/providers/remote/inference/cerebras/config.py index 5c5de27a5..519bd9119 100644 --- a/llama_stack/providers/remote/inference/cerebras/config.py +++ b/llama_stack/providers/remote/inference/cerebras/config.py @@ -7,9 +7,8 @@ import os from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type DEFAULT_BASE_URL = "https://api.cerebras.ai" @@ -21,8 +20,8 @@ class CerebrasImplConfig(BaseModel): default=os.environ.get("CEREBRAS_BASE_URL", DEFAULT_BASE_URL), description="Base URL for the Cerebras API", ) - api_key: MySecretStr = Field( - default=MySecretStr(os.environ.get("CEREBRAS_API_KEY")), + api_key: SecretStr = Field( + default=SecretStr(os.environ.get("CEREBRAS_API_KEY")), description="Cerebras API Key", ) diff --git a/llama_stack/providers/remote/inference/databricks/config.py b/llama_stack/providers/remote/inference/databricks/config.py index 92b250034..2e2b0ff11 100644 --- a/llama_stack/providers/remote/inference/databricks/config.py +++ b/llama_stack/providers/remote/inference/databricks/config.py @@ -6,9 +6,8 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -18,7 +17,7 @@ class DatabricksImplConfig(BaseModel): default=None, description="The URL for the Databricks model serving endpoint", ) - api_token: MySecretStr = Field( + api_token: SecretStr = Field( description="The Databricks API token", ) diff --git a/llama_stack/providers/remote/inference/fireworks/config.py b/llama_stack/providers/remote/inference/fireworks/config.py index 14f2dffc1..c0f5d11d1 100644 --- a/llama_stack/providers/remote/inference/fireworks/config.py +++ b/llama_stack/providers/remote/inference/fireworks/config.py @@ -6,9 +6,8 @@ from typing import Any -from pydantic import Field +from pydantic import Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.inference.model_registry import RemoteInferenceProviderConfig from llama_stack.schema_utils import json_schema_type @@ -19,7 +18,7 @@ class FireworksImplConfig(RemoteInferenceProviderConfig): default="https://api.fireworks.ai/inference/v1", description="The URL for the Fireworks server", ) - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="The Fireworks.ai API Key", ) diff --git a/llama_stack/providers/remote/inference/gemini/config.py b/llama_stack/providers/remote/inference/gemini/config.py index a90fb93cb..ddb3aee8a 100644 --- a/llama_stack/providers/remote/inference/gemini/config.py +++ b/llama_stack/providers/remote/inference/gemini/config.py @@ -6,21 +6,20 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class GeminiProviderDataValidator(BaseModel): - gemini_api_key: MySecretStr = Field( + gemini_api_key: SecretStr = Field( description="API key for Gemini models", ) @json_schema_type class GeminiConfig(BaseModel): - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="API key for Gemini models", ) diff --git a/llama_stack/providers/remote/inference/gemini/gemini.py b/llama_stack/providers/remote/inference/gemini/gemini.py index b02d0d611..673811d8c 100644 --- a/llama_stack/providers/remote/inference/gemini/gemini.py +++ b/llama_stack/providers/remote/inference/gemini/gemini.py @@ -20,7 +20,7 @@ class GeminiInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): LiteLLMOpenAIMixin.__init__( self, litellm_provider_name="gemini", - api_key_from_config=config.api_key.get_secret_value() if config.api_key else None, + api_key_from_config=config.api_key, provider_data_api_key_field="gemini_api_key", ) self.config = config diff --git a/llama_stack/providers/remote/inference/groq/config.py b/llama_stack/providers/remote/inference/groq/config.py index c3c3fd580..e19ee8b7b 100644 --- a/llama_stack/providers/remote/inference/groq/config.py +++ b/llama_stack/providers/remote/inference/groq/config.py @@ -6,21 +6,20 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class GroqProviderDataValidator(BaseModel): - groq_api_key: MySecretStr = Field( + groq_api_key: SecretStr = Field( description="API key for Groq models", ) @json_schema_type class GroqConfig(BaseModel): - api_key: MySecretStr = Field( + api_key: SecretStr = Field( # The Groq client library loads the GROQ_API_KEY environment variable by default description="The Groq API key", ) diff --git a/llama_stack/providers/remote/inference/llama_openai_compat/config.py b/llama_stack/providers/remote/inference/llama_openai_compat/config.py index 863988dd6..7291c81d2 100644 --- a/llama_stack/providers/remote/inference/llama_openai_compat/config.py +++ b/llama_stack/providers/remote/inference/llama_openai_compat/config.py @@ -6,21 +6,20 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class LlamaProviderDataValidator(BaseModel): - llama_api_key: MySecretStr = Field( + llama_api_key: SecretStr = Field( description="API key for api.llama models", ) @json_schema_type class LlamaCompatConfig(BaseModel): - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="The Llama API key", ) diff --git a/llama_stack/providers/remote/inference/nvidia/config.py b/llama_stack/providers/remote/inference/nvidia/config.py index 2f6dfe9f3..9ca2ffdd6 100644 --- a/llama_stack/providers/remote/inference/nvidia/config.py +++ b/llama_stack/providers/remote/inference/nvidia/config.py @@ -7,9 +7,8 @@ import os from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -40,8 +39,8 @@ class NVIDIAConfig(BaseModel): default_factory=lambda: os.getenv("NVIDIA_BASE_URL", "https://integrate.api.nvidia.com"), description="A base url for accessing the NVIDIA NIM", ) - api_key: MySecretStr = Field( - default_factory=lambda: MySecretStr(os.getenv("NVIDIA_API_KEY", "")), + api_key: SecretStr = Field( + default_factory=lambda: SecretStr(os.getenv("NVIDIA_API_KEY", "")), description="The NVIDIA API key, only needed of using the hosted service", ) timeout: int = Field( diff --git a/llama_stack/providers/remote/inference/openai/config.py b/llama_stack/providers/remote/inference/openai/config.py index c8d6be9c2..d7bf9c341 100644 --- a/llama_stack/providers/remote/inference/openai/config.py +++ b/llama_stack/providers/remote/inference/openai/config.py @@ -6,21 +6,20 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class OpenAIProviderDataValidator(BaseModel): - openai_api_key: MySecretStr = Field( + openai_api_key: SecretStr = Field( description="API key for OpenAI models", ) @json_schema_type class OpenAIConfig(BaseModel): - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="API key for OpenAI models", ) base_url: str = Field( diff --git a/llama_stack/providers/remote/inference/passthrough/config.py b/llama_stack/providers/remote/inference/passthrough/config.py index dee3b9173..443b3bba3 100644 --- a/llama_stack/providers/remote/inference/passthrough/config.py +++ b/llama_stack/providers/remote/inference/passthrough/config.py @@ -6,9 +6,8 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -19,7 +18,7 @@ class PassthroughImplConfig(BaseModel): description="The URL for the passthrough endpoint", ) - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="API Key for the passthrouth endpoint", ) diff --git a/llama_stack/providers/remote/inference/runpod/config.py b/llama_stack/providers/remote/inference/runpod/config.py index eaabe4e33..12b8b4ba7 100644 --- a/llama_stack/providers/remote/inference/runpod/config.py +++ b/llama_stack/providers/remote/inference/runpod/config.py @@ -6,9 +6,8 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -18,7 +17,7 @@ class RunpodImplConfig(BaseModel): default=None, description="The URL for the Runpod model serving endpoint", ) - api_token: MySecretStr = Field( + api_token: SecretStr = Field( description="The API token", ) diff --git a/llama_stack/providers/remote/inference/sambanova/config.py b/llama_stack/providers/remote/inference/sambanova/config.py index 10e28c3ac..e0e4aa2cf 100644 --- a/llama_stack/providers/remote/inference/sambanova/config.py +++ b/llama_stack/providers/remote/inference/sambanova/config.py @@ -6,14 +6,13 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class SambaNovaProviderDataValidator(BaseModel): - sambanova_api_key: MySecretStr = Field( + sambanova_api_key: SecretStr = Field( description="Sambanova Cloud API key", ) @@ -24,7 +23,7 @@ class SambaNovaImplConfig(BaseModel): default="https://api.sambanova.ai/v1", description="The URL for the SambaNova AI server", ) - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="The SambaNova cloud API Key", ) diff --git a/llama_stack/providers/remote/inference/sambanova/sambanova.py b/llama_stack/providers/remote/inference/sambanova/sambanova.py index 4d8fd11cd..a8e39ebd6 100644 --- a/llama_stack/providers/remote/inference/sambanova/sambanova.py +++ b/llama_stack/providers/remote/inference/sambanova/sambanova.py @@ -29,7 +29,7 @@ class SambaNovaInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): LiteLLMOpenAIMixin.__init__( self, litellm_provider_name="sambanova", - api_key_from_config=self.config.api_key.get_secret_value() if self.config.api_key else None, + api_key_from_config=self.config.api_key, provider_data_api_key_field="sambanova_api_key", openai_compat_api_base=self.config.url, download_images=True, # SambaNova requires base64 image encoding diff --git a/llama_stack/providers/remote/inference/tgi/config.py b/llama_stack/providers/remote/inference/tgi/config.py index a1f4c19f3..8cd0e15b3 100644 --- a/llama_stack/providers/remote/inference/tgi/config.py +++ b/llama_stack/providers/remote/inference/tgi/config.py @@ -5,9 +5,8 @@ # the root directory of this source tree. -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -33,7 +32,7 @@ class InferenceEndpointImplConfig(BaseModel): endpoint_name: str = Field( description="The name of the Hugging Face Inference Endpoint in the format of '{namespace}/{endpoint_name}' (e.g. 'my-cool-org/meta-llama-3-1-8b-instruct-rce'). Namespace is optional and will default to the user account if not provided.", ) - api_token: MySecretStr = Field( + api_token: SecretStr = Field( description="Your Hugging Face user access token (will default to locally saved token if not provided)", ) @@ -55,7 +54,7 @@ class InferenceAPIImplConfig(BaseModel): huggingface_repo: str = Field( description="The model ID of the model on the Hugging Face Hub (e.g. 'meta-llama/Meta-Llama-3.1-70B-Instruct')", ) - api_token: MySecretStr = Field( + api_token: SecretStr = Field( description="Your Hugging Face user access token (will default to locally saved token if not provided)", ) diff --git a/llama_stack/providers/remote/inference/tgi/tgi.py b/llama_stack/providers/remote/inference/tgi/tgi.py index 239fdce4e..27597900f 100644 --- a/llama_stack/providers/remote/inference/tgi/tgi.py +++ b/llama_stack/providers/remote/inference/tgi/tgi.py @@ -8,6 +8,7 @@ from collections.abc import AsyncGenerator from huggingface_hub import AsyncInferenceClient, HfApi +from pydantic import SecretStr from llama_stack.apis.common.content_types import ( InterleavedContent, @@ -34,7 +35,6 @@ from llama_stack.apis.inference import ( ) from llama_stack.apis.models import Model from llama_stack.apis.models.models import ModelType -from llama_stack.core.secret_types import MySecretStr from llama_stack.log import get_logger from llama_stack.models.llama.sku_list import all_registered_models from llama_stack.providers.datatypes import ModelsProtocolPrivate @@ -79,7 +79,7 @@ class _HfAdapter( ModelsProtocolPrivate, ): url: str - api_key: MySecretStr + api_key: SecretStr hf_client: AsyncInferenceClient max_tokens: int @@ -337,7 +337,7 @@ class TGIAdapter(_HfAdapter): self.max_tokens = endpoint_info["max_total_tokens"] self.model_id = endpoint_info["model_id"] self.url = f"{config.url.rstrip('/')}/v1" - self.api_key = MySecretStr("NO_KEY") + self.api_key = SecretStr("NO_KEY") class InferenceAPIAdapter(_HfAdapter): diff --git a/llama_stack/providers/remote/inference/together/config.py b/llama_stack/providers/remote/inference/together/config.py index a2fa8f76e..b39092913 100644 --- a/llama_stack/providers/remote/inference/together/config.py +++ b/llama_stack/providers/remote/inference/together/config.py @@ -6,9 +6,8 @@ from typing import Any -from pydantic import Field +from pydantic import Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.inference.model_registry import RemoteInferenceProviderConfig from llama_stack.schema_utils import json_schema_type @@ -19,7 +18,7 @@ class TogetherImplConfig(RemoteInferenceProviderConfig): default="https://api.together.xyz/v1", description="The URL for the Together AI server", ) - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="The Together AI API Key", ) diff --git a/llama_stack/providers/remote/inference/vertexai/vertexai.py b/llama_stack/providers/remote/inference/vertexai/vertexai.py index dd1971df4..581a00f29 100644 --- a/llama_stack/providers/remote/inference/vertexai/vertexai.py +++ b/llama_stack/providers/remote/inference/vertexai/vertexai.py @@ -8,9 +8,9 @@ from typing import Any import google.auth.transport.requests from google.auth import default +from pydantic import SecretStr from llama_stack.apis.inference import ChatCompletionRequest -from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.inference.litellm_openai_mixin import ( LiteLLMOpenAIMixin, ) @@ -24,12 +24,12 @@ class VertexAIInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): LiteLLMOpenAIMixin.__init__( self, litellm_provider_name="vertex_ai", - api_key_from_config=MySecretStr(None), # Vertex AI uses ADC, not API keys + api_key_from_config=SecretStr(""), # Vertex AI uses ADC, not API keys provider_data_api_key_field="vertex_project", # Use project for validation ) self.config = config - def get_api_key(self) -> MySecretStr: + def get_api_key(self) -> SecretStr: """ Get an access token for Vertex AI using Application Default Credentials. @@ -40,11 +40,11 @@ class VertexAIInferenceAdapter(OpenAIMixin, LiteLLMOpenAIMixin): # Get default credentials - will read from GOOGLE_APPLICATION_CREDENTIALS credentials, _ = default(scopes=["https://www.googleapis.com/auth/cloud-platform"]) credentials.refresh(google.auth.transport.requests.Request()) - return MySecretStr(credentials.token) + return SecretStr(credentials.token) except Exception: # If we can't get credentials, return empty string to let LiteLLM handle it # This allows the LiteLLM mixin to work with ADC directly - return MySecretStr("") + return SecretStr("") def get_base_url(self) -> str: """ diff --git a/llama_stack/providers/remote/inference/vllm/__init__.py b/llama_stack/providers/remote/inference/vllm/__init__.py index e47597c8f..9c29795ca 100644 --- a/llama_stack/providers/remote/inference/vllm/__init__.py +++ b/llama_stack/providers/remote/inference/vllm/__init__.py @@ -4,15 +4,13 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -from pydantic import BaseModel, Field - -from llama_stack.core.secret_types import MySecretStr +from pydantic import BaseModel, Field, SecretStr from .config import VLLMInferenceAdapterConfig class VLLMProviderDataValidator(BaseModel): - vllm_api_token: MySecretStr = Field( + vllm_api_token: SecretStr = Field( description="API token for vLLM models", ) diff --git a/llama_stack/providers/remote/inference/vllm/config.py b/llama_stack/providers/remote/inference/vllm/config.py index 7cc3ebebb..828c87d69 100644 --- a/llama_stack/providers/remote/inference/vllm/config.py +++ b/llama_stack/providers/remote/inference/vllm/config.py @@ -6,9 +6,8 @@ from pathlib import Path -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field, SecretStr, field_validator -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -22,7 +21,8 @@ class VLLMInferenceAdapterConfig(BaseModel): default=4096, description="Maximum number of tokens to generate.", ) - api_token: MySecretStr = Field( + api_token: SecretStr = Field( + default=SecretStr("fake"), description="The API token", ) tls_verify: bool | str = Field( diff --git a/llama_stack/providers/remote/inference/watsonx/config.py b/llama_stack/providers/remote/inference/watsonx/config.py index 64421c856..a28de1226 100644 --- a/llama_stack/providers/remote/inference/watsonx/config.py +++ b/llama_stack/providers/remote/inference/watsonx/config.py @@ -7,9 +7,8 @@ import os from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type @@ -25,8 +24,8 @@ class WatsonXConfig(BaseModel): default_factory=lambda: os.getenv("WATSONX_BASE_URL", "https://us-south.ml.cloud.ibm.com"), description="A base url for accessing the watsonx.ai", ) - api_key: MySecretStr = Field( - default_factory=lambda: MySecretStr(os.getenv("WATSONX_API_KEY", "")), + api_key: SecretStr = Field( + default_factory=lambda: SecretStr(os.getenv("WATSONX_API_KEY", "")), description="The watsonx API key", ) project_id: str | None = Field( diff --git a/llama_stack/providers/remote/post_training/nvidia/config.py b/llama_stack/providers/remote/post_training/nvidia/config.py index 1730b779f..f194ab437 100644 --- a/llama_stack/providers/remote/post_training/nvidia/config.py +++ b/llama_stack/providers/remote/post_training/nvidia/config.py @@ -7,9 +7,7 @@ import os from typing import Any -from pydantic import BaseModel, Field - -from llama_stack.core.secret_types import MySecretStr +from pydantic import BaseModel, Field, SecretStr # TODO: add default values for all fields @@ -17,8 +15,8 @@ from llama_stack.core.secret_types import MySecretStr class NvidiaPostTrainingConfig(BaseModel): """Configuration for NVIDIA Post Training implementation.""" - api_key: MySecretStr = Field( - default_factory=lambda: MySecretStr(os.getenv("NVIDIA_API_KEY", "")), + api_key: SecretStr = Field( + default_factory=lambda: SecretStr(os.getenv("NVIDIA_API_KEY", "")), description="The NVIDIA API key.", ) diff --git a/llama_stack/providers/remote/safety/sambanova/config.py b/llama_stack/providers/remote/safety/sambanova/config.py index ff58decfd..bf474ba81 100644 --- a/llama_stack/providers/remote/safety/sambanova/config.py +++ b/llama_stack/providers/remote/safety/sambanova/config.py @@ -6,14 +6,13 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.schema_utils import json_schema_type class SambaNovaProviderDataValidator(BaseModel): - sambanova_api_key: MySecretStr = Field( + sambanova_api_key: SecretStr = Field( description="Sambanova Cloud API key", ) @@ -24,7 +23,7 @@ class SambaNovaSafetyConfig(BaseModel): default="https://api.sambanova.ai/v1", description="The URL for the SambaNova AI server", ) - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="The SambaNova cloud API Key", ) diff --git a/llama_stack/providers/remote/tool_runtime/bing_search/config.py b/llama_stack/providers/remote/tool_runtime/bing_search/config.py index 54b3a9d52..591e9586c 100644 --- a/llama_stack/providers/remote/tool_runtime/bing_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/bing_search/config.py @@ -6,15 +6,13 @@ from typing import Any -from pydantic import BaseModel, Field - -from llama_stack.core.secret_types import MySecretStr +from pydantic import BaseModel, Field, SecretStr class BingSearchToolConfig(BaseModel): """Configuration for Bing Search Tool Runtime""" - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="The Bing API key", ) top_k: int = 3 diff --git a/llama_stack/providers/remote/tool_runtime/brave_search/config.py b/llama_stack/providers/remote/tool_runtime/brave_search/config.py index 8fbc42154..bc93bc7f6 100644 --- a/llama_stack/providers/remote/tool_runtime/brave_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/brave_search/config.py @@ -6,13 +6,11 @@ from typing import Any -from pydantic import BaseModel, Field - -from llama_stack.core.secret_types import MySecretStr +from pydantic import BaseModel, Field, SecretStr class BraveSearchToolConfig(BaseModel): - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="The Brave Search API Key", ) max_results: int = Field( diff --git a/llama_stack/providers/remote/tool_runtime/tavily_search/config.py b/llama_stack/providers/remote/tool_runtime/tavily_search/config.py index c2e31e5d3..5afd19bfa 100644 --- a/llama_stack/providers/remote/tool_runtime/tavily_search/config.py +++ b/llama_stack/providers/remote/tool_runtime/tavily_search/config.py @@ -6,13 +6,11 @@ from typing import Any -from pydantic import BaseModel, Field - -from llama_stack.core.secret_types import MySecretStr +from pydantic import BaseModel, Field, SecretStr class TavilySearchToolConfig(BaseModel): - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="The Tavily Search API Key", ) max_results: int = Field( diff --git a/llama_stack/providers/remote/tool_runtime/wolfram_alpha/config.py b/llama_stack/providers/remote/tool_runtime/wolfram_alpha/config.py index eea5ffdd2..980b30411 100644 --- a/llama_stack/providers/remote/tool_runtime/wolfram_alpha/config.py +++ b/llama_stack/providers/remote/tool_runtime/wolfram_alpha/config.py @@ -6,15 +6,13 @@ from typing import Any -from pydantic import BaseModel, Field - -from llama_stack.core.secret_types import MySecretStr +from pydantic import BaseModel, Field, SecretStr class WolframAlphaToolConfig(BaseModel): """Configuration for WolframAlpha Tool Runtime""" - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="The WolframAlpha API Key", ) diff --git a/llama_stack/providers/remote/vector_io/milvus/config.py b/llama_stack/providers/remote/vector_io/milvus/config.py index 899d3678d..83daae779 100644 --- a/llama_stack/providers/remote/vector_io/milvus/config.py +++ b/llama_stack/providers/remote/vector_io/milvus/config.py @@ -6,7 +6,7 @@ from typing import Any -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, SecretStr from llama_stack.providers.utils.kvstore.config import KVStoreConfig, SqliteKVStoreConfig from llama_stack.schema_utils import json_schema_type @@ -15,7 +15,7 @@ from llama_stack.schema_utils import json_schema_type @json_schema_type class MilvusVectorIOConfig(BaseModel): uri: str = Field(description="The URI of the Milvus server") - token: str | None = Field(description="The token of the Milvus server") + token: SecretStr = Field(description="The token of the Milvus server") consistency_level: str = Field(description="The consistency level of the Milvus server", default="Strong") kvstore: KVStoreConfig = Field(description="Config for KV store backend") diff --git a/llama_stack/providers/remote/vector_io/pgvector/config.py b/llama_stack/providers/remote/vector_io/pgvector/config.py index ee4ef40d0..1c6d0ed52 100644 --- a/llama_stack/providers/remote/vector_io/pgvector/config.py +++ b/llama_stack/providers/remote/vector_io/pgvector/config.py @@ -6,9 +6,8 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.kvstore.config import ( KVStoreConfig, SqliteKVStoreConfig, @@ -22,7 +21,7 @@ class PGVectorVectorIOConfig(BaseModel): port: int | None = Field(default=5432) db: str | None = Field(default="postgres") user: str | None = Field(default="postgres") - password: MySecretStr = Field(default=MySecretStr("mysecretpassword")) + password: SecretStr = Field(default=SecretStr("mysecretpassword")) kvstore: KVStoreConfig | None = Field(description="Config for KV store backend (SQLite only for now)", default=None) @classmethod diff --git a/llama_stack/providers/remote/vector_io/qdrant/config.py b/llama_stack/providers/remote/vector_io/qdrant/config.py index 58044e248..8a0be3ab4 100644 --- a/llama_stack/providers/remote/vector_io/qdrant/config.py +++ b/llama_stack/providers/remote/vector_io/qdrant/config.py @@ -6,9 +6,8 @@ from typing import Any -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.kvstore.config import ( KVStoreConfig, SqliteKVStoreConfig, @@ -24,7 +23,7 @@ class QdrantVectorIOConfig(BaseModel): grpc_port: int = 6334 prefer_grpc: bool = False https: bool | None = None - api_key: MySecretStr = Field( + api_key: SecretStr = Field( description="The API key for the Qdrant instance", ) prefix: str | None = None diff --git a/llama_stack/providers/remote/vector_io/qdrant/qdrant.py b/llama_stack/providers/remote/vector_io/qdrant/qdrant.py index ec3869495..0ba7c51f8 100644 --- a/llama_stack/providers/remote/vector_io/qdrant/qdrant.py +++ b/llama_stack/providers/remote/vector_io/qdrant/qdrant.py @@ -173,7 +173,7 @@ class QdrantVectorIOAdapter(OpenAIVectorStoreMixin, VectorIO, VectorDBsProtocolP self._qdrant_lock = asyncio.Lock() async def initialize(self) -> None: - client_config = self.config.model_dump(exclude_none=True, exclude={"kvstore"}) + client_config = self.config.model_dump(exclude_none=True, exclude={"kvstore"}, mode="json") self.client = AsyncQdrantClient(**client_config) self.kvstore = await kvstore_impl(self.config.kvstore) diff --git a/llama_stack/providers/utils/bedrock/config.py b/llama_stack/providers/utils/bedrock/config.py index ae4018b80..2a5c8e882 100644 --- a/llama_stack/providers/utils/bedrock/config.py +++ b/llama_stack/providers/utils/bedrock/config.py @@ -6,9 +6,7 @@ import os -from pydantic import BaseModel, Field - -from llama_stack.core.secret_types import MySecretStr +from pydantic import BaseModel, Field, SecretStr class BedrockBaseConfig(BaseModel): @@ -16,12 +14,12 @@ class BedrockBaseConfig(BaseModel): default_factory=lambda: os.getenv("AWS_ACCESS_KEY_ID"), description="The AWS access key to use. Default use environment variable: AWS_ACCESS_KEY_ID", ) - aws_secret_access_key: MySecretStr = Field( - default_factory=lambda: MySecretStr(os.getenv("AWS_SECRET_ACCESS_KEY", "")), + aws_secret_access_key: SecretStr = Field( + default_factory=lambda: SecretStr(os.getenv("AWS_SECRET_ACCESS_KEY", "")), description="The AWS secret access key to use. Default use environment variable: AWS_SECRET_ACCESS_KEY", ) - aws_session_token: MySecretStr = Field( - default_factory=lambda: MySecretStr(os.getenv("AWS_SESSION_TOKEN", "")), + aws_session_token: SecretStr = Field( + default_factory=lambda: SecretStr(os.getenv("AWS_SESSION_TOKEN", "")), description="The AWS session token to use. Default use environment variable: AWS_SESSION_TOKEN", ) region_name: str | None = Field( diff --git a/llama_stack/providers/utils/inference/litellm_openai_mixin.py b/llama_stack/providers/utils/inference/litellm_openai_mixin.py index a5d320e41..8bdfbbbc9 100644 --- a/llama_stack/providers/utils/inference/litellm_openai_mixin.py +++ b/llama_stack/providers/utils/inference/litellm_openai_mixin.py @@ -8,6 +8,7 @@ from collections.abc import AsyncGenerator, AsyncIterator from typing import Any import litellm +from pydantic import SecretStr from llama_stack.apis.common.content_types import ( InterleavedContent, @@ -39,7 +40,6 @@ from llama_stack.apis.inference import ( ToolPromptFormat, ) from llama_stack.core.request_headers import NeedsRequestProviderData -from llama_stack.core.secret_types import MySecretStr from llama_stack.log import get_logger from llama_stack.providers.utils.inference.model_registry import ModelRegistryHelper, ProviderModelEntry from llama_stack.providers.utils.inference.openai_compat import ( @@ -69,7 +69,7 @@ class LiteLLMOpenAIMixin( def __init__( self, litellm_provider_name: str, - api_key_from_config: MySecretStr, + api_key_from_config: SecretStr, provider_data_api_key_field: str, model_entries: list[ProviderModelEntry] | None = None, openai_compat_api_base: str | None = None, @@ -255,7 +255,7 @@ class LiteLLMOpenAIMixin( **get_sampling_options(request.sampling_params), } - def get_api_key(self) -> MySecretStr: + def get_api_key(self) -> SecretStr: provider_data = self.get_request_provider_data() key_field = self.provider_data_api_key_field if provider_data and getattr(provider_data, key_field, None): diff --git a/llama_stack/providers/utils/inference/openai_mixin.py b/llama_stack/providers/utils/inference/openai_mixin.py index 4bb7a28c5..d63f147bc 100644 --- a/llama_stack/providers/utils/inference/openai_mixin.py +++ b/llama_stack/providers/utils/inference/openai_mixin.py @@ -11,6 +11,7 @@ from collections.abc import AsyncIterator from typing import Any from openai import NOT_GIVEN, AsyncOpenAI +from pydantic import SecretStr from llama_stack.apis.inference import ( Model, @@ -24,7 +25,6 @@ from llama_stack.apis.inference import ( OpenAIResponseFormatParam, ) from llama_stack.apis.models import ModelType -from llama_stack.core.secret_types import MySecretStr from llama_stack.log import get_logger from llama_stack.providers.utils.inference.model_registry import ModelRegistryHelper from llama_stack.providers.utils.inference.openai_compat import prepare_openai_completion_params @@ -71,14 +71,14 @@ class OpenAIMixin(ModelRegistryHelper, ABC): allowed_models: list[str] = [] @abstractmethod - def get_api_key(self) -> MySecretStr: + def get_api_key(self) -> SecretStr: """ Get the API key. This method must be implemented by child classes to provide the API key for authenticating with the OpenAI API or compatible endpoints. - :return: The API key as a MySecretStr + :return: The API key as a SecretStr """ pass diff --git a/llama_stack/providers/utils/kvstore/config.py b/llama_stack/providers/utils/kvstore/config.py index a8dd1a99a..baab4e372 100644 --- a/llama_stack/providers/utils/kvstore/config.py +++ b/llama_stack/providers/utils/kvstore/config.py @@ -8,9 +8,8 @@ import re from enum import Enum from typing import Annotated, Literal -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field, SecretStr, field_validator -from llama_stack.core.secret_types import MySecretStr from llama_stack.core.utils.config_dirs import RUNTIME_BASE_DIR @@ -75,7 +74,7 @@ class PostgresKVStoreConfig(CommonConfig): port: int = 5432 db: str = "llamastack" user: str - password: MySecretStr = MySecretStr("") + password: SecretStr = SecretStr("") ssl_mode: str | None = None ca_cert_path: str | None = None table_name: str = "llamastack_kvstore" @@ -119,7 +118,7 @@ class MongoDBKVStoreConfig(CommonConfig): port: int = 27017 db: str = "llamastack" user: str | None = None - password: MySecretStr = MySecretStr("") + password: SecretStr = SecretStr("") collection_name: str = "llamastack_kvstore" @classmethod diff --git a/llama_stack/providers/utils/sqlstore/sqlstore.py b/llama_stack/providers/utils/sqlstore/sqlstore.py index 3bcd8f40d..6eaafccfe 100644 --- a/llama_stack/providers/utils/sqlstore/sqlstore.py +++ b/llama_stack/providers/utils/sqlstore/sqlstore.py @@ -9,9 +9,8 @@ from enum import StrEnum from pathlib import Path from typing import Annotated, Literal -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr -from llama_stack.core.secret_types import MySecretStr from llama_stack.core.utils.config_dirs import RUNTIME_BASE_DIR from .api import SqlStore @@ -64,7 +63,7 @@ class PostgresSqlStoreConfig(SqlAlchemySqlStoreConfig): port: int = 5432 db: str = "llamastack" user: str - password: MySecretStr = MySecretStr("") + password: SecretStr = SecretStr("") @property def engine_str(self) -> str: diff --git a/tests/unit/providers/files/conftest.py b/tests/unit/providers/files/conftest.py index 46282e3dc..696ee7ba7 100644 --- a/tests/unit/providers/files/conftest.py +++ b/tests/unit/providers/files/conftest.py @@ -7,6 +7,7 @@ import boto3 import pytest from moto import mock_aws +from pydantic import SecretStr from llama_stack.providers.remote.files.s3 import S3FilesImplConfig, get_adapter_impl from llama_stack.providers.utils.sqlstore.sqlstore import SqliteSqlStoreConfig @@ -43,6 +44,7 @@ def s3_config(tmp_path): region="not-a-region", auto_create_bucket=True, metadata_store=SqliteSqlStoreConfig(db_path=db_path.as_posix()), + aws_secret_access_key=SecretStr("fake"), ) diff --git a/tests/unit/providers/inference/bedrock/test_config.py b/tests/unit/providers/inference/bedrock/test_config.py index 1b8639f2e..903b6957a 100644 --- a/tests/unit/providers/inference/bedrock/test_config.py +++ b/tests/unit/providers/inference/bedrock/test_config.py @@ -17,7 +17,7 @@ class TestBedrockBaseConfig: # Basic creds should be None assert config.aws_access_key_id is None - assert config.aws_secret_access_key is None + assert not config.aws_secret_access_key assert config.region_name is None # Timeouts get defaults @@ -39,7 +39,7 @@ class TestBedrockBaseConfig: config = BedrockBaseConfig() assert config.aws_access_key_id == "AKIATEST123" - assert config.aws_secret_access_key == "secret123" + assert config.aws_secret_access_key.get_secret_value() == "secret123" assert config.region_name == "us-west-2" assert config.total_max_attempts == 5 assert config.retry_mode == "adaptive" diff --git a/tests/unit/providers/inference/test_inference_client_caching.py b/tests/unit/providers/inference/test_inference_client_caching.py index 974d55ade..5084ad5f3 100644 --- a/tests/unit/providers/inference/test_inference_client_caching.py +++ b/tests/unit/providers/inference/test_inference_client_caching.py @@ -7,6 +7,8 @@ import json from unittest.mock import MagicMock +from pydantic import SecretStr + from llama_stack.core.request_headers import request_provider_data_context from llama_stack.providers.remote.inference.groq.config import GroqConfig from llama_stack.providers.remote.inference.groq.groq import GroqInferenceAdapter @@ -21,7 +23,7 @@ from llama_stack.providers.remote.inference.together.together import TogetherInf def test_groq_provider_openai_client_caching(): """Ensure the Groq provider does not cache api keys across client requests""" - config = GroqConfig() + config = GroqConfig(api_key=SecretStr("")) inference_adapter = GroqInferenceAdapter(config) inference_adapter.__provider_spec__ = MagicMock() @@ -33,13 +35,13 @@ def test_groq_provider_openai_client_caching(): with request_provider_data_context( {"x-llamastack-provider-data": json.dumps({inference_adapter.provider_data_api_key_field: api_key})} ): - assert inference_adapter.client.api_key.get_secret_value() == api_key + assert inference_adapter.client.api_key == api_key def test_openai_provider_openai_client_caching(): """Ensure the OpenAI provider does not cache api keys across client requests""" - config = OpenAIConfig() + config = OpenAIConfig(api_key=SecretStr("")) inference_adapter = OpenAIInferenceAdapter(config) inference_adapter.__provider_spec__ = MagicMock() @@ -52,13 +54,13 @@ def test_openai_provider_openai_client_caching(): {"x-llamastack-provider-data": json.dumps({inference_adapter.provider_data_api_key_field: api_key})} ): openai_client = inference_adapter.client - assert openai_client.api_key.get_secret_value() == api_key + assert openai_client.api_key == api_key def test_together_provider_openai_client_caching(): """Ensure the Together provider does not cache api keys across client requests""" - config = TogetherImplConfig() + config = TogetherImplConfig(api_key=SecretStr("")) inference_adapter = TogetherInferenceAdapter(config) inference_adapter.__provider_spec__ = MagicMock() @@ -76,7 +78,7 @@ def test_together_provider_openai_client_caching(): def test_llama_compat_provider_openai_client_caching(): """Ensure the LlamaCompat provider does not cache api keys across client requests""" - config = LlamaCompatConfig() + config = LlamaCompatConfig(api_key=SecretStr("")) inference_adapter = LlamaCompatInferenceAdapter(config) inference_adapter.__provider_spec__ = MagicMock() @@ -86,4 +88,4 @@ def test_llama_compat_provider_openai_client_caching(): for api_key in ["test1", "test2"]: with request_provider_data_context({"x-llamastack-provider-data": json.dumps({"llama_api_key": api_key})}): - assert inference_adapter.client.api_key.get_secret_value() == api_key + assert inference_adapter.client.api_key == api_key diff --git a/tests/unit/providers/inference/test_litellm_openai_mixin.py b/tests/unit/providers/inference/test_litellm_openai_mixin.py index 48bf5ce38..cf7623dd1 100644 --- a/tests/unit/providers/inference/test_litellm_openai_mixin.py +++ b/tests/unit/providers/inference/test_litellm_openai_mixin.py @@ -8,20 +8,19 @@ import json from unittest.mock import MagicMock import pytest -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, SecretStr from llama_stack.core.request_headers import request_provider_data_context -from llama_stack.core.secret_types import MySecretStr from llama_stack.providers.utils.inference.litellm_openai_mixin import LiteLLMOpenAIMixin # Test fixtures and helper classes class TestConfig(BaseModel): - api_key: MySecretStr | None = Field(default=None) + api_key: SecretStr | None = Field(default=None) class TestProviderDataValidator(BaseModel): - test_api_key: MySecretStr | None = Field(default=None) + test_api_key: SecretStr | None = Field(default=None) class TestLiteLLMAdapter(LiteLLMOpenAIMixin): @@ -37,7 +36,7 @@ class TestLiteLLMAdapter(LiteLLMOpenAIMixin): @pytest.fixture def adapter_with_config_key(): """Fixture to create adapter with API key in config""" - config = TestConfig(api_key=MySecretStr("config-api-key")) + config = TestConfig(api_key=SecretStr("config-api-key")) adapter = TestLiteLLMAdapter(config) adapter.__provider_spec__ = MagicMock() adapter.__provider_spec__.provider_data_validator = ( diff --git a/tests/unit/providers/inference/test_openai_base_url_config.py b/tests/unit/providers/inference/test_openai_base_url_config.py index 40c789073..e7b5a5267 100644 --- a/tests/unit/providers/inference/test_openai_base_url_config.py +++ b/tests/unit/providers/inference/test_openai_base_url_config.py @@ -7,14 +7,9 @@ import os from unittest.mock import MagicMock, patch -from llama_stack.core.secret_types import MySecretStr - - -# Wrapper for backward compatibility in tests -def replace_env_vars_compat(config, path=""): - return replace_env_vars_compat(config, path, None, None) - +from pydantic import SecretStr +from llama_stack.core.stack import replace_env_vars from llama_stack.providers.remote.inference.openai.config import OpenAIConfig from llama_stack.providers.remote.inference.openai.openai import OpenAIInferenceAdapter @@ -42,7 +37,7 @@ class TestOpenAIBaseURLConfig: """Test that the adapter uses base URL from OPENAI_BASE_URL environment variable.""" # Use sample_run_config which has proper environment variable syntax config_data = OpenAIConfig.sample_run_config(api_key="test-key") - processed_config = replace_env_vars_compat(config_data) + processed_config = replace_env_vars(config_data) config = OpenAIConfig.model_validate(processed_config) adapter = OpenAIInferenceAdapter(config) @@ -66,14 +61,14 @@ class TestOpenAIBaseURLConfig: adapter = OpenAIInferenceAdapter(config) # Mock the get_api_key method since it's delegated to LiteLLMOpenAIMixin - adapter.get_api_key = MagicMock(return_value=MySecretStr("test-key")) + adapter.get_api_key = MagicMock(return_value=SecretStr("test-key")) # Access the client property to trigger AsyncOpenAI initialization _ = adapter.client # Verify AsyncOpenAI was called with the correct base_url mock_openai_class.assert_called_once_with( - api_key=MySecretStr("test-key"), + api_key=SecretStr("test-key").get_secret_value(), base_url=custom_url, ) @@ -85,7 +80,7 @@ class TestOpenAIBaseURLConfig: adapter = OpenAIInferenceAdapter(config) # Mock the get_api_key method - adapter.get_api_key = MagicMock(return_value=MySecretStr("test-key")) + adapter.get_api_key = MagicMock(return_value=SecretStr("test-key")) # Mock a model object that will be returned by models.list() mock_model = MagicMock() @@ -108,7 +103,7 @@ class TestOpenAIBaseURLConfig: # Verify the client was created with the custom URL mock_openai_class.assert_called_with( - api_key=MySecretStr("test-key"), + api_key=SecretStr("test-key").get_secret_value(), base_url=custom_url, ) @@ -121,12 +116,12 @@ class TestOpenAIBaseURLConfig: """Test that setting OPENAI_BASE_URL environment variable affects where model availability is checked.""" # Use sample_run_config which has proper environment variable syntax config_data = OpenAIConfig.sample_run_config(api_key="test-key") - processed_config = replace_env_vars_compat(config_data) + processed_config = replace_env_vars(config_data) config = OpenAIConfig.model_validate(processed_config) adapter = OpenAIInferenceAdapter(config) # Mock the get_api_key method - adapter.get_api_key = MagicMock(return_value=MySecretStr("test-key")) + adapter.get_api_key = MagicMock(return_value=SecretStr("test-key")) # Mock a model object that will be returned by models.list() mock_model = MagicMock() @@ -149,6 +144,6 @@ class TestOpenAIBaseURLConfig: # Verify the client was created with the environment variable URL mock_openai_class.assert_called_with( - api_key=MySecretStr("test-key"), + api_key=SecretStr("test-key").get_secret_value(), base_url="https://proxy.openai.com/v1", ) diff --git a/tests/unit/providers/inference/test_remote_vllm.py b/tests/unit/providers/inference/test_remote_vllm.py index 4dc2e0c16..52a771fae 100644 --- a/tests/unit/providers/inference/test_remote_vllm.py +++ b/tests/unit/providers/inference/test_remote_vllm.py @@ -26,6 +26,7 @@ from openai.types.chat.chat_completion_chunk import ( ChoiceDeltaToolCallFunction as OpenAIChoiceDeltaToolCallFunction, ) from openai.types.model import Model as OpenAIModel +from pydantic import SecretStr from llama_stack.apis.inference import ( ChatCompletionRequest, @@ -688,31 +689,35 @@ async def test_should_refresh_models(): """ # Test case 1: refresh_models is True, api_token is None - config1 = VLLMInferenceAdapterConfig(url="http://test.localhost", api_token=None, refresh_models=True) + config1 = VLLMInferenceAdapterConfig(url="http://test.localhost", api_token=SecretStr(""), refresh_models=True) adapter1 = VLLMInferenceAdapter(config1) result1 = await adapter1.should_refresh_models() assert result1 is True, "should_refresh_models should return True when refresh_models is True" # Test case 2: refresh_models is True, api_token is empty string - config2 = VLLMInferenceAdapterConfig(url="http://test.localhost", api_token="", refresh_models=True) + config2 = VLLMInferenceAdapterConfig(url="http://test.localhost", api_token=SecretStr(""), refresh_models=True) adapter2 = VLLMInferenceAdapter(config2) result2 = await adapter2.should_refresh_models() assert result2 is True, "should_refresh_models should return True when refresh_models is True" # Test case 3: refresh_models is True, api_token is "fake" (default) - config3 = VLLMInferenceAdapterConfig(url="http://test.localhost", api_token="fake", refresh_models=True) + config3 = VLLMInferenceAdapterConfig(url="http://test.localhost", api_token=SecretStr("fake"), refresh_models=True) adapter3 = VLLMInferenceAdapter(config3) result3 = await adapter3.should_refresh_models() assert result3 is True, "should_refresh_models should return True when refresh_models is True" # Test case 4: refresh_models is True, api_token is real token - config4 = VLLMInferenceAdapterConfig(url="http://test.localhost", api_token="real-token-123", refresh_models=True) + config4 = VLLMInferenceAdapterConfig( + url="http://test.localhost", api_token=SecretStr("real-token-123"), refresh_models=True + ) adapter4 = VLLMInferenceAdapter(config4) result4 = await adapter4.should_refresh_models() assert result4 is True, "should_refresh_models should return True when refresh_models is True" # Test case 5: refresh_models is False, api_token is real token - config5 = VLLMInferenceAdapterConfig(url="http://test.localhost", api_token="real-token-456", refresh_models=False) + config5 = VLLMInferenceAdapterConfig( + url="http://test.localhost", api_token=SecretStr("real-token-456"), refresh_models=False + ) adapter5 = VLLMInferenceAdapter(config5) result5 = await adapter5.should_refresh_models() assert result5 is False, "should_refresh_models should return False when refresh_models is False" @@ -735,7 +740,7 @@ async def test_provider_data_var_context_propagation(vllm_inference_adapter): # Mock provider data to return test data mock_provider_data = MagicMock() - mock_provider_data.vllm_api_token = "test-token-123" + mock_provider_data.vllm_api_token = SecretStr("test-token-123") mock_provider_data.vllm_url = "http://test-server:8000/v1" mock_get_provider_data.return_value = mock_provider_data diff --git a/tests/unit/providers/nvidia/test_parameters.py b/tests/unit/providers/nvidia/test_parameters.py index ad381da26..d58809211 100644 --- a/tests/unit/providers/nvidia/test_parameters.py +++ b/tests/unit/providers/nvidia/test_parameters.py @@ -9,6 +9,7 @@ import warnings from unittest.mock import patch import pytest +from pydantic import SecretStr from llama_stack.apis.post_training.post_training import ( DataConfig, @@ -32,7 +33,7 @@ class TestNvidiaParameters: """Setup and teardown for each test method.""" os.environ["NVIDIA_CUSTOMIZER_URL"] = "http://nemo.test" - config = NvidiaPostTrainingConfig(customizer_url=os.environ["NVIDIA_CUSTOMIZER_URL"], api_key=None) + config = NvidiaPostTrainingConfig(customizer_url=os.environ["NVIDIA_CUSTOMIZER_URL"], api_key=SecretStr("")) self.adapter = NvidiaPostTrainingAdapter(config) self.make_request_patcher = patch( diff --git a/tests/unit/providers/nvidia/test_supervised_fine_tuning.py b/tests/unit/providers/nvidia/test_supervised_fine_tuning.py index 91148605d..0f756c7d4 100644 --- a/tests/unit/providers/nvidia/test_supervised_fine_tuning.py +++ b/tests/unit/providers/nvidia/test_supervised_fine_tuning.py @@ -9,6 +9,7 @@ import warnings from unittest.mock import patch import pytest +from pydantic import SecretStr from llama_stack.apis.post_training.post_training import ( DataConfig, @@ -34,7 +35,7 @@ def nvidia_post_training_adapter(): """Fixture to create and configure the NVIDIA post training adapter.""" os.environ["NVIDIA_CUSTOMIZER_URL"] = "http://nemo.test" # needed for nemo customizer - config = NvidiaPostTrainingConfig(customizer_url=os.environ["NVIDIA_CUSTOMIZER_URL"], api_key=None) + config = NvidiaPostTrainingConfig(customizer_url=os.environ["NVIDIA_CUSTOMIZER_URL"], api_key=SecretStr("")) adapter = NvidiaPostTrainingAdapter(config) with patch.object(adapter, "_make_request") as mock_make_request: diff --git a/tests/unit/server/test_replace_env_vars.py b/tests/unit/server/test_replace_env_vars.py index 0c9bec860..14b3b7231 100644 --- a/tests/unit/server/test_replace_env_vars.py +++ b/tests/unit/server/test_replace_env_vars.py @@ -8,10 +8,7 @@ import os import pytest - -# Wrapper for backward compatibility in tests -def replace_env_vars_compat(config, path=""): - return replace_env_vars_compat(config, path, None, None) +from llama_stack.core.stack import replace_env_vars @pytest.fixture @@ -35,54 +32,52 @@ def setup_env_vars(): def test_simple_replacement(setup_env_vars): - assert replace_env_vars_compat("${env.TEST_VAR}") == "test_value" + assert replace_env_vars("${env.TEST_VAR}") == "test_value" def test_default_value_when_not_set(setup_env_vars): - assert replace_env_vars_compat("${env.NOT_SET:=default}") == "default" + assert replace_env_vars("${env.NOT_SET:=default}") == "default" def test_default_value_when_set(setup_env_vars): - assert replace_env_vars_compat("${env.TEST_VAR:=default}") == "test_value" + assert replace_env_vars("${env.TEST_VAR:=default}") == "test_value" def test_default_value_when_empty(setup_env_vars): - assert replace_env_vars_compat("${env.EMPTY_VAR:=default}") == "default" + assert replace_env_vars("${env.EMPTY_VAR:=default}") == "default" def test_none_value_when_empty(setup_env_vars): - assert replace_env_vars_compat("${env.EMPTY_VAR:=}") is None + assert replace_env_vars("${env.EMPTY_VAR:=}") is None def test_value_when_set(setup_env_vars): - assert replace_env_vars_compat("${env.TEST_VAR:=}") == "test_value" + assert replace_env_vars("${env.TEST_VAR:=}") == "test_value" def test_empty_var_no_default(setup_env_vars): - assert replace_env_vars_compat("${env.EMPTY_VAR_NO_DEFAULT:+}") is None + assert replace_env_vars("${env.EMPTY_VAR_NO_DEFAULT:+}") is None def test_conditional_value_when_set(setup_env_vars): - assert replace_env_vars_compat("${env.TEST_VAR:+conditional}") == "conditional" + assert replace_env_vars("${env.TEST_VAR:+conditional}") == "conditional" def test_conditional_value_when_not_set(setup_env_vars): - assert replace_env_vars_compat("${env.NOT_SET:+conditional}") is None + assert replace_env_vars("${env.NOT_SET:+conditional}") is None def test_conditional_value_when_empty(setup_env_vars): - assert replace_env_vars_compat("${env.EMPTY_VAR:+conditional}") is None + assert replace_env_vars("${env.EMPTY_VAR:+conditional}") is None def test_conditional_value_with_zero(setup_env_vars): - assert replace_env_vars_compat("${env.ZERO_VAR:+conditional}") == "conditional" + assert replace_env_vars("${env.ZERO_VAR:+conditional}") == "conditional" def test_mixed_syntax(setup_env_vars): - assert replace_env_vars_compat("${env.TEST_VAR:=default} and ${env.NOT_SET:+conditional}") == "test_value and " - assert ( - replace_env_vars_compat("${env.NOT_SET:=default} and ${env.TEST_VAR:+conditional}") == "default and conditional" - ) + assert replace_env_vars("${env.TEST_VAR:=default} and ${env.NOT_SET:+conditional}") == "test_value and " + assert replace_env_vars("${env.NOT_SET:=default} and ${env.TEST_VAR:+conditional}") == "default and conditional" def test_nested_structures(setup_env_vars): @@ -92,11 +87,11 @@ def test_nested_structures(setup_env_vars): "key3": {"nested": "${env.NOT_SET:+conditional}"}, } expected = {"key1": "test_value", "key2": ["default", "conditional"], "key3": {"nested": None}} - assert replace_env_vars_compat(data) == expected + assert replace_env_vars(data) == expected def test_explicit_strings_preserved(setup_env_vars): # Explicit strings that look like numbers/booleans should remain strings data = {"port": "8080", "enabled": "true", "count": "123", "ratio": "3.14"} expected = {"port": "8080", "enabled": "true", "count": "123", "ratio": "3.14"} - assert replace_env_vars_compat(data) == expected + assert replace_env_vars(data) == expected