diff --git a/litellm/llms/openai_like/dynamic_config.py b/litellm/llms/openai_like/dynamic_config.py index d7aafe9f06e..efe4bd5340d 100644 --- a/litellm/llms/openai_like/dynamic_config.py +++ b/litellm/llms/openai_like/dynamic_config.py @@ -79,6 +79,17 @@ def create_config_class(provider: SimpleProviderConfig): # Resolve API key resolved_key: Final = api_key or get_secret_str(provider.api_key_env) + if provider.special_handling.get("require_api_key") and not resolved_key: + # Fail closed: without this the caller's OPENAI_API_KEY would be used as a fallback + # further down the dispatch path and sent to this provider. + from litellm.exceptions import AuthenticationError + + raise AuthenticationError( + message=f"{provider.slug}: no API key. Pass api_key or set {provider.api_key_env}.", + llm_provider=provider.slug, + model="", + ) + return resolved_base, resolved_key def get_openai_compatible_provider_info( diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index 3f978c4fc6c..83bb5cbe30a 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -308,6 +308,9 @@ "api_key_env": "SAGG_API_KEY", "param_mappings": { "max_completion_tokens": "max_tokens" + }, + "special_handling": { + "require_api_key": true } } } diff --git a/tests/unit/llms/openai_like/test_sagg_provider.py b/tests/unit/llms/openai_like/test_sagg_provider.py index da76601da56..ddbae47cc58 100644 --- a/tests/unit/llms/openai_like/test_sagg_provider.py +++ b/tests/unit/llms/openai_like/test_sagg_provider.py @@ -38,7 +38,7 @@ class TestSaggProviderConfig: model="sagg/deepseek-ai/DeepSeek-V4-Flash-0731", custom_llm_provider=None, api_base=None, - api_key=None, + api_key="test-key", ) assert model == "deepseek-ai/DeepSeek-V4-Flash-0731" @@ -84,3 +84,25 @@ class TestSaggProviderConfig: assert result["max_tokens"] == 100 assert "max_completion_tokens" not in result assert result["temperature"] == 0.7 + + +class TestSaggRequiresItsOwnKey: + def _config(self): + from litellm.llms.openai_like.dynamic_config import create_config_class + from litellm.llms.openai_like.json_loader import JSONProviderRegistry + + return create_config_class(JSONProviderRegistry.get("sagg"))() + + def test_missing_key_fails_closed_even_with_openai_key_set(self, monkeypatch): + import pytest + + monkeypatch.delenv("SAGG_API_KEY", raising=False) + monkeypatch.setenv("OPENAI_API_KEY", "sk-not-for-sagg") + with pytest.raises(litellm.AuthenticationError): + self._config()._get_openai_compatible_provider_info(None, None) + + def test_explicit_key_and_env_key_still_work(self, monkeypatch): + monkeypatch.delenv("SAGG_API_KEY", raising=False) + assert self._config()._get_openai_compatible_provider_info(None, "k1")[1] == "k1" + monkeypatch.setenv("SAGG_API_KEY", "k2") + assert self._config()._get_openai_compatible_provider_info(None, None)[1] == "k2"