HKUDS/DeepTutor · error · VoiceProviderError

{exc}; original /audio/speech error: {original_error}

Error message

{exc}; original /audio/speech error: {original_error}

What it means

In the OpenRouter fallback, the chat/completions endpoint itself returned HTTP >= 400 (converted by _raise_for_provider into VoiceProviderHTTPError). The adapter re-wraps it as VoiceProviderError chaining both the fallback failure and the original /audio/speech error so the developer sees why the fallback was attempted at all.

Source

Thrown at deeptutor/services/voice/adapters/openai_compat.py:237

            "OpenRouter chat-audio synthesize url=%s model=%s voice=%s fmt=%s chars=%d",
            url,
            config.model,
            config.voice,
            audio_format,
            len(text),
        )
        audio_chunks: list[str] = []
        try:
            async with httpx.AsyncClient(timeout=config.request_timeout) as client:
                resp = await client.post(url, headers=headers, json=payload)
            _raise_for_provider(resp, "OpenRouter chat audio synthesis")
            for line in (resp.text or "").splitlines():
                self._collect_audio_line(line, audio_chunks)
        except httpx.HTTPError as exc:
            detail = str(exc) or exc.__class__.__name__
            raise VoiceProviderError(f"TTS request error: {detail}") from exc
        except VoiceProviderHTTPError as exc:
            raise VoiceProviderError(
                f"{exc}; original /audio/speech error: {original_error}"
            ) from exc

        if not audio_chunks:
            raise VoiceProviderError(
                "OpenRouter chat audio returned no audio chunks; "
                f"original /audio/speech error: {original_error}"
            )
        try:
            audio = base64.b64decode("".join(audio_chunks))
        except binascii.Error as exc:
            raise VoiceProviderError("OpenRouter chat audio returned invalid base64.") from exc
        if not audio:
            raise VoiceProviderError("OpenRouter chat audio returned empty audio.")
        content_type = _FORMAT_CONTENT_TYPES.get(audio_format, "application/octet-stream")
        return audio, content_type

    @staticmethod

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Read both halves of the message — the original /audio/speech error often identifies the root cause (e.g. model lacks audio)
  2. Switch to an audio-capable OpenRouter model
  3. Check API key validity and account credits (401/402)
  4. If the original error is 404, fix base_url before debugging the fallback
Defensive patterns

Strategy: try-catch

Try / catch

try:
    audio, ct = await adapter.synthesize(text, config)
except VoiceProviderError as exc:
    log.error("Both /audio/speech and chat fallback failed: %s", exc)
    notify_user_voice_unavailable()
    raise

Prevention

When it happens

Trigger: /audio/speech failed with an HTTP error AND the subsequent chat/completions audio request also returned 4xx/5xx (bad model, auth, quota, unsupported audio modulation).

Common situations: Model lacks audio output support on both endpoints, key/credit problems, or audio-preview models not enabled for the account. The dual message is the key diagnostic: both endpoints rejected the request.

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/2636500ff51cc415. Report an issue: GitHub.