diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index f0ca1f6396f..346a7369e66 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -9352,7 +9352,22 @@ async def audio_speech( ) verbose_proxy_logger.error("litellm.proxy.proxy_server.audio_speech(): Exception occured - {}".format(str(e))) verbose_proxy_logger.debug(traceback.format_exc()) - raise e + if isinstance(e, HTTPException): + raise ProxyException( + message=getattr(e, "message", str(e.detail)), + type=getattr(e, "type", "None"), + param=getattr(e, "param", "None"), + code=getattr(e, "status_code", status.HTTP_400_BAD_REQUEST), + ) + else: + error_msg = f"{str(e)}" + raise ProxyException( + message=getattr(e, "message", error_msg), + type=getattr(e, "type", "None"), + param=getattr(e, "param", "None"), + openai_code=getattr(e, "code", None), + code=getattr(e, "status_code", 500), + ) @router.post( diff --git a/tests/test_litellm/proxy/proxy_server/test_routes_audio.py b/tests/test_litellm/proxy/proxy_server/test_routes_audio.py index 74542a3eaf6..587f635c4e1 100644 --- a/tests/test_litellm/proxy/proxy_server/test_routes_audio.py +++ b/tests/test_litellm/proxy/proxy_server/test_routes_audio.py @@ -79,6 +79,38 @@ def patched_speech_error(monkeypatch): yield +@pytest.fixture +def patched_speech_client_error(monkeypatch): + monkeypatch.setattr(proxy_server, "llm_router", MagicMock()) + monkeypatch.setattr( + proxy_server, + "proxy_logging_obj", + MagicMock( + pre_call_hook=AsyncMock(side_effect=lambda **kw: kw["data"]), + post_call_failure_hook=AsyncMock(), + post_call_response_headers_hook=AsyncMock(return_value={}), + update_request_status=AsyncMock(), + ), + ) + + async def _add_data(data, **kwargs): + return data + + monkeypatch.setattr(proxy_server, "add_litellm_data_to_request", _add_data) + + async def _raise(*args, **kwargs): + import litellm + + raise litellm.BadRequestError( + message="OperationNotSupported: speech is not supported", + model="gpt-audio", + llm_provider="azure", + ) + + monkeypatch.setattr(proxy_server, "route_request", _raise) + yield + + @pytest.fixture def patched_transcription(monkeypatch): router = MagicMock() @@ -162,6 +194,19 @@ def test_audio_speech_error(client, auth_as, patched_speech_error, path): assert len(response.content) > 0 +@pytest.mark.parametrize("path", ["/v1/audio/speech", "/audio/speech"]) +def test_audio_speech_client_error_preserves_status( + client, auth_as, patched_speech_client_error, path +): + """A client-side (4xx) provider error on speech must surface as 4xx, not 500.""" + payload = {"model": "gpt-audio", "input": "Hi", "voice": "alloy"} + with auth_as(): + response = client.post(path, json=payload) + assert response.status_code == 400 + body = response.json() + assert "OperationNotSupported" in body["error"]["message"] + + @pytest.mark.parametrize("path", ["/v1/audio/transcriptions", "/audio/transcriptions"]) def test_audio_transcription_happy_path(client, auth_as, patched_transcription, path): """Pins ``POST /v1/audio/transcriptions`` / ``POST /audio/transcriptions`` (happy)."""