HKUDS/DeepTutor · error · VoiceProviderError

TTS request error: {detail}

Error message

TTS request error: {detail}

What it means

During the OpenRouter chat/completions audio fallback, the httpx POST failed at the transport level (httpx.HTTPError) and the adapter wraps it as VoiceProviderError("TTS request error: {detail}"). Same family as the main-path transport error but on the fallback endpoint.

Source

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

        logger.debug(
            "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

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Increase request_timeout — chat audio generation is slower than /audio/speech
  2. Retry the whole synthesis; if /audio/speech fails persistently at HTTP level, fix that first (see the original error chain)
  3. Verify egress to the chat/completions endpoint
  4. Consider disabling the OpenRouter fallback and using a native TTS provider

Example fix

// before
cfg = TTSConfig(request_timeout=10)
// after
cfg = TTSConfig(request_timeout=120)
Defensive patterns

Strategy: retry

Try / catch

try:
    return await adapter.synthesize(text, config)
except VoiceProviderError as exc:
    if "TTS request error" in str(exc):
        return await asyncio.wait_for(adapter.synthesize(text, config), timeout=180)
    raise

Prevention

When it happens

Trigger: Network/DNS/TLS/timeout failure specifically while POSTing to {base_url}/chat/completions in the fallback, after /audio/speech already failed with an HTTP error.

Common situations: Transient network drop between the two requests, request_timeout too small for chat-with-audio generation (which is slower than direct TTS), or proxy interference on streaming responses.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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