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
- Log the collected chunks to inspect for data-URI prefixes and strip them before decoding
- Check for response truncation (max_tokens too low) cutting the base64 mid-stream
- Retry — intermittent corruption can be transient
- 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
- Watch for data-URI prefixes in streamed audio fields if you pre-process chunks
- Retry on decode failures; corruption is often transient or model-specific
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
- No endpoint URL configured for this provider.
- {exc}{hint}
- TTS request error: {detail}
- {exc}; original /audio/speech error: {original_error}
- OpenRouter chat audio returned no audio chunks; original /au
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/09e9008fcd9bf6e6.
Report an issue: GitHub.