From 77f885f91955a530d980dd03c2be5444a4c3e2c2 Mon Sep 17 00:00:00 2001 From: Angelo Giacco Date: Tue, 27 May 2025 00:45:53 +0100 Subject: [PATCH 1/2] feat: add support for text messages, user activity and contextual updates --- .../conversational_ai/conversation.py | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/elevenlabs/conversational_ai/conversation.py b/src/elevenlabs/conversational_ai/conversation.py index 497dc8ea..cd55c94e 100644 --- a/src/elevenlabs/conversational_ai/conversation.py +++ b/src/elevenlabs/conversational_ai/conversation.py @@ -2,18 +2,18 @@ import base64 import json import threading -from typing import Callable, Optional, Awaitable, Union, Any, Literal, Dict, Tuple +from typing import Callable, Optional, Awaitable, Union, Any, Literal import asyncio from concurrent.futures import ThreadPoolExecutor -from enum import Enum +from enum import StrEnum -from websockets.sync.client import connect, Connection +from websockets.sync.client import connect, ClientConnection from websockets.exceptions import ConnectionClosedOK from ..base_client import BaseElevenLabs -class ClientToOrchestratorEvent(str, Enum): +class ClientToOrchestratorEvent(StrEnum): """Event types that can be sent from client to orchestrator.""" # Response to a ping request. PONG = "pong" @@ -44,7 +44,7 @@ def to_dict(self) -> dict: class UserActivityClientToOrchestratorEvent: """Event for registering user activity (ping to prevent timeout).""" - def __init__(self) -> None: + def __init__(self): self.type: Literal[ClientToOrchestratorEvent.USER_ACTIVITY] = ClientToOrchestratorEvent.USER_ACTIVITY def to_dict(self) -> dict: @@ -118,8 +118,8 @@ class ClientTools: ensuring non-blocking operation of the main conversation thread. """ - def __init__(self) -> None: - self.tools: Dict[str, Tuple[Union[Callable[[dict], Any], Callable[[dict], Awaitable[Any]]], bool]] = {} + def __init__(self): + self.tools: dict[str, tuple[Union[Callable[[dict], Any], Callable[[dict], Awaitable[Any]]], bool]] = {} self.lock = threading.Lock() self._loop = None self._thread = None @@ -196,9 +196,6 @@ def execute_tool(self, tool_name: str, parameters: dict, callback: Callable[[dic """ if not self._running.is_set(): raise RuntimeError("ClientTools event loop is not running") - - if self._loop is None: - raise RuntimeError("Event loop is not available") async def _execute_and_callback(): try: @@ -251,7 +248,7 @@ class Conversation: _should_stop: threading.Event _conversation_id: Optional[str] _last_interrupt_id: int - _ws: Optional[Connection] + _ws: Optional[ClientConnection] def __init__( self, @@ -299,10 +296,11 @@ def __init__( self.client_tools.start() self._thread = None - self._ws: Optional[Connection] = None + self._ws: Optional[ClientConnection] = None self._should_stop = threading.Event() self._conversation_id = None self._last_interrupt_id = 0 + self._ws = None def start_session(self): """Starts the conversation session. @@ -493,6 +491,16 @@ def send_response(response): else: pass # Ignore all other message types. + def send_contextual_update(self, text: str): + if not self._ws: + raise RuntimeError("WebSocket is not connected") + + payload = { + "type": "contextual_update", + "text": text, + } + self._ws.send(json.dumps(payload)) + def _get_wss_url(self): base_ws_url = self.client._client_wrapper.get_environment().wss return f"{base_ws_url}/v1/convai/conversation?agent_id={self.agent_id}" From e5f1eaf28182baedb98c265e634025858caf7276 Mon Sep 17 00:00:00 2001 From: Angelo Giacco Date: Thu, 3 Jul 2025 17:40:11 +0100 Subject: [PATCH 2/2] fix contextual update --- .gitignore | 1 + src/elevenlabs/conversational_ai/conversation.py | 10 ---------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index d2e4ca80..367c9b00 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__/ dist/ poetry.toml +venv \ No newline at end of file diff --git a/src/elevenlabs/conversational_ai/conversation.py b/src/elevenlabs/conversational_ai/conversation.py index cd55c94e..6abc28d3 100644 --- a/src/elevenlabs/conversational_ai/conversation.py +++ b/src/elevenlabs/conversational_ai/conversation.py @@ -491,16 +491,6 @@ def send_response(response): else: pass # Ignore all other message types. - def send_contextual_update(self, text: str): - if not self._ws: - raise RuntimeError("WebSocket is not connected") - - payload = { - "type": "contextual_update", - "text": text, - } - self._ws.send(json.dumps(payload)) - def _get_wss_url(self): base_ws_url = self.client._client_wrapper.get_environment().wss return f"{base_ws_url}/v1/convai/conversation?agent_id={self.agent_id}"