fix(proxy): surface client-side (4xx) errors from /v1/audio/speech instead of 500

This commit is contained in:
Devin AI 2026-07-13 04:07:56 +00:00
parent 3e9e52042a
commit 3059512fa3
2 changed files with 61 additions and 1 deletions

View file

@ -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(

View file

@ -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)."""