From 03bd667f95e11b25c35c9fff4d47f1ccbe81e08e Mon Sep 17 00:00:00 2001 From: Harshita Jain <77115160+harshitajain165@users.noreply.github.com> Date: Mon, 20 Apr 2026 20:45:25 +0530 Subject: [PATCH] Fix Smallest AI TTS WebSocket endpoint URL and remove unsupported flush (#4320) * Fix Smallest AI TTS WebSocket endpoint URL to match API documentation Update base URL from waves-api.smallest.ai to api.smallest.ai and fix path prefix from /api/v1/ to /waves/v1/ per the v4.0.0 docs. * Update keepalive using silent space message instead of unsupported flush --- changelog/4320.fixed.md | 1 + src/pipecat/services/smallest/tts.py | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 changelog/4320.fixed.md diff --git a/changelog/4320.fixed.md b/changelog/4320.fixed.md new file mode 100644 index 000000000..12e49c957 --- /dev/null +++ b/changelog/4320.fixed.md @@ -0,0 +1 @@ +- Fixed `SmallestTTSService` WebSocket endpoint URL to match Smallest AI v4.0.0 API (`wss://waves-api.smallest.ai` → `wss://api.smallest.ai`) and restored keepalive using a silent space message instead of the unsupported flush command. diff --git a/src/pipecat/services/smallest/tts.py b/src/pipecat/services/smallest/tts.py index 33c492c01..8bc22035c 100644 --- a/src/pipecat/services/smallest/tts.py +++ b/src/pipecat/services/smallest/tts.py @@ -125,7 +125,7 @@ class SmallestTTSService(InterruptibleTTSService): self, *, api_key: str, - base_url: str = "wss://waves-api.smallest.ai", + base_url: str = "wss://api.smallest.ai", sample_rate: int | None = None, settings: Settings | None = None, **kwargs, @@ -174,6 +174,10 @@ class SmallestTTSService(InterruptibleTTSService): """ return True + async def flush_audio(self, context_id: str | None = None): + """Flush any pending audio data.""" + logger.trace(f"{self}: flushing audio") + def language_to_service_language(self, language: Language) -> str | None: """Convert a Language enum to Smallest service language format. @@ -217,7 +221,7 @@ class SmallestTTSService(InterruptibleTTSService): def _build_websocket_url(self) -> str: """Build the WebSocket URL from base URL and model.""" - return f"{self._base_url}/api/v1/{self._settings.model}/get_speech/stream" + return f"{self._base_url}/waves/v1/{self._settings.model}/get_speech/stream" async def start(self, frame: StartFrame): """Start the Smallest TTS service. @@ -350,17 +354,15 @@ class SmallestTTSService(InterruptibleTTSService): await self._send_keepalive() async def _send_keepalive(self): - """Send a flush message to keep the connection alive.""" + """Send a silent message to keep the WebSocket connection alive.""" if self._websocket and self._websocket.state is State.OPEN: - msg = {"flush": True} + msg = { + "text": " ", + "voice_id": self._settings.voice, + "language": self._settings.language, + } await self._websocket.send(json.dumps(msg)) - async def flush_audio(self, context_id: str | None = None): - """Flush any pending audio synthesis.""" - if not self._websocket or self._websocket.state is State.CLOSED: - return - await self._get_websocket().send(json.dumps({"flush": True})) - async def _receive_messages(self): """Receive and process messages from the Smallest WebSocket API.""" async for message in self._get_websocket():