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