HKUDS/DeepTutor · error · VoiceProviderError

OpenRouter chat audio returned invalid base64.

Error message

OpenRouter chat audio returned invalid base64.

What it means

The fallback collected base64 audio strings from the SSE stream, but base64.b64decode of their concatenation raised binascii.Error — the payload is not valid base64. The adapter raises VoiceProviderError rather than returning corrupted audio.

Source

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

            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:"):
            return
        data = raw[len("data:") :].strip()
        if not data or data == "[DONE]":
            return
        try:
            chunk = json.loads(data)
        except json.JSONDecodeError:

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Log the collected chunks to inspect for data-URI prefixes and strip them before decoding
  2. Check for response truncation (max_tokens too low) cutting the base64 mid-stream
  3. Retry — intermittent corruption can be transient
  4. Report the payload format to the adapter maintainers if the gateway changed its SSE schema
Defensive patterns

Strategy: try-catch

Try / catch

try:
    audio, ct = await adapter.synthesize(text, config)
except VoiceProviderError as exc:
    if "invalid base64" in str(exc):
        log.warning("Corrupt audio payload from OpenRouter; retrying")
        return await adapter.synthesize(text, config)
    raise

Prevention

When it happens

Trigger: The model/gateway returned audio-like fields containing non-base64 data (URLs, JSON fragments, truncated chunks), or chunk boundaries split the stream such that naive concatenation is invalid.

Common situations: Gateway inserting formatting into data URIs (e.g. data:audio/mp3;base64, prefixes), partial/truncated responses, or upstream format changes in the SSE audio field.

Related errors


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