{"record":{"id":"09e9008fcd9bf6e6","repo":"HKUDS/DeepTutor","slug":"openrouter-chat-audio-returned-invalid-base64","errorCode":null,"errorMessage":"OpenRouter chat audio returned invalid base64.","messagePattern":"OpenRouter chat audio returned invalid base64\\.","errorType":"exception","errorClass":"VoiceProviderError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/voice/adapters/openai_compat.py","lineNumber":249,"sourceCode":"            for line in (resp.text or \"\").splitlines():\n                self._collect_audio_line(line, audio_chunks)\n        except httpx.HTTPError as exc:\n            detail = str(exc) or exc.__class__.__name__\n            raise VoiceProviderError(f\"TTS request error: {detail}\") from exc\n        except VoiceProviderHTTPError as exc:\n            raise VoiceProviderError(\n                f\"{exc}; original /audio/speech error: {original_error}\"\n            ) from exc\n\n        if not audio_chunks:\n            raise VoiceProviderError(\n                \"OpenRouter chat audio returned no audio chunks; \"\n                f\"original /audio/speech error: {original_error}\"\n            )\n        try:\n            audio = base64.b64decode(\"\".join(audio_chunks))\n        except binascii.Error as exc:\n            raise VoiceProviderError(\"OpenRouter chat audio returned invalid base64.\") from exc\n        if not audio:\n            raise VoiceProviderError(\"OpenRouter chat audio returned empty audio.\")\n        content_type = _FORMAT_CONTENT_TYPES.get(audio_format, \"application/octet-stream\")\n        return audio, content_type\n\n    @staticmethod\n    def _collect_audio_line(line: str, audio_chunks: list[str]) -> None:\n        if not line:\n            return\n        raw = line.strip()\n        if not raw.startswith(\"data:\"):\n            return\n        data = raw[len(\"data:\") :].strip()\n        if not data or data == \"[DONE]\":\n            return\n        try:\n            chunk = json.loads(data)\n        except json.JSONDecodeError:","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/voice/adapters/openai_compat.py#L231-L267","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    audio, ct = await adapter.synthesize(text, config)\nexcept VoiceProviderError as exc:\n    if \"invalid base64\" in str(exc):\n        log.warning(\"Corrupt audio payload from OpenRouter; retrying\")\n        return await adapter.synthesize(text, config)\n    raise","preventionTips":["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"],"tags":["voice","tts","openrouter","base64","corrupt-response"],"backgroundTag":"invalid-base64-response","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}