Skip to content

Commit bd607c4

Browse files
committed
Fix issue with conversation when no auth is required
1 parent 01da892 commit bd607c4

3 files changed

Lines changed: 113 additions & 12 deletions

File tree

src/elevenlabs/conversational_ai/conversation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,8 @@ def __init__(
302302
self._last_interrupt_id = 0
303303

304304
def _get_wss_url(self):
305-
base_ws_url = self.client._client_wrapper.get_environment().wss
305+
base_http_url = self.client._client_wrapper.get_base_url()
306+
base_ws_url = base_http_url.replace("https://", "wss://").replace("http://", "ws://")
306307
return f"{base_ws_url}/v1/convai/conversation?agent_id={self.agent_id}&source=python_sdk&version={__version__}"
307308

308309
def _get_signed_url(self):

tests/test_async_convai.py

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def create_mock_async_websocket(messages=None):
4949

5050
# Create an iterator
5151
message_iter = iter(json_messages)
52-
52+
5353
async def mock_recv():
5454
try:
5555
return next(message_iter)
@@ -83,7 +83,7 @@ async def test_async_conversation_basic_flow():
8383
# Run the test
8484
with patch("elevenlabs.conversational_ai.conversation.websockets.connect") as mock_connect:
8585
mock_connect.return_value.__aenter__.return_value = mock_ws
86-
86+
8787
await conversation.start_session()
8888

8989
# Wait a bit for the callback to be called
@@ -97,7 +97,7 @@ async def test_async_conversation_basic_flow():
9797
init_messages = [json.loads(call) for call in send_calls if 'conversation_initiation_client_data' in call]
9898
assert len(init_messages) == 1
9999
init_message = init_messages[0]
100-
100+
101101
assert init_message["type"] == "conversation_initiation_client_data"
102102
assert init_message["custom_llm_extra_body"] == {}
103103
assert init_message["conversation_config_override"] == {}
@@ -134,7 +134,7 @@ async def test_async_conversation_with_auth():
134134
# Run the test
135135
with patch("elevenlabs.conversational_ai.conversation.websockets.connect") as mock_connect:
136136
mock_connect.return_value.__aenter__.return_value = mock_ws
137-
137+
138138
await conversation.start_session()
139139
await conversation.end_session()
140140
await conversation.wait_for_session_end()
@@ -166,7 +166,7 @@ async def test_async_conversation_with_dynamic_variables():
166166
# Run the test
167167
with patch("elevenlabs.conversational_ai.conversation.websockets.connect") as mock_connect:
168168
mock_connect.return_value.__aenter__.return_value = mock_ws
169-
169+
170170
await conversation.start_session()
171171

172172
# Wait a bit for the callback to be called
@@ -180,7 +180,7 @@ async def test_async_conversation_with_dynamic_variables():
180180
init_messages = [json.loads(call) for call in send_calls if 'conversation_initiation_client_data' in call]
181181
assert len(init_messages) == 1
182182
init_message = init_messages[0]
183-
183+
184184
assert init_message["type"] == "conversation_initiation_client_data"
185185
assert init_message["custom_llm_extra_body"] == {}
186186
assert init_message["conversation_config_override"] == {}
@@ -320,10 +320,10 @@ async def test_async_conversation_callback_flows():
320320
"audio_event": {"event_id": "789", "audio_base_64": "dGVzdA=="} # "test" in base64
321321
}
322322
]
323-
323+
324324
mock_ws = create_mock_async_websocket(messages)
325325
mock_client = MagicMock()
326-
326+
327327
# Setup callbacks
328328
agent_response_callback = AsyncMock()
329329
agent_response_correction_callback = AsyncMock()
@@ -349,7 +349,7 @@ async def test_async_conversation_callback_flows():
349349
mock_connect.return_value.__aenter__.return_value = mock_ws
350350

351351
await conversation.start_session()
352-
352+
353353
# Wait for callbacks to be processed
354354
await asyncio.sleep(0.2)
355355

