Skip to content

Commit 032c8dd

Browse files
authored
feat: add user_id to convai (#587)
1 parent e4405ff commit 032c8dd

File tree

2 files changed

+31
-32
lines changed

2 files changed

+31
-32
lines changed

src/elevenlabs/conversational_ai/conversation.py

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
class ClientToOrchestratorEvent(str, Enum):
1717
"""Event types that can be sent from client to orchestrator."""
18+
1819
# Response to a ping request.
1920
PONG = "pong"
2021
CLIENT_TOOL_RESULT = "client_tool_result"
@@ -29,42 +30,34 @@ class ClientToOrchestratorEvent(str, Enum):
2930

3031
class UserMessageClientToOrchestratorEvent:
3132
"""Event for sending user text messages."""
32-
33+
3334
def __init__(self, text: Optional[str] = None):
3435
self.type: Literal[ClientToOrchestratorEvent.USER_MESSAGE] = ClientToOrchestratorEvent.USER_MESSAGE
3536
self.text = text
36-
37+
3738
def to_dict(self) -> dict:
38-
return {
39-
"type": self.type,
40-
"text": self.text
41-
}
39+
return {"type": self.type, "text": self.text}
4240

4341

4442
class UserActivityClientToOrchestratorEvent:
4543
"""Event for registering user activity (ping to prevent timeout)."""
46-
44+
4745
def __init__(self) -> None:
4846
self.type: Literal[ClientToOrchestratorEvent.USER_ACTIVITY] = ClientToOrchestratorEvent.USER_ACTIVITY
49-
47+
5048
def to_dict(self) -> dict:
51-
return {
52-
"type": self.type
53-
}
49+
return {"type": self.type}
5450

5551

5652
class ContextualUpdateClientToOrchestratorEvent:
5753
"""Event for sending non-interrupting contextual updates to the conversation state."""
58-
54+
5955
def __init__(self, text: str):
6056
self.type: Literal[ClientToOrchestratorEvent.CONTEXTUAL_UPDATE] = ClientToOrchestratorEvent.CONTEXTUAL_UPDATE
6157
self.text = text
62-
58+
6359
def to_dict(self) -> dict:
64-
return {
65-
"type": self.type,
66-
"content": self.text
67-
}
60+
return {"type": self.type, "content": self.text}
6861

6962

7063
class AudioInterface(ABC):
@@ -196,7 +189,7 @@ def execute_tool(self, tool_name: str, parameters: dict, callback: Callable[[dic
196189
"""
197190
if not self._running.is_set():
198191
raise RuntimeError("ClientTools event loop is not running")
199-
192+
200193
if self._loop is None:
201194
raise RuntimeError("Event loop is not available")
202195

@@ -257,6 +250,7 @@ def __init__(
257250
self,
258251
client: BaseElevenLabs,
259252
agent_id: str,
253+
user_id: Optional[str] = None,
260254
*,
261255
requires_auth: bool,
262256
audio_interface: AudioInterface,
@@ -274,6 +268,7 @@ def __init__(
274268
Args:
275269
client: The ElevenLabs client to use for the conversation.
276270
agent_id: The ID of the agent to converse with.
271+
user_id: The ID of the user conversing with the agent.
277272
requires_auth: Whether the agent requires authentication.
278273
audio_interface: The audio interface to use for input and output.
279274
client_tools: The client tools to use for the conversation.
@@ -287,6 +282,7 @@ def __init__(
287282

288283
self.client = client
289284
self.agent_id = agent_id
285+
self.user_id = user_id
290286
self.requires_auth = requires_auth
291287
self.audio_interface = audio_interface
292288
self.callback_agent_response = callback_agent_response
@@ -334,16 +330,16 @@ def wait_for_session_end(self) -> Optional[str]:
334330

335331
def send_user_message(self, text: str):
336332
"""Send a text message from the user to the agent.
337-
333+
338334
Args:
339335
text: The text message to send to the agent.
340-
336+
341337
Raises:
342338
RuntimeError: If the session is not active or websocket is not connected.
343339
"""
344340
if not self._ws:
345341
raise RuntimeError("Session not started or websocket not connected.")
346-
342+
347343
event = UserMessageClientToOrchestratorEvent(text=text)
348344
try:
349345
self._ws.send(json.dumps(event.to_dict()))
@@ -353,15 +349,15 @@ def send_user_message(self, text: str):
353349

354350
def register_user_activity(self):
355351
"""Register user activity to prevent session timeout.
356-
352+
357353
This sends a ping to the orchestrator to reset the timeout timer.
358-
354+
359355
Raises:
360356
RuntimeError: If the session is not active or websocket is not connected.
361357
"""
362358
if not self._ws:
363359
raise RuntimeError("Session not started or websocket not connected.")
364-
360+
365361
event = UserActivityClientToOrchestratorEvent()
366362
try:
367363
self._ws.send(json.dumps(event.to_dict()))
@@ -371,19 +367,19 @@ def register_user_activity(self):
371367

372368
def send_contextual_update(self, text: str):
373369
"""Send a contextual update to the conversation.
374-
370+
375371
Contextual updates are non-interrupting content that is sent to the server
376372
to update the conversation state without directly prompting the agent.
377-
373+
378374
Args:
379375
content: The contextual information to send to the conversation.
380-
376+
381377
Raises:
382378
RuntimeError: If the session is not active or websocket is not connected.
383379
"""
384380
if not self._ws:
385381
raise RuntimeError("Session not started or websocket not connected.")
386-
382+
387383
event = ContextualUpdateClientToOrchestratorEvent(text=text)
388384
try:
389385
self._ws.send(json.dumps(event.to_dict()))
@@ -435,7 +431,7 @@ def input_callback(audio):
435431
except Exception as e:
436432
print(f"Error receiving message: {e}")
437433
self.end_session()
438-
434+
439435
self._ws = None
440436

441437
def _handle_message(self, message, ws):

tests/test_convai.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ def test_conversation_basic_flow():
5252
mock_ws = create_mock_websocket()
5353
mock_client = MagicMock()
5454
agent_response_callback = MagicMock()
55+
test_user_id = "test_user_123"
5556

5657
# Setup the conversation
5758
conversation = Conversation(
5859
client=mock_client,
5960
agent_id=TEST_AGENT_ID,
61+
user_id=test_user_id,
6062
requires_auth=False,
6163
audio_interface=MockAudioInterface(),
6264
callback_agent_response=agent_response_callback,
@@ -86,6 +88,7 @@ def test_conversation_basic_flow():
8688
mock_ws.send.assert_any_call(json.dumps(expected_init_message))
8789
agent_response_callback.assert_called_once_with("Hello there!")
8890
assert conversation._conversation_id == TEST_CONVERSATION_ID
91+
assert conversation.user_id == test_user_id
8992

9093

9194
def test_conversation_with_auth():
@@ -118,6 +121,7 @@ def test_conversation_with_auth():
118121
# Assertions
119122
mock_client.conversational_ai.conversations.get_signed_url.assert_called_once_with(agent_id=TEST_AGENT_ID)
120123

124+
121125
def test_conversation_with_dynamic_variables():
122126
# Mock setup
123127
mock_ws = create_mock_websocket()
@@ -156,14 +160,13 @@ def test_conversation_with_dynamic_variables():
156160
"type": "conversation_initiation_client_data",
157161
"custom_llm_extra_body": {},
158162
"conversation_config_override": {},
159-
"dynamic_variables": {
160-
"name": "angelo"
161-
},
163+
"dynamic_variables": {"name": "angelo"},
162164
}
163165
mock_ws.send.assert_any_call(json.dumps(expected_init_message))
164166
agent_response_callback.assert_called_once_with("Hello there!")
165167
assert conversation._conversation_id == TEST_CONVERSATION_ID
166168

169+
167170
def test_conversation_with_contextual_update():
168171
# Mock setup
169172
mock_ws = create_mock_websocket([])

0 commit comments

Comments
 (0)