mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
fix(litellm_proxy): add fake-api-key fallback when LITELLM_PROXY_API_KEY is not set
This commit is contained in:
parent
b8cef1a4e5
commit
cdc76f8b50
8 changed files with 245 additions and 8 deletions
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Translate from OpenAI's `/v1/chat/completions` to VLLM's `/v1/chat/completions`
|
||||
Translate from OpenAI's `/v1/chat/completions` to LiteLLM Proxy's `/v1/chat/completions`
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple
|
||||
|
|
@ -39,7 +39,9 @@ class LiteLLMProxyChatConfig(OpenAIGPTConfig):
|
|||
self, api_base: Optional[str], api_key: Optional[str]
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
api_base = api_base or get_secret_str("LITELLM_PROXY_API_BASE") # type: ignore
|
||||
dynamic_api_key = api_key or get_secret_str("LITELLM_PROXY_API_KEY")
|
||||
dynamic_api_key = (
|
||||
api_key or get_secret_str("LITELLM_PROXY_API_KEY") or "fake-api-key"
|
||||
) # litellm_proxy does not require an api key, but OpenAI client requires non-None value
|
||||
return api_base, dynamic_api_key
|
||||
|
||||
def get_models(
|
||||
|
|
@ -55,7 +57,9 @@ class LiteLLMProxyChatConfig(OpenAIGPTConfig):
|
|||
|
||||
@staticmethod
|
||||
def get_api_key(api_key: Optional[str] = None) -> Optional[str]:
|
||||
return api_key or get_secret_str("LITELLM_PROXY_API_KEY")
|
||||
return (
|
||||
api_key or get_secret_str("LITELLM_PROXY_API_KEY") or "fake-api-key"
|
||||
) # litellm_proxy does not require an api key
|
||||
|
||||
@staticmethod
|
||||
def _should_use_litellm_proxy_by_default(
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ class LiteLLMProxyImageEditConfig(OpenAIImageEditConfig):
|
|||
def validate_environment(
|
||||
self, headers: dict, model: str, api_key: Optional[str] = None
|
||||
) -> dict:
|
||||
api_key = api_key or get_secret_str("LITELLM_PROXY_API_KEY")
|
||||
api_key = (
|
||||
api_key or get_secret_str("LITELLM_PROXY_API_KEY") or "fake-api-key"
|
||||
) # litellm_proxy does not require an api key
|
||||
headers.update({"Authorization": f"Bearer {api_key}"})
|
||||
return headers
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from litellm.secret_managers.main import get_secret_str
|
|||
|
||||
class LiteLLMProxyImageGenerationConfig(GPTImageGenerationConfig):
|
||||
"""Configuration for image generation requests routed through LiteLLM Proxy."""
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
|
|
@ -18,7 +19,9 @@ class LiteLLMProxyImageGenerationConfig(GPTImageGenerationConfig):
|
|||
api_key: Optional[str] = None,
|
||||
api_base: Optional[str] = None,
|
||||
) -> dict:
|
||||
api_key = api_key or get_secret_str("LITELLM_PROXY_API_KEY")
|
||||
api_key = (
|
||||
api_key or get_secret_str("LITELLM_PROXY_API_KEY") or "fake-api-key"
|
||||
) # litellm_proxy does not require an api key
|
||||
headers.update({"Authorization": f"Bearer {api_key}"})
|
||||
return headers
|
||||
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ from typing import Optional
|
|||
|
||||
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.types.utils import LlmProviders
|
||||
|
||||
|
||||
class LiteLLMProxyResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
||||
"""
|
||||
Configuration for LiteLLM Proxy Responses API support.
|
||||
|
||||
|
||||
Extends OpenAI's config since the proxy follows OpenAI's API spec,
|
||||
but uses LITELLM_PROXY_API_BASE for the base URL.
|
||||
"""
|
||||
|
|
@ -24,6 +25,21 @@ class LiteLLMProxyResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
|||
def custom_llm_provider(self) -> LlmProviders:
|
||||
return LlmProviders.LITELLM_PROXY
|
||||
|
||||
def validate_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
model: str,
|
||||
litellm_params: Optional[GenericLiteLLMParams],
|
||||
) -> dict:
|
||||
litellm_params = litellm_params or GenericLiteLLMParams()
|
||||
api_key = (
|
||||
litellm_params.api_key
|
||||
or get_secret_str("LITELLM_PROXY_API_KEY")
|
||||
or "fake-api-key" # litellm_proxy does not require an api key
|
||||
)
|
||||
headers.update({"Authorization": f"Bearer {api_key}"})
|
||||
return headers
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: Optional[str],
|
||||
|
|
@ -31,11 +47,11 @@ class LiteLLMProxyResponsesAPIConfig(OpenAIResponsesAPIConfig):
|
|||
) -> str:
|
||||
"""
|
||||
Get the endpoint for LiteLLM Proxy responses API.
|
||||
|
||||
|
||||
Uses LITELLM_PROXY_API_BASE environment variable if api_base is not provided.
|
||||
"""
|
||||
api_base = api_base or get_secret_str("LITELLM_PROXY_API_BASE")
|
||||
|
||||
|
||||
if api_base is None:
|
||||
raise ValueError(
|
||||
"api_base not set for LiteLLM Proxy responses API. "
|
||||
|
|
|
|||
|
|
@ -40,3 +40,101 @@ def test_litellm_gateway_from_sdk_with_user_param():
|
|||
)
|
||||
print(f"supported_params: {supported_params}")
|
||||
assert "user" in supported_params
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input_api_key, env_api_key, expected_api_key",
|
||||
[
|
||||
("user-provided-key", "secret-key", "user-provided-key"),
|
||||
(None, "secret-key", "secret-key"),
|
||||
(None, None, "fake-api-key"),
|
||||
("", "secret-key", "secret-key"),
|
||||
("", None, "fake-api-key"),
|
||||
],
|
||||
)
|
||||
def test_get_openai_compatible_provider_info_api_key(
|
||||
input_api_key, env_api_key, expected_api_key
|
||||
):
|
||||
config = LiteLLMProxyChatConfig()
|
||||
env = {}
|
||||
if env_api_key is not None:
|
||||
env["LITELLM_PROXY_API_KEY"] = env_api_key
|
||||
|
||||
with patch.dict("os.environ", env, clear=True):
|
||||
_, result_key = config._get_openai_compatible_provider_info(
|
||||
api_base=None, api_key=input_api_key
|
||||
)
|
||||
assert result_key == expected_api_key
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input_api_key, env_api_key, expected_api_key",
|
||||
[
|
||||
("user-provided-key", "secret-key", "user-provided-key"),
|
||||
(None, "secret-key", "secret-key"),
|
||||
(None, None, "fake-api-key"),
|
||||
("", "secret-key", "secret-key"),
|
||||
("", None, "fake-api-key"),
|
||||
],
|
||||
)
|
||||
def test_get_api_key(input_api_key, env_api_key, expected_api_key):
|
||||
env = {}
|
||||
if env_api_key is not None:
|
||||
env["LITELLM_PROXY_API_KEY"] = env_api_key
|
||||
|
||||
with patch.dict("os.environ", env, clear=True):
|
||||
result = LiteLLMProxyChatConfig.get_api_key(input_api_key)
|
||||
assert result == expected_api_key
|
||||
|
||||
|
||||
def test_completion_with_litellm_proxy_no_api_key():
|
||||
"""
|
||||
E2E mock test: USE_LITELLM_PROXY=true with no LITELLM_PROXY_API_KEY
|
||||
should use "fake-api-key" as fallback.
|
||||
"""
|
||||
with patch(
|
||||
"litellm.main.openai_chat_completions.completion"
|
||||
) as mock_completion_func:
|
||||
mock_completion_func.return_value = {}
|
||||
|
||||
env = {
|
||||
"USE_LITELLM_PROXY": "true",
|
||||
"LITELLM_PROXY_API_BASE": "http://localhost:4000",
|
||||
}
|
||||
with patch.dict("os.environ", env, clear=True):
|
||||
_ = litellm.completion(
|
||||
model="openai/gpt-4o",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
)
|
||||
|
||||
mock_completion_func.assert_called_once()
|
||||
_, call_kwargs = mock_completion_func.call_args
|
||||
assert call_kwargs.get("api_key") == "fake-api-key"
|
||||
assert call_kwargs.get("custom_llm_provider") == "litellm_proxy"
|
||||
|
||||
|
||||
def test_completion_with_litellm_proxy_does_not_use_openai_key():
|
||||
"""
|
||||
OPENAI_API_KEY should NOT be sent to litellm_proxy.
|
||||
Even when OPENAI_API_KEY is in the environment, the proxy should use
|
||||
"fake-api-key" (truthy value stops the or-chain in main.py:2366-2371).
|
||||
"""
|
||||
with patch(
|
||||
"litellm.main.openai_chat_completions.completion"
|
||||
) as mock_completion_func:
|
||||
mock_completion_func.return_value = {}
|
||||
|
||||
env = {
|
||||
"USE_LITELLM_PROXY": "true",
|
||||
"LITELLM_PROXY_API_BASE": "http://localhost:4000",
|
||||
"OPENAI_API_KEY": "sk-real-openai-key",
|
||||
}
|
||||
with patch.dict("os.environ", env, clear=True):
|
||||
_ = litellm.completion(
|
||||
model="openai/gpt-4o",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
)
|
||||
|
||||
_, call_kwargs = mock_completion_func.call_args
|
||||
assert call_kwargs.get("api_key") == "fake-api-key"
|
||||
assert call_kwargs.get("api_key") != "sk-real-openai-key"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.llms.litellm_proxy.image_edit.transformation import (
|
||||
LiteLLMProxyImageEditConfig,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input_api_key, env_api_key, expected_bearer",
|
||||
[
|
||||
("user-provided-key", "secret-key", "Bearer user-provided-key"),
|
||||
(None, "secret-key", "Bearer secret-key"),
|
||||
(None, None, "Bearer fake-api-key"),
|
||||
("", "secret-key", "Bearer secret-key"),
|
||||
("", None, "Bearer fake-api-key"),
|
||||
],
|
||||
)
|
||||
def test_validate_environment(input_api_key, env_api_key, expected_bearer):
|
||||
config = LiteLLMProxyImageEditConfig()
|
||||
env = {}
|
||||
if env_api_key is not None:
|
||||
env["LITELLM_PROXY_API_KEY"] = env_api_key
|
||||
|
||||
with patch.dict("os.environ", env, clear=True):
|
||||
headers = config.validate_environment(
|
||||
headers={}, model="dall-e-3", api_key=input_api_key
|
||||
)
|
||||
assert headers.get("Authorization") == expected_bearer
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.llms.litellm_proxy.image_generation.transformation import (
|
||||
LiteLLMProxyImageGenerationConfig,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input_api_key, env_api_key, expected_bearer",
|
||||
[
|
||||
("user-provided-key", "secret-key", "Bearer user-provided-key"),
|
||||
(None, "secret-key", "Bearer secret-key"),
|
||||
(None, None, "Bearer fake-api-key"),
|
||||
("", "secret-key", "Bearer secret-key"),
|
||||
("", None, "Bearer fake-api-key"),
|
||||
],
|
||||
)
|
||||
def test_validate_environment(input_api_key, env_api_key, expected_bearer):
|
||||
config = LiteLLMProxyImageGenerationConfig()
|
||||
env = {}
|
||||
if env_api_key is not None:
|
||||
env["LITELLM_PROXY_API_KEY"] = env_api_key
|
||||
|
||||
with patch.dict("os.environ", env, clear=True):
|
||||
headers = config.validate_environment(
|
||||
headers={},
|
||||
model="dall-e-3",
|
||||
messages=[],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=input_api_key,
|
||||
)
|
||||
assert headers.get("Authorization") == expected_bearer
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.llms.litellm_proxy.responses.transformation import (
|
||||
LiteLLMProxyResponsesAPIConfig,
|
||||
)
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"litellm_params_api_key, env_api_key, expected_bearer",
|
||||
[
|
||||
("user-provided-key", "secret-key", "Bearer user-provided-key"),
|
||||
(None, "secret-key", "Bearer secret-key"),
|
||||
(None, None, "Bearer fake-api-key"),
|
||||
("", "secret-key", "Bearer secret-key"),
|
||||
("", None, "Bearer fake-api-key"),
|
||||
],
|
||||
)
|
||||
def test_validate_environment(litellm_params_api_key, env_api_key, expected_bearer):
|
||||
config = LiteLLMProxyResponsesAPIConfig()
|
||||
env = {}
|
||||
if env_api_key is not None:
|
||||
env["LITELLM_PROXY_API_KEY"] = env_api_key
|
||||
|
||||
litellm_params = GenericLiteLLMParams(api_key=litellm_params_api_key)
|
||||
|
||||
with patch.dict("os.environ", env, clear=True):
|
||||
headers = config.validate_environment(
|
||||
headers={}, model="gpt-4o", litellm_params=litellm_params
|
||||
)
|
||||
assert headers.get("Authorization") == expected_bearer
|
||||
|
||||
|
||||
def test_validate_environment_does_not_use_openai_key():
|
||||
"""
|
||||
OPENAI_API_KEY should NOT be used for litellm_proxy requests.
|
||||
The proxy should use LITELLM_PROXY_API_KEY or fall back to fake-api-key.
|
||||
"""
|
||||
config = LiteLLMProxyResponsesAPIConfig()
|
||||
env = {"OPENAI_API_KEY": "sk-real-openai-key"}
|
||||
litellm_params = GenericLiteLLMParams()
|
||||
|
||||
with patch.dict("os.environ", env, clear=True):
|
||||
headers = config.validate_environment(
|
||||
headers={}, model="gpt-4o", litellm_params=litellm_params
|
||||
)
|
||||
assert headers.get("Authorization") == "Bearer fake-api-key"
|
||||
Loading…
Add table
Reference in a new issue