HKUDS/DeepTutor · error · VoiceProviderError

OpenRouter chat audio returned no audio chunks; original /au

Error message

OpenRouter chat audio returned no audio chunks; original /audio/speech error: {original_error}

What it means

The OpenRouter chat/completions fallback completed with HTTP 200 and its SSE lines were scanned, but no audio chunks were collected (audio_chunks empty). The adapter raises VoiceProviderError including the original /audio/speech error for context. It means the model replied with text (or nothing parseable) instead of audio data.

Source

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

            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
    def _collect_audio_line(line: str, audio_chunks: list[str]) -> None:
        if not line:
            return
        raw = line.strip()
        if not raw.startswith("data:"):

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Use an audio-capable model (e.g. *-audio-preview) on OpenRouter
  2. Check debug logs for "Ignoring malformed OpenRouter SSE line" — a format change would show there
  3. Test the same chat/completions request with curl including the audio output modulation to confirm the model emits audio
  4. Fall back to a native TTS provider for speech synthesis
Defensive patterns

Strategy: fallback

Try / catch

try:
    audio, ct = await openrouter_adapter.synthesize(text, config)
except VoiceProviderError as exc:
    if "no audio chunks" in str(exc):
        return await native_tts.synthesize(text, native_config)
    raise

Prevention

When it happens

Trigger: The chat request succeeded but the model returned plain text content with no base64 audio payload — e.g. the model doesn't support the audio output modulation, or the prompt/modality flags weren't accepted.

Common situations: Using a non-audio model on OpenRouter with the fallback enabled; the gateway silently dropping the audio field; or an SSE format change that _collect_audio_line no longer recognizes.

Related errors


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