Detect broken STT backends via real transcription probe in health check
_stt_backend_health now sends a minimal WAV to the transcription endpoint after passing /health. A 500 response marks the backend unavailable, catching containers that pass health checks but crash on model load (e.g. CTranslate2 built without CUDA support). Error messages from _transcribe_audio now include the backend URL and replace generic 'Internal Server Error' with an actionable explanation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
28361f2db6
commit
8f9060d025
42
server.py
42
server.py
@ -1903,6 +1903,20 @@ def _stt_backend_api_key(settings: dict, backend: str) -> str:
|
||||
return settings.get("whisper_api_key", "").strip()
|
||||
|
||||
|
||||
def _make_minimal_wav() -> bytes:
|
||||
"""44-byte WAV header with 1 frame of silence — smallest valid WAV."""
|
||||
import struct
|
||||
num_frames = 1
|
||||
data = b"\x00\x00"
|
||||
header = struct.pack(
|
||||
"<4sI4s4sIHHIIHH4sI",
|
||||
b"RIFF", 36 + len(data), b"WAVE",
|
||||
b"fmt ", 16, 1, 1, 16000, 32000, 2, 16,
|
||||
b"data", len(data),
|
||||
)
|
||||
return header + data
|
||||
|
||||
|
||||
def _stt_backend_health(url: str) -> tuple[bool, list[str]]:
|
||||
base = _validate_http_url(url, allow_private=True).rstrip("/")
|
||||
models: list[str] = []
|
||||
@ -1925,6 +1939,30 @@ def _stt_backend_health(url: str) -> tuple[bool, list[str]]:
|
||||
models.append(item)
|
||||
except Exception:
|
||||
pass
|
||||
if not ok:
|
||||
return False, models
|
||||
# Verify the transcription endpoint actually works (catches broken builds like
|
||||
# CTranslate2 containers compiled without CUDA that pass /health but fail on load)
|
||||
try:
|
||||
wav = _make_minimal_wav()
|
||||
for path in ("/v1/audio/transcriptions", "/transcribe"):
|
||||
try:
|
||||
r = requests.post(
|
||||
f"{base}{path}",
|
||||
files={"file": ("probe.wav", wav, "audio/wav")},
|
||||
data={"model": "whisper-1", "response_format": "text"},
|
||||
timeout=8,
|
||||
)
|
||||
if r.status_code == 500:
|
||||
ok = False # backend is broken (model load failed etc.)
|
||||
break # 200, 400, 404, 422 all mean the endpoint exists and model loaded
|
||||
except requests.exceptions.ConnectionError:
|
||||
ok = False
|
||||
break
|
||||
except Exception:
|
||||
pass # timeout or other — don't mark as broken, just skip
|
||||
except Exception:
|
||||
pass
|
||||
return ok, models
|
||||
|
||||
|
||||
@ -2039,7 +2077,9 @@ def _transcribe_audio(src: Path, settings: dict, backend: str = "configured") ->
|
||||
detail = body.get("detail") or body.get("error") or body.get("message") or str(body)
|
||||
except Exception:
|
||||
detail = resp.text[:300].strip()
|
||||
raise RuntimeError(f"STT backend returned HTTP {resp.status_code}: {detail}")
|
||||
if not detail or detail.lower() in {"internal server error", "unknown error"}:
|
||||
detail = f"HTTP {resp.status_code} — backend may be misconfigured or missing CUDA support"
|
||||
raise RuntimeError(f"STT ({stt_url}): {detail}")
|
||||
return _transcription_text_from_response(resp), backend
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user