{"record":{"id":"d3c84ed29dfc72b5","repo":"HKUDS/DeepTutor","slug":"tts-provider-returned-empty-audio","errorCode":null,"errorMessage":"TTS provider returned empty audio.","messagePattern":"TTS provider returned empty audio\\.","errorType":"exception","errorClass":"VoiceProviderError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/voice/adapters/openai_compat.py","lineNumber":157,"sourceCode":"            config.voice,\n            response_format,\n            len(text),\n        )\n        try:\n            async with httpx.AsyncClient(timeout=config.request_timeout) as client:\n                resp = await client.post(url, headers=headers, json=payload)\n        except httpx.HTTPError as exc:\n            raise VoiceProviderError(f\"TTS request error: {exc}\") from exc\n        try:\n            _raise_for_provider(resp, \"TTS synthesis\")\n        except VoiceProviderHTTPError as exc:\n            hint = _openrouter_tts_hint(config)\n            if hint:\n                raise VoiceProviderError(f\"{exc}{hint}\") from exc\n            raise\n        audio = resp.content\n        if not audio:\n            raise VoiceProviderError(\"TTS provider returned empty audio.\")\n        content_type = resp.headers.get(\"content-type\") or _FORMAT_CONTENT_TYPES.get(\n            response_format, \"application/octet-stream\"\n        )\n        # Some gateways return JSON content-type with audio; trust the format map.\n        if \"json\" in content_type:\n            content_type = _FORMAT_CONTENT_TYPES.get(response_format, \"audio/mpeg\")\n        return audio, content_type\n\n\nclass OpenRouterTTSAdapter(BaseTTSAdapter):\n    \"\"\"OpenRouter TTS with fallback for streaming chat-audio models.\n\n    OpenRouter documents both a dedicated ``/audio/speech`` endpoint for TTS\n    models and audio output through ``/chat/completions`` for models that expose\n    the ``audio`` output modality. Try the dedicated endpoint first, then fall\n    back to chat audio for configs pointed at audio-output chat models.\n    \"\"\"\n","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/voice/adapters/openai_compat.py#L139-L175","documentation":"After a successful HTTP response from /audio/speech, the adapter checks resp.content; an empty body means the provider claimed success but returned zero audio bytes, so a VoiceProviderError is raised rather than returning invalid audio.","triggerScenarios":"Provider returns 200 with an empty body — seen with some gateways/proxies that strip binary responses, misconfigured self-hosted TTS, or models that silently produce nothing.","commonSituations":"A reverse proxy buffering/corrupting binary output, a provider bug for a specific voice/format combination, or an unsupported response_format that the gateway ignores.","solutions":["Retry once — some providers intermittently return empty bodies","Try a different response_format (e.g. mp3 vs wav) or a different voice/model","If self-hosted, check server logs; if proxied, bypass the proxy to compare","Report to the provider if it consistently returns 200 with empty content"],"exampleFix":null,"handlingStrategy":"retry","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) and attempt < MAX:\n        continue  # retry — intermittently empty bodies\n    raise","preventionTips":["Retry empty-audio responses once or twice","Validate the chosen response_format against the provider's docs","Report providers that consistently return 200 with empty bodies"],"tags":["voice","tts","empty-response","provider"],"backgroundTag":"empty-response-body","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}