conversimple-sdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- conversimple/__init__.py +26 -0
- conversimple/agent.py +354 -0
- conversimple/callbacks.py +174 -0
- conversimple/connection.py +359 -0
- conversimple/tools.py +313 -0
- conversimple/utils.py +69 -0
- conversimple_sdk-0.1.0.dist-info/METADATA +377 -0
- conversimple_sdk-0.1.0.dist-info/RECORD +11 -0
- conversimple_sdk-0.1.0.dist-info/WHEEL +5 -0
- conversimple_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- conversimple_sdk-0.1.0.dist-info/top_level.txt +1 -0
conversimple/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Conversimple SDK - Python client library for the Conversational AI Platform.
|
|
3
|
+
|
|
4
|
+
This SDK enables customers to build and deploy AI agents that integrate with
|
|
5
|
+
the Conversimple platform's WebRTC infrastructure and conversation management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .agent import ConversimpleAgent
|
|
9
|
+
from .tools import tool, tool_async
|
|
10
|
+
from .callbacks import (
|
|
11
|
+
ConversationLifecycleEvent,
|
|
12
|
+
ToolCallEvent,
|
|
13
|
+
ErrorEvent,
|
|
14
|
+
ConfigUpdateEvent
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0"
|
|
18
|
+
__all__ = [
|
|
19
|
+
"ConversimpleAgent",
|
|
20
|
+
"tool",
|
|
21
|
+
"tool_async",
|
|
22
|
+
"ConversationLifecycleEvent",
|
|
23
|
+
"ToolCallEvent",
|
|
24
|
+
"ErrorEvent",
|
|
25
|
+
"ConfigUpdateEvent"
|
|
26
|
+
]
|
conversimple/agent.py
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main ConversimpleAgent class for customer workflow integration.
|
|
3
|
+
|
|
4
|
+
Provides WebSocket-based connection to the Conversimple platform with:
|
|
5
|
+
- Tool registration and execution
|
|
6
|
+
- Conversation lifecycle management
|
|
7
|
+
- Real-time event streaming
|
|
8
|
+
- Fault-tolerant connection handling
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
import uuid
|
|
16
|
+
from typing import Dict, List, Optional, Callable, Any, Union
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
import datetime as dt
|
|
19
|
+
|
|
20
|
+
from .connection import WebSocketConnection
|
|
21
|
+
from .tools import ToolRegistry, ToolCall, auto_register_tools
|
|
22
|
+
from .callbacks import CallbackManager
|
|
23
|
+
from .utils import setup_logging
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConversimpleAgent:
|
|
29
|
+
"""
|
|
30
|
+
Main agent class for Conversimple platform integration.
|
|
31
|
+
|
|
32
|
+
Follows the agent session model - one instance per conversation.
|
|
33
|
+
Each agent instance handles a single conversation lifecycle.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
api_key: str,
|
|
39
|
+
customer_id: Optional[str] = None,
|
|
40
|
+
platform_url: str = "ws://localhost:4000/sdk/websocket"
|
|
41
|
+
):
|
|
42
|
+
"""
|
|
43
|
+
Initialize Conversimple agent.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
api_key: Customer authentication token
|
|
47
|
+
customer_id: Customer identifier (derived from API key if not provided)
|
|
48
|
+
platform_url: WebSocket URL for platform connection
|
|
49
|
+
"""
|
|
50
|
+
self.api_key = api_key
|
|
51
|
+
self.customer_id = customer_id or self._derive_customer_id(api_key)
|
|
52
|
+
self.platform_url = platform_url
|
|
53
|
+
|
|
54
|
+
# Core components
|
|
55
|
+
self.connection = WebSocketConnection(
|
|
56
|
+
url=platform_url,
|
|
57
|
+
api_key=api_key,
|
|
58
|
+
customer_id=self.customer_id
|
|
59
|
+
)
|
|
60
|
+
self.tool_registry = ToolRegistry()
|
|
61
|
+
self.callback_manager = CallbackManager()
|
|
62
|
+
|
|
63
|
+
# State management
|
|
64
|
+
self.conversation_id: Optional[str] = None
|
|
65
|
+
self.connection_state = "disconnected"
|
|
66
|
+
self.registered_tools: List[Dict] = []
|
|
67
|
+
self.pending_tool_calls: Dict[str, ToolCall] = {}
|
|
68
|
+
|
|
69
|
+
# Event callbacks
|
|
70
|
+
self.callbacks: Dict[str, Callable] = {}
|
|
71
|
+
|
|
72
|
+
# Setup logging
|
|
73
|
+
setup_logging()
|
|
74
|
+
|
|
75
|
+
def _derive_customer_id(self, api_key: str) -> str:
|
|
76
|
+
"""Derive customer ID from API key if not provided."""
|
|
77
|
+
# In production, this would decode/validate the API key
|
|
78
|
+
# For now, use a hash-based approach
|
|
79
|
+
import hashlib
|
|
80
|
+
return hashlib.md5(api_key.encode()).hexdigest()[:12]
|
|
81
|
+
|
|
82
|
+
async def start(self, conversation_id: Optional[str] = None) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Start the agent and connect to the platform.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
conversation_id: Unique conversation identifier (generated if not provided)
|
|
88
|
+
"""
|
|
89
|
+
self.conversation_id = conversation_id or f"conv_{uuid.uuid4().hex[:8]}"
|
|
90
|
+
|
|
91
|
+
logger.info(f"Starting agent for conversation: {self.conversation_id}")
|
|
92
|
+
|
|
93
|
+
# Set up message handlers
|
|
94
|
+
self.connection.set_message_handler(self._handle_platform_message)
|
|
95
|
+
self.connection.set_connection_handler(self._handle_connection_event)
|
|
96
|
+
|
|
97
|
+
# Connect to platform
|
|
98
|
+
await self.connection.connect()
|
|
99
|
+
self.connection_state = "connected"
|
|
100
|
+
|
|
101
|
+
# Auto-register tools from decorated methods
|
|
102
|
+
auto_register_tools(self)
|
|
103
|
+
|
|
104
|
+
# Store tools for later registration when real conversations start
|
|
105
|
+
# Don't register immediately since we don't have a real conversation ID yet
|
|
106
|
+
await self._register_tools()
|
|
107
|
+
|
|
108
|
+
logger.info(f"Agent started successfully for conversation: {self.conversation_id}")
|
|
109
|
+
|
|
110
|
+
async def stop(self) -> None:
|
|
111
|
+
"""Stop the agent and disconnect from platform."""
|
|
112
|
+
logger.info(f"Stopping agent for conversation: {self.conversation_id}")
|
|
113
|
+
|
|
114
|
+
self.connection_state = "disconnecting"
|
|
115
|
+
await self.connection.disconnect()
|
|
116
|
+
self.connection_state = "disconnected"
|
|
117
|
+
|
|
118
|
+
# Trigger conversation ended callback
|
|
119
|
+
await self.callback_manager.trigger_conversation_ended(self.conversation_id)
|
|
120
|
+
|
|
121
|
+
async def _register_tools(self) -> None:
|
|
122
|
+
"""Store discovered tools for registration when conversations start."""
|
|
123
|
+
tools = self.tool_registry.get_registered_tools()
|
|
124
|
+
|
|
125
|
+
if not tools:
|
|
126
|
+
logger.info("No tools discovered")
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
self.registered_tools = tools
|
|
130
|
+
logger.info(f"Discovered {len(tools)} tools, will register when conversations start")
|
|
131
|
+
|
|
132
|
+
async def _register_conversation_tools(self, conversation_id: str, tools: list) -> None:
|
|
133
|
+
"""Register tools for a specific conversation."""
|
|
134
|
+
message = {
|
|
135
|
+
"conversation_id": conversation_id,
|
|
136
|
+
"tools": tools
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await self.connection.send_message("register_conversation_tools", message)
|
|
140
|
+
logger.info(f"Registered {len(tools)} tools for conversation {conversation_id}")
|
|
141
|
+
|
|
142
|
+
async def _handle_platform_message(self, event: str, payload: Dict) -> None:
|
|
143
|
+
"""Handle incoming messages from the platform."""
|
|
144
|
+
logger.info(f"📨 Received platform message: {event} - {payload}")
|
|
145
|
+
|
|
146
|
+
handlers = {
|
|
147
|
+
"config_update": self._handle_config_update,
|
|
148
|
+
"analytics_update": self._handle_analytics_update,
|
|
149
|
+
"tool_call_request": self._handle_tool_call_request,
|
|
150
|
+
"conversation_lifecycle": self._handle_conversation_lifecycle,
|
|
151
|
+
"hook_event": self._handle_hook_event,
|
|
152
|
+
"connection_warning": self._handle_connection_warning,
|
|
153
|
+
"error_notification": self._handle_error_notification,
|
|
154
|
+
"conversation_ready": self._handle_conversation_ready
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
handler = handlers.get(event)
|
|
158
|
+
if handler:
|
|
159
|
+
try:
|
|
160
|
+
await handler(payload)
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logger.error(f"Error handling {event}: {e}")
|
|
163
|
+
await self._send_error_response(event, str(e))
|
|
164
|
+
else:
|
|
165
|
+
logger.warning(f"Unhandled platform message: {event}")
|
|
166
|
+
|
|
167
|
+
async def _handle_config_update(self, payload: Dict) -> None:
|
|
168
|
+
"""Handle configuration updates from platform."""
|
|
169
|
+
logger.info(f"Configuration updated for customer: {payload.get('customer_id')}")
|
|
170
|
+
await self.callback_manager.trigger_config_update(payload)
|
|
171
|
+
|
|
172
|
+
async def _handle_analytics_update(self, payload: Dict) -> None:
|
|
173
|
+
"""Handle analytics updates from platform."""
|
|
174
|
+
logger.debug(f"Analytics update received: {payload}")
|
|
175
|
+
# Analytics updates are typically fire-and-forget
|
|
176
|
+
|
|
177
|
+
async def _handle_conversation_ready(self, payload: Dict) -> None:
|
|
178
|
+
"""Handle conversation ready events from platform."""
|
|
179
|
+
conversation_id = payload.get('conversation_id')
|
|
180
|
+
customer_id = payload.get('customer_id')
|
|
181
|
+
|
|
182
|
+
logger.info(f"🔧 AGENT_FLOW: Conversation ready event received for {conversation_id}")
|
|
183
|
+
|
|
184
|
+
if not conversation_id:
|
|
185
|
+
logger.error("🔧 AGENT_FLOW: No conversation_id in conversation_ready event")
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
if not self.registered_tools:
|
|
189
|
+
logger.warning("🔧 AGENT_FLOW: No tools available to register for conversation")
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# Register tools for this specific conversation
|
|
193
|
+
logger.info(f"🔧 AGENT_FLOW: Registering {len(self.registered_tools)} tools for conversation {conversation_id}")
|
|
194
|
+
await self._register_conversation_tools(conversation_id, self.registered_tools)
|
|
195
|
+
|
|
196
|
+
# Trigger conversation started callback
|
|
197
|
+
await self.callback_manager.trigger_conversation_started(conversation_id)
|
|
198
|
+
|
|
199
|
+
async def _handle_tool_call_request(self, payload: Dict) -> None:
|
|
200
|
+
"""Handle tool execution requests from platform."""
|
|
201
|
+
call_id = payload.get("call_id")
|
|
202
|
+
tool_name = payload.get("tool_name")
|
|
203
|
+
arguments = payload.get("arguments", {})
|
|
204
|
+
|
|
205
|
+
logger.info(f"Tool call requested: {tool_name} (call_id: {call_id})")
|
|
206
|
+
|
|
207
|
+
if not call_id or not tool_name:
|
|
208
|
+
await self._send_tool_error(call_id or "unknown", "Missing call_id or tool_name")
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
# Create tool call object
|
|
212
|
+
tool_call = ToolCall(
|
|
213
|
+
call_id=call_id,
|
|
214
|
+
tool_name=tool_name,
|
|
215
|
+
arguments=arguments,
|
|
216
|
+
conversation_id=self.conversation_id,
|
|
217
|
+
timeout_seconds=payload.get("timeout_seconds", 30)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Store as pending
|
|
221
|
+
self.pending_tool_calls[call_id] = tool_call
|
|
222
|
+
|
|
223
|
+
# Trigger callback
|
|
224
|
+
await self.callback_manager.trigger_tool_called(tool_call)
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
# Execute the tool
|
|
228
|
+
result = await self.tool_registry.execute_tool(tool_name, arguments)
|
|
229
|
+
|
|
230
|
+
# Send successful result
|
|
231
|
+
await self._send_tool_result(call_id, result)
|
|
232
|
+
|
|
233
|
+
# Trigger completion callback
|
|
234
|
+
await self.callback_manager.trigger_tool_completed(call_id, result)
|
|
235
|
+
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error(f"Tool execution failed for {tool_name}: {e}")
|
|
238
|
+
await self._send_tool_error(call_id, str(e))
|
|
239
|
+
finally:
|
|
240
|
+
# Clean up pending call
|
|
241
|
+
self.pending_tool_calls.pop(call_id, None)
|
|
242
|
+
|
|
243
|
+
async def _handle_conversation_lifecycle(self, payload: Dict) -> None:
|
|
244
|
+
"""Handle conversation lifecycle events."""
|
|
245
|
+
event = payload.get("event")
|
|
246
|
+
conversation_id = payload.get("conversation_id")
|
|
247
|
+
|
|
248
|
+
logger.info(f"🎭 Conversation lifecycle: {event} for {conversation_id}")
|
|
249
|
+
logger.info(f"🎭 Full payload: {payload}")
|
|
250
|
+
|
|
251
|
+
if event == "conversation_started":
|
|
252
|
+
# Auto-register tools for this conversation
|
|
253
|
+
if conversation_id and self.registered_tools:
|
|
254
|
+
logger.info(f"🔧 Auto-registering {len(self.registered_tools)} tools for conversation {conversation_id}")
|
|
255
|
+
try:
|
|
256
|
+
await self._register_conversation_tools(conversation_id, self.registered_tools)
|
|
257
|
+
logger.info(f"✅ Successfully auto-registered tools for conversation {conversation_id}")
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.error(f"❌ Failed to auto-register tools for conversation {conversation_id}: {e}")
|
|
260
|
+
else:
|
|
261
|
+
logger.warning(f"⚠️ Cannot auto-register tools - conversation_id: {conversation_id}, tools available: {bool(self.registered_tools)}")
|
|
262
|
+
|
|
263
|
+
await self.callback_manager.trigger_conversation_started(conversation_id, payload)
|
|
264
|
+
elif event == "conversation_ended":
|
|
265
|
+
logger.info(f"🏁 Conversation ended: {conversation_id}")
|
|
266
|
+
await self.callback_manager.trigger_conversation_ended(conversation_id, payload)
|
|
267
|
+
else:
|
|
268
|
+
logger.warning(f"❓ Unknown lifecycle event: {event}")
|
|
269
|
+
|
|
270
|
+
async def _handle_hook_event(self, payload: Dict) -> None:
|
|
271
|
+
"""Handle hook events from platform."""
|
|
272
|
+
event_type = payload.get("event_type")
|
|
273
|
+
logger.debug(f"Hook event received: {event_type}")
|
|
274
|
+
|
|
275
|
+
# Hook events are typically informational
|
|
276
|
+
# Customers can subscribe to specific hook events if needed
|
|
277
|
+
|
|
278
|
+
async def _handle_connection_warning(self, payload: Dict) -> None:
|
|
279
|
+
"""Handle connection warnings from platform."""
|
|
280
|
+
message = payload.get("message", "Connection warning")
|
|
281
|
+
logger.warning(f"Platform connection warning: {message}")
|
|
282
|
+
|
|
283
|
+
async def _handle_error_notification(self, payload: Dict) -> None:
|
|
284
|
+
"""Handle error notifications from platform."""
|
|
285
|
+
error_type = payload.get("error_type")
|
|
286
|
+
error_message = payload.get("error_message")
|
|
287
|
+
|
|
288
|
+
logger.error(f"Platform error ({error_type}): {error_message}")
|
|
289
|
+
await self.callback_manager.trigger_error(error_type, error_message, payload)
|
|
290
|
+
|
|
291
|
+
async def _handle_connection_event(self, event: str, data: Any = None) -> None:
|
|
292
|
+
"""Handle WebSocket connection events."""
|
|
293
|
+
logger.info(f"Connection event: {event}")
|
|
294
|
+
|
|
295
|
+
if event == "connected":
|
|
296
|
+
self.connection_state = "connected"
|
|
297
|
+
elif event == "disconnected":
|
|
298
|
+
self.connection_state = "disconnected"
|
|
299
|
+
elif event == "error":
|
|
300
|
+
logger.error(f"Connection error: {data}")
|
|
301
|
+
self.connection_state = "error"
|
|
302
|
+
|
|
303
|
+
async def _send_tool_result(self, call_id: str, result: Any) -> None:
|
|
304
|
+
"""Send tool execution result to platform."""
|
|
305
|
+
message = {
|
|
306
|
+
"call_id": call_id,
|
|
307
|
+
"result": result
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
await self.connection.send_message("tool_call_response", message)
|
|
311
|
+
logger.debug(f"Sent tool result for call: {call_id}")
|
|
312
|
+
|
|
313
|
+
async def _send_tool_error(self, call_id: str, error: str) -> None:
|
|
314
|
+
"""Send tool execution error to platform."""
|
|
315
|
+
message = {
|
|
316
|
+
"call_id": call_id,
|
|
317
|
+
"error": {
|
|
318
|
+
"message": error,
|
|
319
|
+
"timestamp": datetime.now(dt.timezone.utc).isoformat()
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
await self.connection.send_message("tool_call_error", message)
|
|
324
|
+
logger.debug(f"Sent tool error for call: {call_id}")
|
|
325
|
+
|
|
326
|
+
async def _send_error_response(self, event: str, error: str) -> None:
|
|
327
|
+
"""Send error response for event handling failure."""
|
|
328
|
+
logger.error(f"Failed to handle event {event}: {error}")
|
|
329
|
+
# Platform will handle this through connection monitoring
|
|
330
|
+
|
|
331
|
+
# Event callback registration methods
|
|
332
|
+
def on_conversation_started(self, callback: Callable[[str], None]) -> None:
|
|
333
|
+
"""Register callback for conversation started events."""
|
|
334
|
+
self.callback_manager.on_conversation_started = callback
|
|
335
|
+
|
|
336
|
+
def on_conversation_ended(self, callback: Callable[[str], None]) -> None:
|
|
337
|
+
"""Register callback for conversation ended events."""
|
|
338
|
+
self.callback_manager.on_conversation_ended = callback
|
|
339
|
+
|
|
340
|
+
def on_tool_called(self, callback: Callable[[ToolCall], None]) -> None:
|
|
341
|
+
"""Register callback for tool call events."""
|
|
342
|
+
self.callback_manager.on_tool_called = callback
|
|
343
|
+
|
|
344
|
+
def on_tool_completed(self, callback: Callable[[str, Any], None]) -> None:
|
|
345
|
+
"""Register callback for tool completion events."""
|
|
346
|
+
self.callback_manager.on_tool_completed = callback
|
|
347
|
+
|
|
348
|
+
def on_error(self, callback: Callable[[str, str, Dict], None]) -> None:
|
|
349
|
+
"""Register callback for error events."""
|
|
350
|
+
self.callback_manager.on_error = callback
|
|
351
|
+
|
|
352
|
+
def on_config_update(self, callback: Callable[[Dict], None]) -> None:
|
|
353
|
+
"""Register callback for configuration updates."""
|
|
354
|
+
self.callback_manager.on_config_update = callback
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Event callbacks and lifecycle management for Conversimple SDK.
|
|
3
|
+
|
|
4
|
+
Provides structured event handling for:
|
|
5
|
+
- Conversation lifecycle events
|
|
6
|
+
- Tool execution events
|
|
7
|
+
- Configuration updates
|
|
8
|
+
- Error notifications
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Dict, Optional, Callable, Any
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
|
|
16
|
+
from .tools import ToolCall
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ConversationLifecycleEvent:
|
|
23
|
+
"""Represents a conversation lifecycle event."""
|
|
24
|
+
event_type: str # "started" or "ended"
|
|
25
|
+
conversation_id: str
|
|
26
|
+
timestamp: datetime
|
|
27
|
+
metadata: Dict[str, Any]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ToolCallEvent:
|
|
32
|
+
"""Represents a tool call event."""
|
|
33
|
+
tool_call: ToolCall
|
|
34
|
+
event_type: str # "called", "completed", "failed"
|
|
35
|
+
timestamp: datetime
|
|
36
|
+
result: Optional[Any] = None
|
|
37
|
+
error: Optional[str] = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ErrorEvent:
|
|
42
|
+
"""Represents an error event."""
|
|
43
|
+
error_type: str
|
|
44
|
+
error_message: str
|
|
45
|
+
conversation_id: Optional[str]
|
|
46
|
+
timestamp: datetime
|
|
47
|
+
details: Dict[str, Any]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class ConfigUpdateEvent:
|
|
52
|
+
"""Represents a configuration update event."""
|
|
53
|
+
customer_id: str
|
|
54
|
+
configuration: Dict[str, Any]
|
|
55
|
+
timestamp: datetime
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CallbackManager:
|
|
59
|
+
"""
|
|
60
|
+
Manages event callbacks for the Conversimple agent.
|
|
61
|
+
|
|
62
|
+
Provides a centralized way to register and trigger callbacks
|
|
63
|
+
for various platform events.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(self):
|
|
67
|
+
# Event callbacks
|
|
68
|
+
self.on_conversation_started: Optional[Callable[[str], None]] = None
|
|
69
|
+
self.on_conversation_ended: Optional[Callable[[str], None]] = None
|
|
70
|
+
self.on_tool_called: Optional[Callable[[ToolCall], None]] = None
|
|
71
|
+
self.on_tool_completed: Optional[Callable[[str, Any], None]] = None
|
|
72
|
+
self.on_error: Optional[Callable[[str, str, Dict], None]] = None
|
|
73
|
+
self.on_config_update: Optional[Callable[[Dict], None]] = None
|
|
74
|
+
|
|
75
|
+
async def trigger_conversation_started(
|
|
76
|
+
self,
|
|
77
|
+
conversation_id: str,
|
|
78
|
+
metadata: Optional[Dict] = None
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Trigger conversation started callback."""
|
|
81
|
+
if self.on_conversation_started:
|
|
82
|
+
try:
|
|
83
|
+
if asyncio.iscoroutinefunction(self.on_conversation_started):
|
|
84
|
+
await self.on_conversation_started(conversation_id)
|
|
85
|
+
else:
|
|
86
|
+
self.on_conversation_started(conversation_id)
|
|
87
|
+
|
|
88
|
+
logger.debug(f"Triggered conversation_started callback for {conversation_id}")
|
|
89
|
+
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(f"Error in conversation_started callback: {e}")
|
|
92
|
+
|
|
93
|
+
async def trigger_conversation_ended(
|
|
94
|
+
self,
|
|
95
|
+
conversation_id: str,
|
|
96
|
+
metadata: Optional[Dict] = None
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Trigger conversation ended callback."""
|
|
99
|
+
if self.on_conversation_ended:
|
|
100
|
+
try:
|
|
101
|
+
if asyncio.iscoroutinefunction(self.on_conversation_ended):
|
|
102
|
+
await self.on_conversation_ended(conversation_id)
|
|
103
|
+
else:
|
|
104
|
+
self.on_conversation_ended(conversation_id)
|
|
105
|
+
|
|
106
|
+
logger.debug(f"Triggered conversation_ended callback for {conversation_id}")
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"Error in conversation_ended callback: {e}")
|
|
110
|
+
|
|
111
|
+
async def trigger_tool_called(self, tool_call: ToolCall) -> None:
|
|
112
|
+
"""Trigger tool called callback."""
|
|
113
|
+
if self.on_tool_called:
|
|
114
|
+
try:
|
|
115
|
+
if asyncio.iscoroutinefunction(self.on_tool_called):
|
|
116
|
+
await self.on_tool_called(tool_call)
|
|
117
|
+
else:
|
|
118
|
+
self.on_tool_called(tool_call)
|
|
119
|
+
|
|
120
|
+
logger.debug(f"Triggered tool_called callback for {tool_call.tool_name}")
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(f"Error in tool_called callback: {e}")
|
|
124
|
+
|
|
125
|
+
async def trigger_tool_completed(self, call_id: str, result: Any) -> None:
|
|
126
|
+
"""Trigger tool completed callback."""
|
|
127
|
+
if self.on_tool_completed:
|
|
128
|
+
try:
|
|
129
|
+
if asyncio.iscoroutinefunction(self.on_tool_completed):
|
|
130
|
+
await self.on_tool_completed(call_id, result)
|
|
131
|
+
else:
|
|
132
|
+
self.on_tool_completed(call_id, result)
|
|
133
|
+
|
|
134
|
+
logger.debug(f"Triggered tool_completed callback for {call_id}")
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"Error in tool_completed callback: {e}")
|
|
138
|
+
|
|
139
|
+
async def trigger_error(
|
|
140
|
+
self,
|
|
141
|
+
error_type: str,
|
|
142
|
+
error_message: str,
|
|
143
|
+
details: Dict[str, Any]
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Trigger error callback."""
|
|
146
|
+
if self.on_error:
|
|
147
|
+
try:
|
|
148
|
+
if asyncio.iscoroutinefunction(self.on_error):
|
|
149
|
+
await self.on_error(error_type, error_message, details)
|
|
150
|
+
else:
|
|
151
|
+
self.on_error(error_type, error_message, details)
|
|
152
|
+
|
|
153
|
+
logger.debug(f"Triggered error callback for {error_type}")
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.error(f"Error in error callback: {e}")
|
|
157
|
+
|
|
158
|
+
async def trigger_config_update(self, config: Dict[str, Any]) -> None:
|
|
159
|
+
"""Trigger configuration update callback."""
|
|
160
|
+
if self.on_config_update:
|
|
161
|
+
try:
|
|
162
|
+
if asyncio.iscoroutinefunction(self.on_config_update):
|
|
163
|
+
await self.on_config_update(config)
|
|
164
|
+
else:
|
|
165
|
+
self.on_config_update(config)
|
|
166
|
+
|
|
167
|
+
logger.debug("Triggered config_update callback")
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"Error in config_update callback: {e}")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# Import asyncio for callback detection
|
|
174
|
+
import asyncio
|