@@ -364,3 +364,53 @@ async def test_async_conversation_callback_flows():
364364
end_session_callback.assert_called_once()
365365
assert conversation._conversation_id == TEST_CONVERSATION_ID
366366
assert conversation._last_interrupt_id == 456
367+
368+
369+
@pytest.mark.asyncio
370+
async def test_async_conversation_wss_url_generation_without_get_environment():
371+
372+
from elevenlabs.core.client_wrapper import SyncClientWrapper
373+
374+
# Test with various base URL formats to ensure robustness
375+
test_cases = [
376+
("https://api.elevenlabs.io", "wss://api.elevenlabs.io"),
377+
("https://api.us.elevenlabs.io", "wss://api.us.elevenlabs.io"),
378+
("https://api.eu.residency.elevenlabs.io", "wss://api.eu.residency.elevenlabs.io"),
379+
("http://localhost:8000", "ws://localhost:8000"),
380+
]
381+
382+
for base_url, expected_ws_base in test_cases:
383+
# Create a real SyncClientWrapper to ensure it doesn't have get_environment method
384+
mock_client = MagicMock()
385+
mock_client._client_wrapper = SyncClientWrapper(
386+
base_url=base_url,
387+
api_key="test_key",
388+
httpx_client=MagicMock(),
389+
timeout=30.0
390+
)
391+
392+
conversation = AsyncConversation(
393+
client=mock_client,
394+
agent_id=TEST_AGENT_ID,
395+
requires_auth=False,
396+
audio_interface=MockAsyncAudioInterface()
397+
)
398+
399+
try:
400+
wss_url = conversation._get_wss_url()
401+
402+
# Verify the URL is correctly generated
403+
expected_url = f"{expected_ws_base}/v1/convai/conversation?agent_id={TEST_AGENT_ID}&source=python_sdk&version="
404+
assert wss_url.startswith(expected_url), f"URL should start with {expected_url}, got {wss_url}"
405+
406+
# Verify the URL contains version parameter
407+
assert "version=" in wss_url, f"URL should contain version parameter, got {wss_url}"
408+
409+
except AttributeError as e:
410+
if "get_environment" in str(e):
411+
assert False
412+
else:
413+
raise # Re-raise if it's a different AttributeError
414+
415+
except Exception as e:
416+
assert False, f"Unexpected error generating WebSocket URL: {e}"

tests/test_convai.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def test_conversation_basic_flow():
8484
init_messages = [json.loads(call) for call in send_calls if 'conversation_initiation_client_data' in call]
8585
assert len(init_messages) == 1
8686
init_message = init_messages[0]
87-
87+
8888
assert init_message["type"] == "conversation_initiation_client_data"
8989
assert init_message["custom_llm_extra_body"] == {}
9090
assert init_message["conversation_config_override"] == {}
@@ -166,7 +166,7 @@ def test_conversation_with_dynamic_variables():
166166
init_messages = [json.loads(call) for call in send_calls if 'conversation_initiation_client_data' in call]
167167
assert len(init_messages) == 1
168168
init_message = init_messages[0]
169-
169+
170170
assert init_message["type"] == "conversation_initiation_client_data"
171171
assert init_message["custom_llm_extra_body"] == {}
172172
assert init_message["conversation_config_override"] == {}
@@ -206,3 +206,53 @@ def test_conversation_with_contextual_update():
206206
# Assertions
207207
expected = json.dumps({"type": "contextual_update", "text": "User appears to be looking at pricing page"})
208208
mock_ws.send.assert_any_call(expected)
209+
210+
211+
def test_conversation_wss_url_generation_without_get_environment():
212+
213+
from elevenlabs.core.client_wrapper import SyncClientWrapper
214+
215+
# Test with various base URL formats to ensure robustness
216+
test_cases = [
217+
("https://api.elevenlabs.io", "wss://api.elevenlabs.io"),
218+
("https://api.us.elevenlabs.io", "wss://api.us.elevenlabs.io"),
219+
("https://api.eu.residency.elevenlabs.io", "wss://api.eu.residency.elevenlabs.io"),
220+
("http://localhost:8000", "ws://localhost:8000"),
221+
]
222+
223+
for base_url, expected_ws_base in test_cases:
224+
# Create a real SyncClientWrapper to ensure it doesn't have get_environment method
225+
mock_client = MagicMock()
226+
mock_client._client_wrapper = SyncClientWrapper(
227+
base_url=base_url,
228+
api_key="test_key",
229+
httpx_client=MagicMock(),
230+
timeout=30.0
231+
)
232+
233+
# Create conversation with requires_auth=False
234+
conversation = Conversation(
235+
client=mock_client,
236+
agent_id=TEST_AGENT_ID,
237+
requires_auth=False,
238+
audio_interface=MockAudioInterface()
239+
)
240+
241+
try:
242+
wss_url = conversation._get_wss_url()
243+
244+
# Verify the URL is correctly generated
245+
expected_url = f"{expected_ws_base}/v1/convai/conversation?agent_id={TEST_AGENT_ID}&source=python_sdk&version="
246+
assert wss_url.startswith(expected_url), f"URL should start with {expected_url}, got {wss_url}"
247+
248+
# Verify the URL contains version parameter
249+
assert "version=" in wss_url, f"URL should contain version parameter, got {wss_url}"
250+
251+
except AttributeError as e:
252+
if "get_environment" in str(e):
253+
assert False
254+
else:
255+
raise # Re-raise if it's a different AttributeError
256+
257+
except Exception as e:
258+
assert False, f"Unexpected error generating WebSocket URL: {e}"

0 commit comments

Comments
 (0)