{"record":{"id":"306ba1c6ac0854b9","repo":"HKUDS/DeepTutor","slug":"openrouter-chat-audio-returned-empty-audio","errorCode":null,"errorMessage":"OpenRouter chat audio returned empty audio.","messagePattern":"OpenRouter chat audio returned empty audio\\.","errorType":"exception","errorClass":"VoiceProviderError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/voice/adapters/openai_compat.py","lineNumber":251,"sourceCode":"        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:\n            logger.debug(\"Ignoring malformed OpenRouter SSE line: %s\", data[:160])\n            return","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/voice/adapters/openai_compat.py#L233-L269","documentation":"The OpenRouter fallback successfully decoded base64 audio but the decoded bytes are empty (zero-length). The adapter refuses to return empty audio and raises VoiceProviderError.","triggerScenarios":"The SSE stream contained one or more chunks that decode to zero bytes — e.g. a single empty-string chunk, or padding-only base64.","commonSituations":"Model acknowledged the audio request but produced an empty payload (often when the prompt elicits no spoken content, or the modulation is ignored). Usually paired with a prior /audio/speech failure.","solutions":["Retry with a shorter, clearly speakable text input","Switch to an audio-capable model or a native TTS provider","Check the original /audio/speech error (not included here) for the underlying provider issue","Enable debug logging to see the raw chunks received"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    audio, ct = await adapter.synthesize(text, config)\nexcept VoiceProviderError as exc:\n    if \"empty audio\" in str(exc):\n        return await native_tts.synthesize(text, native_config)\n    raise","preventionTips":["Keep a secondary TTS provider configured for automatic failover","Smoke-test new models with short utterances before production use"],"tags":["voice","tts","openrouter","empty-response"],"backgroundTag":"empty-response-body","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}