diff --git a/litellm/main.py b/litellm/main.py index 0bca4a7350e..229520bebf1 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -8073,7 +8073,8 @@ def speech( # set API KEY api_key = ( api_key - or litellm.api_key # for deepinfra/perplexity/anyscale we check in get_llm_provider and pass in the api key from there + or dynamic_api_key # for deepinfra/perplexity/anyscale we check in get_llm_provider and pass in the api key from there + or litellm.api_key or litellm.openai_key or get_secret("OPENAI_API_KEY") ) @@ -8157,6 +8158,7 @@ def speech( api_key = ( api_key + or dynamic_api_key or litellm.api_key or litellm.azure_key or get_secret("AZURE_OPENAI_API_KEY") diff --git a/tests/test_litellm/test_main.py b/tests/test_litellm/test_main.py index 3c8bf142835..7e2bc3dbe30 100644 --- a/tests/test_litellm/test_main.py +++ b/tests/test_litellm/test_main.py @@ -1,6 +1,8 @@ import asyncio import base64 +import threading from datetime import datetime +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import contextlib import copy import json @@ -3259,3 +3261,74 @@ def test_stream_chunk_builder_leaves_xai_reported_cost_to_the_calculator(monkeyp assert getattr(response.usage, "cost", None) == pytest.approx(0.42) assert response._hidden_params.get("response_cost") is None assert logging_obj._response_cost_calculator(result=response) == pytest.approx(0.63) + + +@contextlib.contextmanager +def _recording_tts_server(): + received: Final[list[Mapping[str, str]]] = [] + + class _Handler(BaseHTTPRequestHandler): + def do_POST(self): + received.append({key.lower(): value for key, value in self.headers.items()}) + self.rfile.read(int(self.headers.get("Content-Length") or 0)) + body = b"ID3\x04\x00\x00\x00fake-mp3-payload" + self.send_response(200) + self.send_header("Content-Type", "audio/mpeg") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, format, *args): + return + + server: Final = ThreadingHTTPServer(("127.0.0.1", 0), _Handler) + thread: Final = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + yield f"http://127.0.0.1:{server.server_port}", received + finally: + server.shutdown() + server.server_close() + thread.join(timeout=5) + + +def test_speech_openai_compatible_sends_the_provider_scoped_key(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setattr(litellm, "api_key", None) + monkeypatch.setattr(litellm, "openai_key", None) + monkeypatch.setattr(litellm, "api_base", None) + monkeypatch.setenv("HOSTED_VLLM_API_KEY", "hosted-vllm-scoped-key") + monkeypatch.setenv("OPENAI_API_KEY", "generic-openai-key") + + with _recording_tts_server() as (api_base, received): + response: Final = litellm.speech( + model="hosted_vllm/tts-1", + input="which key goes out", + voice="alloy", + api_base=api_base, + ) + + assert response.content == b"ID3\x04\x00\x00\x00fake-mp3-payload" + assert len(received) == 1 + assert received[0]["authorization"] == "Bearer hosted-vllm-scoped-key" + + +def test_speech_azure_sends_the_provider_scoped_key(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setattr(litellm, "api_key", None) + monkeypatch.setattr(litellm, "azure_key", None) + monkeypatch.setattr(litellm, "api_base", None) + monkeypatch.setenv("AZURE_AI_API_KEY", "azure-ai-scoped-key") + monkeypatch.setenv("AZURE_OPENAI_API_KEY", "legacy-azure-openai-key") + monkeypatch.setenv("AZURE_API_KEY", "legacy-azure-key") + + with _recording_tts_server() as (api_base, received): + response: Final = litellm.speech( + model="azure_ai/tts-1", + input="which key goes out", + voice="alloy", + api_base=api_base, + api_version="2024-05-01-preview", + ) + + assert response.content == b"ID3\x04\x00\x00\x00fake-mp3-payload" + assert len(received) == 1 + assert received[0]["api-key"] == "azure-ai-scoped-key"