personabot-api / app /services /tts_client.py
GitHub Actions
Deploy 8e59863
7ff4fe5
import asyncio
import httpx
from app.core.exceptions import GenerationError
class TTSClient:
def __init__(self, tts_space_url: str, timeout_seconds: float) -> None:
self._tts_space_url = tts_space_url.rstrip("/")
self._timeout_seconds = timeout_seconds
# Persistent client — reuses connections
self._http = httpx.AsyncClient(
timeout=timeout_seconds,
limits=httpx.Limits(max_keepalive_connections=5, max_connections=10),
)
async def close(self):
await self._http.aclose()
@property
def is_configured(self) -> bool:
return bool(self._tts_space_url)
async def ping(self) -> None:
if not self.is_configured:
return
try:
await self._http.post(
f"{self._tts_space_url}/synthesize",
json={"text": "hi", "voice": "bf_emma"},
headers={"Content-Type": "application/json"},
timeout=5.0,
)
except Exception:
pass
async def synthesize(self, text: str, voice: str = "bf_emma") -> bytes:
if not self.is_configured:
raise GenerationError("TTS client is not configured")
try:
response = await self._http.post(
f"{self._tts_space_url}/synthesize",
json={"text": text, "voice": voice},
headers={"Content-Type": "application/json"},
)
response.raise_for_status()
audio_bytes = response.content
if not audio_bytes:
raise GenerationError("TTS response was empty")
return audio_bytes
except httpx.TimeoutException as exc:
raise GenerationError("TTS request timed out") from exc
except httpx.HTTPStatusError as exc:
raise GenerationError(
"TTS upstream returned an error",
context={"status_code": exc.response.status_code},
) from exc
except GenerationError:
raise
except Exception as exc:
raise GenerationError("TTS synthesis failed", context={"error": str(exc)}) from exc
async def synthesize_stream(self, text: str, voice: str = "bf_emma"):
text = text.strip()
if not text:
raise GenerationError("TTS request text is empty")
if not self.is_configured:
raise GenerationError("TTS client is not configured")
try:
async with self._http.stream(
"POST",
f"{self._tts_space_url}/synthesize",
json={"text": text, "voice": voice},
headers={"Content-Type": "application/json"},
) as response:
response.raise_for_status()
async for chunk in response.aiter_bytes():
yield chunk
except httpx.TimeoutException as exc:
raise GenerationError("TTS request timed out") from exc
except httpx.HTTPStatusError as exc:
raise GenerationError(
"TTS upstream returned an error",
context={"status_code": exc.response.status_code},
) from exc
except Exception as exc:
raise GenerationError("TTS synthesis stream failed", context={"error": str(exc)}) from exc