From 47baf0dc2c24ada4b92ba206e09b65275b525de5 Mon Sep 17 00:00:00 2001 From: Adir Amsalem Date: Thu, 5 Mar 2026 10:23:41 +0000 Subject: [PATCH] fix: add separate realtime_base_url defaulting to wss://api3.decart.ai Aligns Python SDK with JS SDK which uses separate base URLs for batch (https://api.decart.ai) and realtime (wss://api3.decart.ai). Previously, the Python SDK used the same base_url for both, converting https://api.decart.ai to wss://api.decart.ai for WebSocket connections. Changes: - Add realtime_base_url param to DecartClient (default: wss://api3.decart.ai) - Update all examples and tests to use client.realtime_base_url for realtime - Update lipsync client default to wss://api3.decart.ai --- decart/client.py | 5 +++- decart/lipsync/client.py | 2 +- examples/avatar_live.py | 2 +- examples/realtime_file.py | 2 +- examples/realtime_synthetic.py | 2 +- playground/playground.py | 2 +- tests/test_realtime_unit.py | 46 +++++++++++++++++----------------- 7 files changed, 32 insertions(+), 29 deletions(-) diff --git a/decart/client.py b/decart/client.py index 247976a..a159357 100644 --- a/decart/client.py +++ b/decart/client.py @@ -23,7 +23,8 @@ class DecartClient: Args: api_key: Your Decart API key. Defaults to the DECART_API_KEY environment variable. - base_url: API base URL (defaults to production) + base_url: API base URL for batch/process/queue endpoints (defaults to https://api.decart.ai) + realtime_base_url: WebSocket base URL for realtime streaming (defaults to wss://api3.decart.ai) integration: Optional integration identifier (e.g., "langchain/0.1.0") Example: @@ -52,6 +53,7 @@ def __init__( self, api_key: Optional[str] = None, base_url: str = "https://api.decart.ai", + realtime_base_url: str = "wss://api3.decart.ai", integration: Optional[str] = None, ) -> None: resolved_api_key = api_key or os.environ.get("DECART_API_KEY", "").strip() or None @@ -64,6 +66,7 @@ def __init__( self.api_key = resolved_api_key self.base_url = base_url + self.realtime_base_url = realtime_base_url self.integration = integration self._session: Optional[aiohttp.ClientSession] = None self._queue: Optional[QueueClient] = None diff --git a/decart/lipsync/client.py b/decart/lipsync/client.py index d3bafea..17ccb95 100644 --- a/decart/lipsync/client.py +++ b/decart/lipsync/client.py @@ -29,7 +29,7 @@ class RealtimeLipsyncClient: def __init__( self, api_key: str, - base_url: str = "https://api.decart.ai", + base_url: str = "wss://api3.decart.ai", audio_sample_rate: int = 16000, video_fps: int = VIDEO_FPS, sync_latency: float = 0.0, diff --git a/examples/avatar_live.py b/examples/avatar_live.py index 32b6e6f..dbcbbb1 100644 --- a/examples/avatar_live.py +++ b/examples/avatar_live.py @@ -115,7 +115,7 @@ def on_error(error): try: realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=audio_track, # Can be None if no audio options=RealtimeConnectOptions( diff --git a/examples/realtime_file.py b/examples/realtime_file.py index f58a745..9544613 100644 --- a/examples/realtime_file.py +++ b/examples/realtime_file.py @@ -79,7 +79,7 @@ def on_error(error): from decart.types import ModelState, Prompt realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=player.video, options=RealtimeConnectOptions( diff --git a/examples/realtime_synthetic.py b/examples/realtime_synthetic.py index 71f9062..de93686 100644 --- a/examples/realtime_synthetic.py +++ b/examples/realtime_synthetic.py @@ -105,7 +105,7 @@ def on_error(error): from decart.types import ModelState, Prompt realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=video_track, options=RealtimeConnectOptions( diff --git a/playground/playground.py b/playground/playground.py index 08c6551..a7a25ce 100644 --- a/playground/playground.py +++ b/playground/playground.py @@ -321,7 +321,7 @@ def on_error(error: Exception) -> None: client = DecartClient(api_key=api_key) realtime = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=local_track, options=RealtimeConnectOptions( diff --git a/tests/test_realtime_unit.py b/tests/test_realtime_unit.py index 6cf95a0..9663cef 100644 --- a/tests/test_realtime_unit.py +++ b/tests/test_realtime_unit.py @@ -82,7 +82,7 @@ async def test_realtime_client_creation_with_mock(): from decart.types import ModelState, Prompt realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -143,7 +143,7 @@ def register_prompt_wait(prompt): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -182,7 +182,7 @@ async def test_buffered_events_delivered_after_handler_registration(): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -219,7 +219,7 @@ async def test_realtime_events(): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -281,7 +281,7 @@ def register_prompt_wait(prompt): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -328,7 +328,7 @@ def register_prompt_wait(prompt): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -396,7 +396,7 @@ async def test_avatar_live_connect_with_initial_image(): from decart.types import ModelState realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -444,7 +444,7 @@ async def test_avatar_live_set_image(): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -487,7 +487,7 @@ async def test_set_image_works_for_any_model(): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -524,7 +524,7 @@ async def test_set_image_null_clears_image(): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -565,7 +565,7 @@ async def test_set_image_with_prompt_and_enhance(): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -610,7 +610,7 @@ async def test_avatar_live_set_image_timeout(): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -655,7 +655,7 @@ async def test_avatar_live_set_image_server_error(): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -698,7 +698,7 @@ async def test_set_rejects_when_neither_prompt_nor_image(): from decart.errors import InvalidInputError realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -736,7 +736,7 @@ async def test_set_rejects_empty_prompt(): from decart.errors import InvalidInputError realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -774,7 +774,7 @@ async def test_set_sends_prompt_only(): from decart.realtime.client import SetInput realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -820,7 +820,7 @@ async def test_set_sends_prompt_with_enhance(): from decart.realtime.client import SetInput realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -869,7 +869,7 @@ async def test_set_sends_image_only(): from decart.realtime.client import SetInput realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -919,7 +919,7 @@ async def test_set_sends_prompt_and_image(): from decart.realtime.client import SetInput realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -968,7 +968,7 @@ async def test_set_converts_bytes_image(): from decart.realtime.client import SetInput realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -1017,7 +1017,7 @@ async def test_connect_with_initial_prompt(): from decart.types import ModelState, Prompt realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -1123,7 +1123,7 @@ async def test_connect_without_initial_state_sends_passthrough(): from decart.realtime.types import RealtimeConnectOptions realtime_client = await RealtimeClient.connect( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, local_track=mock_track, options=RealtimeConnectOptions( @@ -1195,7 +1195,7 @@ async def test_subscribe_mode_skips_passthrough(): from decart.realtime.subscribe import SubscribeOptions sub_client = await RealtimeClient.subscribe( - base_url=client.base_url, + base_url=client.realtime_base_url, api_key=client.api_key, options=SubscribeOptions( token=token,