From 14b4b3d9667253fc80d4ac498403812d1071c279 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Thu, 26 Feb 2026 11:07:54 +0800 Subject: [PATCH] Implement API URL resolution for OpenAICompatibleTTSService to handle both base and full speech endpoint formats. --- engine/services/openai_compatible_tts.py | 28 +++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/engine/services/openai_compatible_tts.py b/engine/services/openai_compatible_tts.py index 1abb1e5..b2dc30d 100644 --- a/engine/services/openai_compatible_tts.py +++ b/engine/services/openai_compatible_tts.py @@ -10,6 +10,7 @@ import os import asyncio import aiohttp from typing import AsyncIterator, Optional +from urllib.parse import urlparse, urlunparse from loguru import logger from services.base import BaseTTSService, TTSChunk, ServiceState @@ -74,10 +75,35 @@ class OpenAICompatibleTTSService(BaseTTSService): self.api_key = api_key or os.getenv("TTS_API_KEY") or os.getenv("SILICONFLOW_API_KEY") self.model = model - self.api_url = api_url or os.getenv("TTS_API_URL") or "https://api.siliconflow.cn/v1/audio/speech" + raw_api_url = api_url or os.getenv("TTS_API_URL") or "https://api.siliconflow.cn/v1/audio/speech" + self.api_url = self._resolve_speech_endpoint(raw_api_url) self._session: Optional[aiohttp.ClientSession] = None self._cancel_event = asyncio.Event() + + @staticmethod + def _resolve_speech_endpoint(api_url: str) -> str: + """ + Accept either: + - base URL: https://host/v1 + - full speech endpoint: https://host/v1/audio/speech + and always return the final speech endpoint URL. + """ + raw = str(api_url or "").strip() + if not raw: + return "https://api.siliconflow.cn/v1/audio/speech" + + parsed = urlparse(raw) + path = (parsed.path or "").rstrip("/") + if path.endswith("/audio/speech"): + return raw + + if not path: + new_path = "/audio/speech" + else: + new_path = f"{path}/audio/speech" + + return urlunparse(parsed._replace(path=new_path)) async def connect(self) -> None: """Initialize HTTP session."""