@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / ae88468bcc
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 7, 09:00 UTC

fix: Ensure invalid audio files return 400 error (#34715)

Signed-off-by: Jason Ozuzu <jasonozuzu@cohere.com> Co-authored-by: Nicolò Lucchesi <nlucches@redhat.com>

JasonCohere committed Mar 3, 2026 at 16:47 UTC ae88468bcc88773d548122dc05f041a1b3670745
2 files changed +35 -3
tests/entrypoints/openai/test_transcription_validation_whisper.py
+17
@@ -108,6 +108,23 @@ async def test_long_audio_request(mary_had_lamb, whisper_client):
108 assert out_usage["seconds"] == 161, out_usage["seconds"]
109
110
111 +@pytest.mark.asyncio
112 +async def test_invalid_audio_file(whisper_client):
113 + """Corrupted audio should surface as HTTP 400."""
114 + invalid_audio = io.BytesIO(b"not a valid audio file")
115 + invalid_audio.name = "invalid.wav"
116 +
117 + with pytest.raises(openai.BadRequestError) as exc_info:
118 + await whisper_client.audio.transcriptions.create(
119 + model=MODEL_NAME,
120 + file=invalid_audio,
121 + language="en",
122 + )
123 +
124 + assert exc_info.value.status_code == 400
125 + assert "Invalid or unsupported audio file" in exc_info.value.message
126 +
127 +
128 @pytest.mark.asyncio
129 async def test_completion_endpoints(whisper_client):
130 # text to text model
vllm/entrypoints/openai/speech_to_text/speech_to_text.py
+18 -3
@@ -11,6 +11,7 @@ from typing import Final, Literal, TypeAlias, TypeVar, cast
11
12 import numpy as np
13 from fastapi import Request
14 +from soundfile import LibsndfileError
15 from transformers import PreTrainedTokenizerBase
16
17 import vllm.envs as envs
@@ -57,6 +58,14 @@ try:
58 except ImportError:
59 librosa = PlaceholderModule("librosa") # type: ignore[assignment]
60
61 +# Public libsndfile error codes exposed via `soundfile.LibsndfileError.code`, soundfile
62 +# being librosa's main backend. Used to validate if an audio loading error is due to a
63 +# server error vs a client error (invalid audio file).
64 +# 1 = unrecognised format (file is not a supported audio container)
65 +# 3 = malformed file (corrupt or structurally invalid audio)
66 +# 4 = unsupported encoding (codec not supported by this libsndfile build)
67 +_BAD_SF_CODES = {1, 3, 4}
68 +
69 SpeechToTextResponse: TypeAlias = TranscriptionResponse | TranslationResponse
70 SpeechToTextResponseVerbose: TypeAlias = (
71 TranscriptionResponseVerbose | TranslationResponseVerbose
@@ -315,9 +324,15 @@ class OpenAISpeechToText(OpenAIServing):
324 )
325
326 with io.BytesIO(audio_data) as bytes_:
318 - # NOTE resample to model SR here for efficiency. This is also a
319 - # pre-requisite for chunking, as it assumes Whisper SR.
320 - y, sr = librosa.load(bytes_, sr=self.asr_config.sample_rate)
327 + try:
328 + # NOTE resample to model SR here for efficiency. This is also a
329 + # pre-requisite for chunking, as it assumes Whisper SR.
330 + y, sr = librosa.load(bytes_, sr=self.asr_config.sample_rate)
331 + except LibsndfileError as exc:
332 + # Distinguish client errors (invalid audio) from server errors
333 + if exc.code in _BAD_SF_CODES:
334 + raise ValueError("Invalid or unsupported audio file.") from exc
335 + raise
336
337 duration = librosa.get_duration(y=y, sr=sr)
338 do_split_audio = (