odysseus-dev/odysseus · error · HTTPException
Transcription failed
Error message
Transcription failed
What it means
Raised by the STT (speech-to-text) upload endpoint when stt_service.transcribe(audio_bytes) returns None instead of a transcription string. The service object reported itself as available, but the transcription step itself failed internally (model load failure, unsupported/corrupt audio, decode error) and signals failure by returning None. The API maps that sentinel to HTTP 500 with detail {"message": "Transcription failed"}.
Source
Thrown at routes/stt_routes.py:41
raise HTTPException(status_code=500, detail=str(e))
@router.post("/transcribe")
async def transcribe_audio(file: UploadFile = File(...)):
"""Transcribe uploaded audio file to text"""
try:
if not stt_service.available:
raise HTTPException(
status_code=503,
detail={"message": "STT service not available or set to browser mode"}
)
audio_bytes = await read_upload_limited(file, STT_MAX_AUDIO_BYTES, "Audio file")
if not audio_bytes:
raise HTTPException(status_code=400, detail={"message": "Empty audio file"})
text = stt_service.transcribe(audio_bytes)
if text is None:
raise HTTPException(
status_code=500,
detail={"message": "Transcription failed"}
)
return {"text": text}
except HTTPException:
raise
except Exception as e:
logger.error(f"Transcription error: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail={"message": f"Transcription failed: {str(e)}"}
)
return router
View on GitHub (pinned to f9235ebbf1)
Solutions
- Check server logs: the companion handler at stt_routes.py logs 'Transcription error: ...' with exc_info for unexpected exceptions — read the traceback to find the real cause.
- Verify the audio format is supported: convert the clip to WAV/MP3 (16 kHz mono WAV is safest) and retry.
- Confirm the STT model artifacts exist and load: check the stt_service configuration (model path/name) and that the container/host has ffmpeg installed.
- If transcription keeps returning None on valid input, inspect stt_service.transcribe for swallowed exceptions and make it raise or log the underlying error instead of returning None.
Example fix
// before: caller cannot tell why it failed
const r = await fetch('/api/stt/transcribe', {method:'POST', body: fd});
if (!r.ok) throw new Error('failed');
// after: surface the structured detail
const r = await fetch('/api/stt/transcribe', {method:'POST', body: fd});
if (r.status === 500) {
const {detail} = await r.json();
throw new Error(detail?.message ?? 'Transcription failed');
} Defensive patterns
Strategy: fallback
Validate before calling
const MAX = 25 * 1024 * 1024; // match STT_MAX_AUDIO_BYTES
async function preflight(file) {
if (!file || file.size === 0) throw new Error('Empty audio file');
if (file.size > MAX) throw new Error('Audio too large');
if (!/^(audio|video)\//.test(file.type) && !/\.(wav|mp3|m4a|ogg|webm)$/i.test(file.name))
throw new Error('Unsupported audio format');
const health = await fetch('/api/stt/health'); // if exposed
return health.ok;
} Type guard
function isTranscribeOk(body: unknown): body is { text: string } {
return typeof body === 'object' && body !== null && typeof (body as any).text === 'string' && (body as any).text.length >= 0;
} Try / catch
try {
const res = await fetch('/api/stt/transcribe', {method: 'POST', body: fd});
if (res.status === 503) return useBrowserStt(); // service off
if (res.status === 400) return alertUser('Bad audio');
if (res.status === 500) return useBrowserStt(); // transcribe() returned None
return (await res.json()).text;
} catch (e) {
return useBrowserStt(); // network-level fallback
} Prevention
- Convert uploads to 16 kHz mono WAV client-side (e.g. WebAudio downmix) before POSTing.
- Guard the endpoint in UI behind the STT availability flag so users never reach a 500.
- Keep a browser-based SpeechRecognition fallback for when server transcription fails.
- Alert on 500-rate of this route: transcribe() returning None usually means model/env drift.
When it happens
Trigger: POST to the /api/stt transcribe route with a multipart audio file while stt_service.available is truthy, but transcribe() returns None — e.g. whisper model missing/corrupt on disk, audio container/codec the backend cannot decode, empty audio stream after a non-zero-length upload, or an OOM/exception swallowed inside the service that returns None.
Common situations: STT backend set to a local model path that does not exist; whisper weights downloaded for a different architecture; uploading webm/ogg clips when only wav is supported; GPU out of memory during inference; docker image built without the audio decode system libs (ffmpeg).
Related errors
- Transcription failed: {str(e)}
- Failed to delete calendar
- Failed to list calendars
- Failed to list events
- Failed to create event
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/112deebca6842651.
Report an issue: GitHub.