mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-25 05:54:53 +00:00
refactor(gradium): take api_key and api_base from the model only
The API key and base are set by whoever creates the model, like any other deployment credential. Drop the GRADIUM_API_KEY / GRADIUM_API_BASE environment fallbacks, the litellm.api_key fallback (which could send an unrelated provider's global key to Gradium) and the hardcoded default base URL; a missing value now raises a clear error. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e89422693c
commit
264651bc36
2 changed files with 41 additions and 19 deletions
|
|
@ -18,7 +18,6 @@ from litellm.llms.base_llm.text_to_speech.transformation import (
|
|||
BaseTextToSpeechConfig,
|
||||
TextToSpeechRequestData,
|
||||
)
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
||||
from ..common_utils import GradiumException
|
||||
|
||||
|
|
@ -37,7 +36,6 @@ class GradiumTextToSpeechConfig(BaseTextToSpeechConfig):
|
|||
Reference: https://docs.gradium.ai/api-reference/endpoint/tts-post
|
||||
"""
|
||||
|
||||
TTS_BASE_URL = "https://api.gradium.ai"
|
||||
TTS_ENDPOINT_PATH = "/api/post/speech/tts"
|
||||
DEFAULT_OUTPUT_FORMAT = "wav"
|
||||
|
||||
|
|
@ -128,14 +126,12 @@ class GradiumTextToSpeechConfig(BaseTextToSpeechConfig):
|
|||
api_key: str | None = None,
|
||||
api_base: str | None = None,
|
||||
) -> dict[str, str]: # mutable-ok: signature fixed by BaseTextToSpeechConfig
|
||||
resolved_key: Final = api_key or litellm.api_key or get_secret_str("GRADIUM_API_KEY")
|
||||
|
||||
if resolved_key is None:
|
||||
raise ValueError("Gradium API key is required. Set GRADIUM_API_KEY environment variable.")
|
||||
if not api_key:
|
||||
raise ValueError("Gradium API key is required. Set `api_key` on the model.")
|
||||
|
||||
return {
|
||||
**headers,
|
||||
"x-api-key": resolved_key,
|
||||
"x-api-key": api_key,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +149,12 @@ class GradiumTextToSpeechConfig(BaseTextToSpeechConfig):
|
|||
api_base: str | None,
|
||||
litellm_params: dict[str, object], # mutable-ok: signature fixed by BaseTextToSpeechConfig
|
||||
) -> str:
|
||||
base_url: Final = (api_base or get_secret_str("GRADIUM_API_BASE") or self.TTS_BASE_URL).rstrip("/")
|
||||
if not api_base:
|
||||
raise ValueError(
|
||||
"Gradium API base is required. Set `api_base` on the model, e.g. https://eu.api.gradium.ai."
|
||||
)
|
||||
|
||||
base_url: Final = api_base.rstrip("/")
|
||||
# Gradium documents its regional bases with the /api suffix already on
|
||||
# them (https://eu.api.gradium.ai/api), so appending the full endpoint
|
||||
# path to one would request /api/api/post/speech/tts.
|
||||
|
|
|
|||
|
|
@ -12,13 +12,14 @@ from litellm.llms.gradium.text_to_speech.transformation import (
|
|||
)
|
||||
|
||||
_AUDIO: Final = b"RIFFgradium-audio"
|
||||
_URL: Final = "https://api.gradium.ai/api/post/speech/tts"
|
||||
_API_BASE: Final = "https://eu.api.gradium.ai"
|
||||
_API_KEY: Final = "gd_test_key"
|
||||
_URL: Final = f"{_API_BASE}/api/post/speech/tts"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gradium_env(monkeypatch):
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
monkeypatch.setenv("GRADIUM_API_KEY", "gd_test_key")
|
||||
|
||||
|
||||
def _sent_body(route) -> dict:
|
||||
|
|
@ -150,12 +151,22 @@ def test_builds_the_rest_tts_url_without_doubling_the_api_segment(api_base: str)
|
|||
assert url == "https://eu.api.gradium.ai/api/post/speech/tts"
|
||||
|
||||
|
||||
def test_defaults_to_the_latency_routed_endpoint():
|
||||
def test_requires_an_api_base():
|
||||
config = GradiumTextToSpeechConfig()
|
||||
|
||||
url = config.get_complete_url(model="default", api_base=None, litellm_params={})
|
||||
with pytest.raises(ValueError, match="Gradium API base is required"):
|
||||
config.get_complete_url(model="default", api_base=None, litellm_params={})
|
||||
|
||||
assert url == "https://api.gradium.ai/api/post/speech/tts"
|
||||
|
||||
def test_ignores_gradium_environment_variables(monkeypatch):
|
||||
monkeypatch.setenv("GRADIUM_API_KEY", "gd_from_env")
|
||||
monkeypatch.setenv("GRADIUM_API_BASE", "https://eu.api.gradium.ai")
|
||||
config = GradiumTextToSpeechConfig()
|
||||
|
||||
with pytest.raises(ValueError, match="Gradium API key is required"):
|
||||
config.validate_environment(headers={}, model="default", api_key=None)
|
||||
with pytest.raises(ValueError, match="Gradium API base is required"):
|
||||
config.get_complete_url(model="default", api_base=None, litellm_params={})
|
||||
|
||||
|
||||
def test_requests_raw_audio_rather_than_a_json_message_stream():
|
||||
|
|
@ -226,6 +237,8 @@ def test_speech_calls_the_gradium_rest_endpoint(gradium_env):
|
|||
input="Bonjour",
|
||||
voice="alloy",
|
||||
response_format="wav",
|
||||
api_base=_API_BASE,
|
||||
api_key=_API_KEY,
|
||||
)
|
||||
|
||||
assert response.content == _AUDIO
|
||||
|
|
@ -242,7 +255,9 @@ def test_speech_forwards_a_non_default_model_name(gradium_env):
|
|||
with respx.mock:
|
||||
route = respx.post(_URL).mock(return_value=httpx.Response(200, content=_AUDIO))
|
||||
|
||||
litellm.speech(model="gradium/pro-voice-clone", input="Bonjour", voice="alloy")
|
||||
litellm.speech(
|
||||
model="gradium/pro-voice-clone", input="Bonjour", voice="alloy", api_base=_API_BASE, api_key=_API_KEY
|
||||
)
|
||||
|
||||
assert _sent_body(route)["model_name"] == "pro-voice-clone"
|
||||
|
||||
|
|
@ -252,14 +267,16 @@ async def test_aspeech_calls_the_gradium_rest_endpoint(gradium_env):
|
|||
with respx.mock:
|
||||
respx.post(_URL).mock(return_value=httpx.Response(200, content=_AUDIO))
|
||||
|
||||
response = await litellm.aspeech(model="gradium/default", input="Bonjour", voice="alloy")
|
||||
response = await litellm.aspeech(
|
||||
model="gradium/default", input="Bonjour", voice="alloy", api_base=_API_BASE, api_key=_API_KEY
|
||||
)
|
||||
|
||||
assert response.content == _AUDIO
|
||||
|
||||
|
||||
def test_speech_rejects_a_missing_voice(gradium_env):
|
||||
with pytest.raises(ValueError, match="voice_id is required"):
|
||||
litellm.speech(model="gradium/default", input="Bonjour", voice=None)
|
||||
litellm.speech(model="gradium/default", input="Bonjour", voice=None, api_base=_API_BASE, api_key=_API_KEY)
|
||||
|
||||
|
||||
def test_speech_rejects_a_missing_voice_without_a_provider_config(gradium_env, monkeypatch):
|
||||
|
|
@ -271,7 +288,7 @@ def test_speech_rejects_a_missing_voice_without_a_provider_config(gradium_env, m
|
|||
)
|
||||
|
||||
with pytest.raises(litellm.BadRequestError, match="must resolve to a Gradium voice id"):
|
||||
litellm.speech(model="gradium/default", input="Bonjour", voice=None)
|
||||
litellm.speech(model="gradium/default", input="Bonjour", voice=None, api_base=_API_BASE, api_key=_API_KEY)
|
||||
|
||||
|
||||
def test_speech_honors_an_explicit_api_base_and_api_key(gradium_env):
|
||||
|
|
@ -302,7 +319,9 @@ def test_speech_falls_back_to_its_own_config(gradium_env, monkeypatch):
|
|||
with respx.mock:
|
||||
respx.post(_URL).mock(return_value=httpx.Response(200, content=_AUDIO))
|
||||
|
||||
response = litellm.speech(model="gradium/default", input="Bonjour", voice="alloy")
|
||||
response = litellm.speech(
|
||||
model="gradium/default", input="Bonjour", voice="alloy", api_base=_API_BASE, api_key=_API_KEY
|
||||
)
|
||||
|
||||
assert response.content == _AUDIO
|
||||
|
||||
|
|
@ -311,6 +330,8 @@ def test_speech_tracks_cost_per_character(gradium_env, local_model_cost_map):
|
|||
with respx.mock:
|
||||
respx.post(_URL).mock(return_value=httpx.Response(200, content=_AUDIO))
|
||||
|
||||
response = litellm.speech(model="gradium/default", input="a" * 100, voice="alloy")
|
||||
response = litellm.speech(
|
||||
model="gradium/default", input="a" * 100, voice="alloy", api_base=_API_BASE, api_key=_API_KEY
|
||||
)
|
||||
|
||||
assert response._hidden_params["response_cost"] == pytest.approx(100 * 6.9e-05)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue