{"record":{"id":"d63177eb3bccce91","repo":"HKUDS/DeepTutor","slug":"no-endpoint-url-configured-for-tts","errorCode":null,"errorMessage":"No endpoint URL configured for TTS.","messagePattern":"No endpoint URL configured for TTS\\.","errorType":"exception","errorClass":"VoiceProviderError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/voice/adapters/openai_compat.py","lineNumber":117,"sourceCode":"    if (\n        config.provider_name == \"openrouter\"\n        and \"gemini\" in model\n        and \"tts\" in model\n        and voice.lower() in _OPENAI_TTS_VOICES\n    ):\n        return (\n            f\" Voice `{voice}` is an OpenAI TTS voice; Gemini TTS expects Google \"\n            \"prebuilt voice names such as `Kore` or `Puck`.\"\n        )\n    return \"\"\n\n\nclass OpenAICompatTTSAdapter(BaseTTSAdapter):\n    \"\"\"POST ``{base}/audio/speech`` with a JSON body, returning raw audio bytes.\"\"\"\n\n    async def synthesize(self, text: str, config: TTSConfig) -> tuple[bytes, str]:\n        if not config.base_url:\n            raise VoiceProviderError(\"No endpoint URL configured for TTS.\")\n        url = join_audio_path(config.base_url, \"audio/speech\")\n        headers = {\n            \"Content-Type\": \"application/json\",\n            **build_auth_headers(config.auth_style, config.api_key),\n            **(config.extra_headers or {}),\n        }\n        response_format = (config.response_format or \"mp3\").lower()\n        payload: dict[str, Any] = {\n            \"model\": config.model,\n            \"input\": text,\n            \"response_format\": response_format,\n        }\n        if config.voice:\n            payload[\"voice\"] = config.voice\n        if config.speed is not None:\n            payload[\"speed\"] = config.speed\n\n        logger.debug(","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/voice/adapters/openai_compat.py#L99-L135","documentation":"OpenAICompatTTSAdapter.synthesize POSTs to {base_url}/audio/speech and requires a non-empty config.base_url. With no base URL the adapter cannot form the endpoint and raises VoiceProviderError before any request. It is the primary guard for TTS endpoint configuration.","triggerScenarios":"Calling synthesize(text, config) where TTSConfig.base_url is empty/None — e.g. the voice provider entry in settings has an API key but no endpoint.","commonSituations":"Admin configured only an api_key assuming a default OpenAI endpoint, settings JSON lost the base_url field, or an env var override set it to an empty string.","solutions":["Set base_url on the TTSConfig / voice provider settings (e.g. https://api.openai.com/v1)","Check data/user/settings/*.json and process-env overrides for an empty voice endpoint","Add startup validation of voice provider config"],"exampleFix":"// before\nawait adapter.synthesize(\"hi\", TTSConfig(base_url=\"\", api_key=key))\n// after\nawait adapter.synthesize(\"hi\", TTSConfig(base_url=\"https://api.openai.com/v1\", api_key=key))","handlingStrategy":"validation","validationCode":"if not (tts_config.base_url or \"\").strip():\n    raise ValueError(\"TTS base_url must be configured\")","typeGuard":null,"tryCatchPattern":"try:\n    audio, content_type = await adapter.synthesize(text, config)\nexcept VoiceProviderError as exc:\n    if \"No endpoint URL configured for TTS\" in str(exc):\n        # config problem, not transient — do not retry\n        alert_admin_missing_tts_endpoint()","preventionTips":["Populate base_url whenever an api_key is set for a voice provider","Fail fast on startup with a settings validator"],"tags":["voice","tts","missing-config","endpoint"],"backgroundTag":"missing-base-url","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}