{"record":{"id":"2636500ff51cc415","repo":"HKUDS/DeepTutor","slug":"exc-original-audio-speech-error-original-err","errorCode":null,"errorMessage":"{exc}; original /audio/speech error: {original_error}","messagePattern":"(.+?); original /audio/speech error: (.+?)","errorType":"exception","errorClass":"VoiceProviderError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/voice/adapters/openai_compat.py","lineNumber":237,"sourceCode":"            \"OpenRouter chat-audio synthesize url=%s model=%s voice=%s fmt=%s chars=%d\",\n            url,\n            config.model,\n            config.voice,\n            audio_format,\n            len(text),\n        )\n        audio_chunks: list[str] = []\n        try:\n            async with httpx.AsyncClient(timeout=config.request_timeout) as client:\n                resp = await client.post(url, headers=headers, json=payload)\n            _raise_for_provider(resp, \"OpenRouter chat audio synthesis\")\n            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","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/voice/adapters/openai_compat.py#L219-L255","documentation":"In the OpenRouter fallback, the chat/completions endpoint itself returned HTTP >= 400 (converted by _raise_for_provider into VoiceProviderHTTPError). The adapter re-wraps it as VoiceProviderError chaining both the fallback failure and the original /audio/speech error so the developer sees why the fallback was attempted at all.","triggerScenarios":"/audio/speech failed with an HTTP error AND the subsequent chat/completions audio request also returned 4xx/5xx (bad model, auth, quota, unsupported audio modulation).","commonSituations":"Model lacks audio output support on both endpoints, key/credit problems, or audio-preview models not enabled for the account. The dual message is the key diagnostic: both endpoints rejected the request.","solutions":["Read both halves of the message — the original /audio/speech error often identifies the root cause (e.g. model lacks audio)","Switch to an audio-capable OpenRouter model","Check API key validity and account credits (401/402)","If the original error is 404, fix base_url before debugging the fallback"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    audio, ct = await adapter.synthesize(text, config)\nexcept VoiceProviderError as exc:\n    log.error(\"Both /audio/speech and chat fallback failed: %s\", exc)\n    notify_user_voice_unavailable()\n    raise","preventionTips":["Treat the chained message as one incident: fix the original /audio/speech error","Verify model audio support and account credits before enabling the fallback"],"tags":["voice","tts","openrouter","http-error","chained-error"],"backgroundTag":"provider-http-error","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}