{"record":{"id":"ca074a131afe361c","repo":"HKUDS/DeepTutor","slug":"no-endpoint-url-configured-for-this-provider-ca074a","errorCode":null,"errorMessage":"No endpoint URL configured for this provider.","messagePattern":"No endpoint URL configured for this provider\\.","errorType":"exception","errorClass":"VoiceProviderError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/voice/adapters/openai_compat.py","lineNumber":80,"sourceCode":"\n\ndef _raise_for_provider(resp: httpx.Response, action: str) -> None:\n    \"\"\"Surface a provider error with a trimmed body for diagnostics.\"\"\"\n    if resp.status_code < 400:\n        return\n    body = resp.text or \"\"\n    raise VoiceProviderHTTPError(\n        _provider_error_message(action, resp.status_code, body),\n        status_code=resp.status_code,\n        body=body,\n    )\n\n\ndef _join_api_path(base_url: str, suffix: str) -> str:\n    \"\"\"Append a generic API path to ``base_url`` while preserving query strings.\"\"\"\n    base = (base_url or \"\").strip()\n    if not base:\n        raise VoiceProviderError(\"No endpoint URL configured for this provider.\")\n    head, sep, query = base.partition(\"?\")\n    suffix = suffix.strip(\"/\")\n    if head.rstrip(\"/\").endswith(f\"/{suffix}\"):\n        return base\n    joined = f\"{head.rstrip('/')}/{suffix}\"\n    return f\"{joined}?{query}\" if sep else joined\n\n\ndef _chat_audio_format(response_format: str) -> str:\n    \"\"\"Map OpenAI speech formats onto OpenRouter chat audio formats.\"\"\"\n    fmt = (response_format or \"mp3\").strip().lower()\n    return \"pcm16\" if fmt == \"pcm\" else fmt\n\n\ndef _openrouter_tts_hint(config: TTSConfig) -> str:\n    \"\"\"Return a provider/model-specific hint for opaque OpenRouter TTS errors.\"\"\"\n    model = (config.model or \"\").lower()\n    voice = (config.voice or \"\").strip()","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/voice/adapters/openai_compat.py#L62-L98","documentation":"_join_api_path builds an OpenRouter chat/completions URL from the provider base_url. An empty or whitespace-only base_url means no endpoint is configured, so the adapter refuses to construct a URL and raises VoiceProviderError. This is a configuration error surfaced before any network call.","triggerScenarios":"_synthesize_chat_audio is reached (OpenRouter TTS fallback after /audio/speech fails) while config.base_url is empty, None, or only whitespace.","commonSituations":"Voice settings JSON omits the base URL for the OpenRouter provider, the env override was cleared, or a provider profile was created without an endpoint because the user relied on a default that only exists for OpenAI-style keys.","solutions":["Set the provider's base_url in voice settings (e.g. https://openrouter.ai/api/v1)","Verify runtime settings/env overrides are not blanking the field (data/user/settings/*.json)","Fail fast at app startup by validating that any enabled voice provider has a base URL"],"exampleFix":"// before\nconfig.base_url = \"\"\n// after\nconfig.base_url = \"https://openrouter.ai/api/v1\"","handlingStrategy":"validation","validationCode":"if not (config.base_url or \"\").strip():\n    raise ValueError(\"OpenRouter TTS base_url is required before synthesis\")","typeGuard":null,"tryCatchPattern":"try:\n    audio, ct = await adapter.synthesize(text, config)\nexcept VoiceProviderError as exc:\n    if \"No endpoint URL\" in str(exc):\n        log.error(\"Voice provider misconfigured: missing base_url\")\n    raise","preventionTips":["Require base_url in provider settings schemas","Run a config health check on startup for all enabled voice providers"],"tags":["voice","tts","openrouter","missing-config"],"backgroundTag":"missing-base-url","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}