diff --git a/src/valence/core/config.py b/src/valence/core/config.py index 0f49a76a..094eb88f 100644 --- a/src/valence/core/config.py +++ b/src/valence/core/config.py @@ -17,7 +17,7 @@ from __future__ import annotations -from pydantic import Field +from pydantic import Field, model_validator from pydantic_settings import BaseSettings, SettingsConfigDict @@ -229,6 +229,24 @@ class CoreSettings(BaseSettings): # These are checked via os.environ in health.py # ========================================================================== + # VALIDATORS + # ========================================================================== + + @model_validator(mode="after") + def _auto_select_embedding_provider(self) -> CoreSettings: + """Auto-select openai embedding provider when API key is available.""" + # Only auto-select if no explicit provider was set (still default "local") + # and an OpenAI API key is available + if self.embedding_provider == "local" and self.openai_api_key: + import os + + # Check if VALENCE_EMBEDDING_PROVIDER was explicitly set + if not os.environ.get("VALENCE_EMBEDDING_PROVIDER"): + self.embedding_provider = "openai" + return self + + # ========================================================================== + # COMPUTED PROPERTIES # ========================================================================== diff --git a/tests/core/test_config.py b/tests/core/test_config.py index 7b5bb862..99867daa 100644 --- a/tests/core/test_config.py +++ b/tests/core/test_config.py @@ -383,3 +383,39 @@ def test_int_coercion_seed_port(self, monkeypatch, clean_env): settings = CoreSettings() assert settings.seed_port == 9000 assert isinstance(settings.seed_port, int) + + +# ============================================================================ +# Embedding Provider Auto-Detection (#72) +# ============================================================================ + + +class TestEmbeddingProviderAutoDetect: + """Test auto-detection of embedding provider based on available API keys.""" + + def test_autoselects_openai_when_key_available(self, monkeypatch, clean_env): + """When OPENAI_API_KEY is set and VALENCE_EMBEDDING_PROVIDER is NOT set, + provider should auto-select to 'openai'.""" + monkeypatch.setenv("OPENAI_API_KEY", "sk-test-autodetect-key") + monkeypatch.delenv("VALENCE_EMBEDDING_PROVIDER", raising=False) + + settings = CoreSettings() + assert settings.embedding_provider == "openai" + + def test_explicit_local_override_respected(self, monkeypatch, clean_env): + """When VALENCE_EMBEDDING_PROVIDER is explicitly set to 'local', + it should stay 'local' even if OPENAI_API_KEY is set (user override).""" + monkeypatch.setenv("OPENAI_API_KEY", "sk-test-explicit-local") + monkeypatch.setenv("VALENCE_EMBEDDING_PROVIDER", "local") + + settings = CoreSettings() + assert settings.embedding_provider == "local" + + def test_stays_local_when_no_key(self, monkeypatch, clean_env): + """When neither OPENAI_API_KEY nor VALENCE_EMBEDDING_PROVIDER is set, + provider should remain the default 'local'.""" + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + monkeypatch.delenv("VALENCE_EMBEDDING_PROVIDER", raising=False) + + settings = CoreSettings() + assert settings.embedding_provider == "local"