lucidicai 2.1.2__py3-none-any.whl → 3.0.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.
- lucidicai/__init__.py +32 -390
- lucidicai/api/client.py +260 -92
- lucidicai/api/resources/__init__.py +16 -1
- lucidicai/api/resources/dataset.py +422 -82
- lucidicai/api/resources/event.py +399 -27
- lucidicai/api/resources/experiment.py +108 -0
- lucidicai/api/resources/feature_flag.py +78 -0
- lucidicai/api/resources/prompt.py +84 -0
- lucidicai/api/resources/session.py +545 -38
- lucidicai/client.py +395 -480
- lucidicai/core/config.py +73 -48
- lucidicai/core/errors.py +3 -3
- lucidicai/sdk/bound_decorators.py +321 -0
- lucidicai/sdk/context.py +20 -2
- lucidicai/sdk/decorators.py +283 -74
- lucidicai/sdk/event.py +538 -36
- lucidicai/sdk/event_builder.py +2 -4
- lucidicai/sdk/features/dataset.py +408 -232
- lucidicai/sdk/features/feature_flag.py +344 -3
- lucidicai/sdk/init.py +50 -279
- lucidicai/sdk/session.py +502 -0
- lucidicai/sdk/shutdown_manager.py +103 -46
- lucidicai/session_obj.py +321 -0
- lucidicai/telemetry/context_capture_processor.py +13 -6
- lucidicai/telemetry/extract.py +60 -63
- lucidicai/telemetry/litellm_bridge.py +3 -44
- lucidicai/telemetry/lucidic_exporter.py +143 -131
- lucidicai/telemetry/openai_agents_instrumentor.py +2 -2
- lucidicai/telemetry/openai_patch.py +7 -6
- lucidicai/telemetry/telemetry_manager.py +183 -0
- lucidicai/telemetry/utils/model_pricing.py +21 -30
- lucidicai/telemetry/utils/provider.py +77 -0
- lucidicai/utils/images.py +30 -14
- lucidicai/utils/queue.py +2 -2
- lucidicai/utils/serialization.py +27 -0
- {lucidicai-2.1.2.dist-info → lucidicai-3.0.0.dist-info}/METADATA +1 -1
- {lucidicai-2.1.2.dist-info → lucidicai-3.0.0.dist-info}/RECORD +39 -30
- {lucidicai-2.1.2.dist-info → lucidicai-3.0.0.dist-info}/WHEEL +0 -0
- {lucidicai-2.1.2.dist-info → lucidicai-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,23 +1,360 @@
|
|
|
1
1
|
"""Session resource API operations."""
|
|
2
|
-
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
3
6
|
|
|
4
7
|
from ..client import HttpClient
|
|
5
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ...client import LucidicAI
|
|
11
|
+
from ...session_obj import Session
|
|
12
|
+
from ...core.config import SDKConfig
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("Lucidic")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _truncate_id(id_str: Optional[str]) -> str:
|
|
18
|
+
"""Truncate ID for logging."""
|
|
19
|
+
if not id_str:
|
|
20
|
+
return "None"
|
|
21
|
+
return f"{id_str[:8]}..." if len(id_str) > 8 else id_str
|
|
22
|
+
|
|
6
23
|
|
|
7
24
|
class SessionResource:
|
|
8
25
|
"""Handle session-related API operations."""
|
|
9
|
-
|
|
10
|
-
def __init__(
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
http: HttpClient,
|
|
30
|
+
client: "LucidicAI",
|
|
31
|
+
config: "SDKConfig",
|
|
32
|
+
production: bool = False,
|
|
33
|
+
):
|
|
11
34
|
"""Initialize session resource.
|
|
12
|
-
|
|
35
|
+
|
|
13
36
|
Args:
|
|
14
37
|
http: HTTP client instance
|
|
38
|
+
client: Parent LucidicAI client
|
|
39
|
+
config: SDK configuration
|
|
40
|
+
production: Whether to suppress errors in production mode
|
|
15
41
|
"""
|
|
16
42
|
self.http = http
|
|
17
|
-
|
|
18
|
-
|
|
43
|
+
self._client = client
|
|
44
|
+
self._config = config
|
|
45
|
+
self._production = production
|
|
46
|
+
|
|
47
|
+
# ==================== High-Level Session Methods ====================
|
|
48
|
+
|
|
49
|
+
def create(
|
|
50
|
+
self,
|
|
51
|
+
session_name: Optional[str] = None,
|
|
52
|
+
session_id: Optional[str] = None,
|
|
53
|
+
task: Optional[str] = None,
|
|
54
|
+
tags: Optional[List[str]] = None,
|
|
55
|
+
experiment_id: Optional[str] = None,
|
|
56
|
+
datasetitem_id: Optional[str] = None,
|
|
57
|
+
evaluators: Optional[List[str]] = None,
|
|
58
|
+
auto_end: Optional[bool] = None,
|
|
59
|
+
production_monitoring: bool = False,
|
|
60
|
+
) -> "Session":
|
|
19
61
|
"""Create a new session.
|
|
20
|
-
|
|
62
|
+
|
|
63
|
+
Sessions track a unit of work and can be used as context managers.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
session_name: Human-readable name for the session.
|
|
67
|
+
session_id: Optional custom session ID. Auto-generated if not provided.
|
|
68
|
+
task: Task description for the session.
|
|
69
|
+
tags: List of tags for filtering/grouping.
|
|
70
|
+
experiment_id: Link session to an experiment.
|
|
71
|
+
datasetitem_id: Link session to a dataset item.
|
|
72
|
+
evaluators: List of evaluator names to run.
|
|
73
|
+
auto_end: Override client's auto_end setting for this session.
|
|
74
|
+
production_monitoring: Enable lightweight production monitoring.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
A Session object that can be used as a context manager.
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
with client.sessions.create(session_name="My Session") as session:
|
|
81
|
+
# Do work
|
|
82
|
+
pass
|
|
83
|
+
"""
|
|
84
|
+
# Late imports to avoid circular dependencies
|
|
85
|
+
from ...session_obj import Session
|
|
86
|
+
from ...core.errors import LucidicError
|
|
87
|
+
from ...sdk.shutdown_manager import ShutdownManager, SessionState
|
|
88
|
+
|
|
89
|
+
if not self._client.is_valid:
|
|
90
|
+
if self._production:
|
|
91
|
+
# Return a dummy session in production mode
|
|
92
|
+
return Session(
|
|
93
|
+
client=self._client,
|
|
94
|
+
session_id=session_id or str(uuid.uuid4()),
|
|
95
|
+
session_name=session_name,
|
|
96
|
+
auto_end=False,
|
|
97
|
+
)
|
|
98
|
+
raise LucidicError("Client is not properly configured")
|
|
99
|
+
|
|
100
|
+
# Use client's auto_end by default
|
|
101
|
+
if auto_end is None:
|
|
102
|
+
auto_end = self._config.auto_end
|
|
103
|
+
|
|
104
|
+
# Generate session ID if not provided
|
|
105
|
+
real_session_id = session_id or str(uuid.uuid4())
|
|
106
|
+
|
|
107
|
+
# Build session parameters
|
|
108
|
+
session_params: Dict[str, Any] = {
|
|
109
|
+
"session_id": real_session_id,
|
|
110
|
+
"session_name": session_name or "Unnamed Session",
|
|
111
|
+
"agent_id": self._config.agent_id,
|
|
112
|
+
}
|
|
113
|
+
if task:
|
|
114
|
+
session_params["task"] = task
|
|
115
|
+
if tags:
|
|
116
|
+
session_params["tags"] = tags
|
|
117
|
+
if experiment_id:
|
|
118
|
+
session_params["experiment_id"] = experiment_id
|
|
119
|
+
if datasetitem_id:
|
|
120
|
+
session_params["datasetitem_id"] = datasetitem_id
|
|
121
|
+
if evaluators:
|
|
122
|
+
session_params["evaluators"] = evaluators
|
|
123
|
+
if production_monitoring:
|
|
124
|
+
session_params["production_monitoring"] = True
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
# Create via API
|
|
128
|
+
response = self.create_session(session_params)
|
|
129
|
+
real_session_id = response.get("session_id", real_session_id)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
if self._production:
|
|
132
|
+
logger.error(f"[SessionResource] Failed to create session: {e}")
|
|
133
|
+
else:
|
|
134
|
+
raise
|
|
135
|
+
|
|
136
|
+
# Create Session object
|
|
137
|
+
session = Session(
|
|
138
|
+
client=self._client,
|
|
139
|
+
session_id=real_session_id,
|
|
140
|
+
session_name=session_name,
|
|
141
|
+
auto_end=auto_end,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Bind session context immediately
|
|
145
|
+
session._bind_context()
|
|
146
|
+
|
|
147
|
+
# Track session
|
|
148
|
+
with self._client._session_lock:
|
|
149
|
+
self._client._sessions[real_session_id] = session
|
|
150
|
+
|
|
151
|
+
# Register with shutdown manager for auto-end
|
|
152
|
+
if auto_end:
|
|
153
|
+
shutdown_manager = ShutdownManager()
|
|
154
|
+
state = SessionState(
|
|
155
|
+
session_id=real_session_id,
|
|
156
|
+
http_client=self._client._resources,
|
|
157
|
+
auto_end=auto_end,
|
|
158
|
+
)
|
|
159
|
+
shutdown_manager.register_session(real_session_id, state)
|
|
160
|
+
|
|
161
|
+
logger.debug(f"[SessionResource] Created session {real_session_id[:8]}...")
|
|
162
|
+
return session
|
|
163
|
+
|
|
164
|
+
async def acreate(
|
|
165
|
+
self,
|
|
166
|
+
session_name: Optional[str] = None,
|
|
167
|
+
session_id: Optional[str] = None,
|
|
168
|
+
task: Optional[str] = None,
|
|
169
|
+
tags: Optional[List[str]] = None,
|
|
170
|
+
experiment_id: Optional[str] = None,
|
|
171
|
+
datasetitem_id: Optional[str] = None,
|
|
172
|
+
evaluators: Optional[List[str]] = None,
|
|
173
|
+
auto_end: Optional[bool] = None,
|
|
174
|
+
production_monitoring: bool = False,
|
|
175
|
+
) -> "Session":
|
|
176
|
+
"""Create a new session (async version).
|
|
177
|
+
|
|
178
|
+
See create() for full documentation.
|
|
179
|
+
"""
|
|
180
|
+
# Late imports to avoid circular dependencies
|
|
181
|
+
from ...session_obj import Session
|
|
182
|
+
from ...core.errors import LucidicError
|
|
183
|
+
from ...sdk.shutdown_manager import ShutdownManager, SessionState
|
|
184
|
+
|
|
185
|
+
if not self._client.is_valid:
|
|
186
|
+
if self._production:
|
|
187
|
+
return Session(
|
|
188
|
+
client=self._client,
|
|
189
|
+
session_id=session_id or str(uuid.uuid4()),
|
|
190
|
+
session_name=session_name,
|
|
191
|
+
auto_end=False,
|
|
192
|
+
)
|
|
193
|
+
raise LucidicError("Client is not properly configured")
|
|
194
|
+
|
|
195
|
+
if auto_end is None:
|
|
196
|
+
auto_end = self._config.auto_end
|
|
197
|
+
|
|
198
|
+
real_session_id = session_id or str(uuid.uuid4())
|
|
199
|
+
|
|
200
|
+
session_params: Dict[str, Any] = {
|
|
201
|
+
"session_id": real_session_id,
|
|
202
|
+
"session_name": session_name or "Unnamed Session",
|
|
203
|
+
"agent_id": self._config.agent_id,
|
|
204
|
+
}
|
|
205
|
+
if task:
|
|
206
|
+
session_params["task"] = task
|
|
207
|
+
if tags:
|
|
208
|
+
session_params["tags"] = tags
|
|
209
|
+
if experiment_id:
|
|
210
|
+
session_params["experiment_id"] = experiment_id
|
|
211
|
+
if datasetitem_id:
|
|
212
|
+
session_params["datasetitem_id"] = datasetitem_id
|
|
213
|
+
if evaluators:
|
|
214
|
+
session_params["evaluators"] = evaluators
|
|
215
|
+
if production_monitoring:
|
|
216
|
+
session_params["production_monitoring"] = True
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
response = await self.acreate_session(session_params)
|
|
220
|
+
real_session_id = response.get("session_id", real_session_id)
|
|
221
|
+
except Exception as e:
|
|
222
|
+
if self._production:
|
|
223
|
+
logger.error(f"[SessionResource] Failed to create session: {e}")
|
|
224
|
+
else:
|
|
225
|
+
raise
|
|
226
|
+
|
|
227
|
+
session = Session(
|
|
228
|
+
client=self._client,
|
|
229
|
+
session_id=real_session_id,
|
|
230
|
+
session_name=session_name,
|
|
231
|
+
auto_end=auto_end,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
session._bind_context()
|
|
235
|
+
|
|
236
|
+
with self._client._session_lock:
|
|
237
|
+
self._client._sessions[real_session_id] = session
|
|
238
|
+
|
|
239
|
+
if auto_end:
|
|
240
|
+
shutdown_manager = ShutdownManager()
|
|
241
|
+
state = SessionState(
|
|
242
|
+
session_id=real_session_id,
|
|
243
|
+
http_client=self._client._resources,
|
|
244
|
+
auto_end=auto_end,
|
|
245
|
+
)
|
|
246
|
+
shutdown_manager.register_session(real_session_id, state)
|
|
247
|
+
|
|
248
|
+
logger.debug(f"[SessionResource] Created async session {real_session_id[:8]}...")
|
|
249
|
+
return session
|
|
250
|
+
|
|
251
|
+
def end(
|
|
252
|
+
self,
|
|
253
|
+
session_id: Optional[str] = None,
|
|
254
|
+
is_successful: Optional[bool] = None,
|
|
255
|
+
is_successful_reason: Optional[str] = None,
|
|
256
|
+
session_eval: Optional[float] = None,
|
|
257
|
+
session_eval_reason: Optional[str] = None,
|
|
258
|
+
) -> None:
|
|
259
|
+
"""End a session.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
session_id: Session ID to end. If None, attempts to use current context session.
|
|
263
|
+
is_successful: Whether the session was successful.
|
|
264
|
+
is_successful_reason: Reason for success/failure status.
|
|
265
|
+
session_eval: Evaluation score (0.0 to 1.0).
|
|
266
|
+
session_eval_reason: Reason for the evaluation score.
|
|
267
|
+
"""
|
|
268
|
+
from ...sdk.shutdown_manager import ShutdownManager
|
|
269
|
+
|
|
270
|
+
if not self._client.is_valid:
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
# If no session_id, try to get from context
|
|
274
|
+
if not session_id:
|
|
275
|
+
from ...sdk.context import current_session_id
|
|
276
|
+
session_id = current_session_id.get(None)
|
|
277
|
+
|
|
278
|
+
if not session_id:
|
|
279
|
+
logger.debug("[SessionResource] No session to end")
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
self.end_session(
|
|
284
|
+
session_id=session_id,
|
|
285
|
+
is_successful=is_successful,
|
|
286
|
+
is_successful_reason=is_successful_reason,
|
|
287
|
+
session_eval=session_eval,
|
|
288
|
+
session_eval_reason=session_eval_reason,
|
|
289
|
+
)
|
|
290
|
+
except Exception as e:
|
|
291
|
+
if self._production:
|
|
292
|
+
logger.error(f"[SessionResource] Failed to end session: {e}")
|
|
293
|
+
else:
|
|
294
|
+
raise
|
|
295
|
+
|
|
296
|
+
# Remove from tracking
|
|
297
|
+
with self._client._session_lock:
|
|
298
|
+
self._client._sessions.pop(session_id, None)
|
|
299
|
+
|
|
300
|
+
# Unregister from shutdown manager
|
|
301
|
+
shutdown_manager = ShutdownManager()
|
|
302
|
+
shutdown_manager.unregister_session(session_id)
|
|
303
|
+
|
|
304
|
+
logger.debug(f"[SessionResource] Ended session {session_id[:8]}...")
|
|
305
|
+
|
|
306
|
+
async def aend(
|
|
307
|
+
self,
|
|
308
|
+
session_id: Optional[str] = None,
|
|
309
|
+
is_successful: Optional[bool] = None,
|
|
310
|
+
is_successful_reason: Optional[str] = None,
|
|
311
|
+
session_eval: Optional[float] = None,
|
|
312
|
+
session_eval_reason: Optional[str] = None,
|
|
313
|
+
) -> None:
|
|
314
|
+
"""End a session (async version).
|
|
315
|
+
|
|
316
|
+
See end() for full documentation.
|
|
317
|
+
"""
|
|
318
|
+
from ...sdk.shutdown_manager import ShutdownManager
|
|
319
|
+
|
|
320
|
+
if not self._client.is_valid:
|
|
321
|
+
return
|
|
322
|
+
|
|
323
|
+
if not session_id:
|
|
324
|
+
from ...sdk.context import current_session_id
|
|
325
|
+
session_id = current_session_id.get(None)
|
|
326
|
+
|
|
327
|
+
if not session_id:
|
|
328
|
+
logger.debug("[SessionResource] No session to end")
|
|
329
|
+
return
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
await self.aend_session(
|
|
333
|
+
session_id=session_id,
|
|
334
|
+
is_successful=is_successful,
|
|
335
|
+
is_successful_reason=is_successful_reason,
|
|
336
|
+
session_eval=session_eval,
|
|
337
|
+
session_eval_reason=session_eval_reason,
|
|
338
|
+
)
|
|
339
|
+
except Exception as e:
|
|
340
|
+
if self._production:
|
|
341
|
+
logger.error(f"[SessionResource] Failed to end session: {e}")
|
|
342
|
+
else:
|
|
343
|
+
raise
|
|
344
|
+
|
|
345
|
+
with self._client._session_lock:
|
|
346
|
+
self._client._sessions.pop(session_id, None)
|
|
347
|
+
|
|
348
|
+
shutdown_manager = ShutdownManager()
|
|
349
|
+
shutdown_manager.unregister_session(session_id)
|
|
350
|
+
|
|
351
|
+
logger.debug(f"[SessionResource] Ended async session {session_id[:8]}...")
|
|
352
|
+
|
|
353
|
+
# ==================== Low-Level HTTP Methods ====================
|
|
354
|
+
|
|
355
|
+
def create_session(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
356
|
+
"""Create a new session via API.
|
|
357
|
+
|
|
21
358
|
Args:
|
|
22
359
|
params: Session parameters including:
|
|
23
360
|
- session_name: Name of the session
|
|
@@ -25,37 +362,63 @@ class SessionResource:
|
|
|
25
362
|
- task: Optional task description
|
|
26
363
|
- tags: Optional tags
|
|
27
364
|
- etc.
|
|
28
|
-
|
|
365
|
+
|
|
29
366
|
Returns:
|
|
30
367
|
Created session data with session_id
|
|
31
368
|
"""
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
369
|
+
session_id = params.get("session_id")
|
|
370
|
+
session_name = params.get("session_name")
|
|
371
|
+
logger.debug(
|
|
372
|
+
f"[Session] create_session() called - "
|
|
373
|
+
f"session_id={_truncate_id(session_id)}, name={session_name!r}, "
|
|
374
|
+
f"params={list(params.keys())}"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
response = self.http.post("initsession", params)
|
|
378
|
+
|
|
379
|
+
resp_session_id = response.get("session_id") if response else None
|
|
380
|
+
logger.debug(
|
|
381
|
+
f"[Session] create_session() response - "
|
|
382
|
+
f"session_id={_truncate_id(resp_session_id)}, response_keys={list(response.keys()) if response else 'None'}"
|
|
383
|
+
)
|
|
384
|
+
return response
|
|
385
|
+
|
|
386
|
+
def get(self, session_id: str) -> Dict[str, Any]:
|
|
35
387
|
"""Get a session by ID.
|
|
36
|
-
|
|
388
|
+
|
|
37
389
|
Args:
|
|
38
390
|
session_id: Session ID
|
|
39
|
-
|
|
391
|
+
|
|
40
392
|
Returns:
|
|
41
393
|
Session data
|
|
42
394
|
"""
|
|
43
395
|
return self.http.get(f"sessions/{session_id}")
|
|
44
|
-
|
|
45
|
-
def
|
|
396
|
+
|
|
397
|
+
def update(self, session_id: str, **updates) -> Dict[str, Any]:
|
|
46
398
|
"""Update an existing session.
|
|
47
|
-
|
|
399
|
+
|
|
48
400
|
Args:
|
|
49
401
|
session_id: Session ID
|
|
50
|
-
updates: Fields to update (task, is_finished, etc.)
|
|
51
|
-
|
|
402
|
+
**updates: Fields to update (task, is_finished, etc.)
|
|
403
|
+
|
|
52
404
|
Returns:
|
|
53
405
|
Updated session data
|
|
54
406
|
"""
|
|
407
|
+
logger.debug(
|
|
408
|
+
f"[Session] update() called - "
|
|
409
|
+
f"session_id={_truncate_id(session_id)}, updates={updates}"
|
|
410
|
+
)
|
|
411
|
+
|
|
55
412
|
# Add session_id to the updates payload
|
|
56
413
|
updates["session_id"] = session_id
|
|
57
|
-
|
|
58
|
-
|
|
414
|
+
response = self.http.put("updatesession", updates)
|
|
415
|
+
|
|
416
|
+
logger.debug(
|
|
417
|
+
f"[Session] update() response - "
|
|
418
|
+
f"session_id={_truncate_id(session_id)}, response_keys={list(response.keys()) if response else 'None'}"
|
|
419
|
+
)
|
|
420
|
+
return response
|
|
421
|
+
|
|
59
422
|
def end_session(
|
|
60
423
|
self,
|
|
61
424
|
session_id: str,
|
|
@@ -64,37 +427,43 @@ class SessionResource:
|
|
|
64
427
|
session_eval: Optional[float] = None,
|
|
65
428
|
session_eval_reason: Optional[str] = None
|
|
66
429
|
) -> Dict[str, Any]:
|
|
67
|
-
"""End a session.
|
|
68
|
-
|
|
430
|
+
"""End a session via API.
|
|
431
|
+
|
|
69
432
|
Args:
|
|
70
433
|
session_id: Session ID
|
|
71
434
|
is_successful: Whether session was successful
|
|
72
435
|
is_successful_reason: Reason for success or failure
|
|
73
436
|
session_eval: Session evaluation score
|
|
74
437
|
session_eval_reason: Reason for evaluation
|
|
75
|
-
|
|
438
|
+
|
|
76
439
|
Returns:
|
|
77
440
|
Final session data
|
|
78
441
|
"""
|
|
79
|
-
|
|
442
|
+
logger.debug(
|
|
443
|
+
f"[Session] end_session() called - "
|
|
444
|
+
f"session_id={_truncate_id(session_id)}, is_successful={is_successful}, "
|
|
445
|
+
f"session_eval={session_eval}"
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
updates: Dict[str, Any] = {
|
|
80
449
|
"is_finished": True
|
|
81
450
|
}
|
|
82
|
-
|
|
451
|
+
|
|
83
452
|
if is_successful is not None:
|
|
84
453
|
updates["is_successful"] = is_successful
|
|
85
|
-
|
|
454
|
+
|
|
86
455
|
if session_eval is not None:
|
|
87
456
|
updates["session_eval"] = session_eval
|
|
88
|
-
|
|
457
|
+
|
|
89
458
|
if session_eval_reason is not None:
|
|
90
459
|
updates["session_eval_reason"] = session_eval_reason
|
|
91
460
|
|
|
92
461
|
if is_successful_reason is not None:
|
|
93
462
|
updates["is_successful_reason"] = is_successful_reason
|
|
94
|
-
|
|
95
|
-
return self.
|
|
96
|
-
|
|
97
|
-
def
|
|
463
|
+
|
|
464
|
+
return self.update(session_id, **updates)
|
|
465
|
+
|
|
466
|
+
def list(
|
|
98
467
|
self,
|
|
99
468
|
agent_id: Optional[str] = None,
|
|
100
469
|
experiment_id: Optional[str] = None,
|
|
@@ -102,25 +471,163 @@ class SessionResource:
|
|
|
102
471
|
offset: int = 0
|
|
103
472
|
) -> Dict[str, Any]:
|
|
104
473
|
"""List sessions with optional filters.
|
|
105
|
-
|
|
474
|
+
|
|
106
475
|
Args:
|
|
107
476
|
agent_id: Filter by agent ID
|
|
108
477
|
experiment_id: Filter by experiment ID
|
|
109
478
|
limit: Maximum number of sessions
|
|
110
479
|
offset: Pagination offset
|
|
111
|
-
|
|
480
|
+
|
|
112
481
|
Returns:
|
|
113
482
|
List of sessions and pagination info
|
|
114
483
|
"""
|
|
115
|
-
params = {
|
|
484
|
+
params: Dict[str, Any] = {
|
|
116
485
|
"limit": limit,
|
|
117
486
|
"offset": offset
|
|
118
487
|
}
|
|
119
|
-
|
|
488
|
+
|
|
120
489
|
if agent_id:
|
|
121
490
|
params["agent_id"] = agent_id
|
|
122
|
-
|
|
491
|
+
|
|
123
492
|
if experiment_id:
|
|
124
493
|
params["experiment_id"] = experiment_id
|
|
125
|
-
|
|
126
|
-
return self.http.get("sessions", params)
|
|
494
|
+
|
|
495
|
+
return self.http.get("sessions", params)
|
|
496
|
+
|
|
497
|
+
# ==================== Asynchronous HTTP Methods ====================
|
|
498
|
+
|
|
499
|
+
async def acreate_session(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
500
|
+
"""Create a new session via API (asynchronous).
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
params: Session parameters
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
Created session data with session_id
|
|
507
|
+
"""
|
|
508
|
+
session_id = params.get("session_id")
|
|
509
|
+
session_name = params.get("session_name")
|
|
510
|
+
logger.debug(
|
|
511
|
+
f"[Session] acreate_session() called - "
|
|
512
|
+
f"session_id={_truncate_id(session_id)}, name={session_name!r}, "
|
|
513
|
+
f"params={list(params.keys())}"
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
response = await self.http.apost("initsession", params)
|
|
517
|
+
|
|
518
|
+
resp_session_id = response.get("session_id") if response else None
|
|
519
|
+
logger.debug(
|
|
520
|
+
f"[Session] acreate_session() response - "
|
|
521
|
+
f"session_id={_truncate_id(resp_session_id)}, response_keys={list(response.keys()) if response else 'None'}"
|
|
522
|
+
)
|
|
523
|
+
return response
|
|
524
|
+
|
|
525
|
+
async def aget(self, session_id: str) -> Dict[str, Any]:
|
|
526
|
+
"""Get a session by ID (asynchronous).
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
session_id: Session ID
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
Session data
|
|
533
|
+
"""
|
|
534
|
+
return await self.http.aget(f"sessions/{session_id}")
|
|
535
|
+
|
|
536
|
+
async def aupdate(self, session_id: str, **updates) -> Dict[str, Any]:
|
|
537
|
+
"""Update an existing session (asynchronous).
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
session_id: Session ID
|
|
541
|
+
**updates: Fields to update (task, is_finished, etc.)
|
|
542
|
+
|
|
543
|
+
Returns:
|
|
544
|
+
Updated session data
|
|
545
|
+
"""
|
|
546
|
+
logger.debug(
|
|
547
|
+
f"[Session] aupdate() called - "
|
|
548
|
+
f"session_id={_truncate_id(session_id)}, updates={updates}"
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
updates["session_id"] = session_id
|
|
552
|
+
response = await self.http.aput("updatesession", updates)
|
|
553
|
+
|
|
554
|
+
logger.debug(
|
|
555
|
+
f"[Session] aupdate() response - "
|
|
556
|
+
f"session_id={_truncate_id(session_id)}, response_keys={list(response.keys()) if response else 'None'}"
|
|
557
|
+
)
|
|
558
|
+
return response
|
|
559
|
+
|
|
560
|
+
async def aend_session(
|
|
561
|
+
self,
|
|
562
|
+
session_id: str,
|
|
563
|
+
is_successful: Optional[bool] = None,
|
|
564
|
+
is_successful_reason: Optional[str] = None,
|
|
565
|
+
session_eval: Optional[float] = None,
|
|
566
|
+
session_eval_reason: Optional[str] = None
|
|
567
|
+
) -> Dict[str, Any]:
|
|
568
|
+
"""End a session via API (asynchronous).
|
|
569
|
+
|
|
570
|
+
Args:
|
|
571
|
+
session_id: Session ID
|
|
572
|
+
is_successful: Whether session was successful
|
|
573
|
+
is_successful_reason: Reason for success or failure
|
|
574
|
+
session_eval: Session evaluation score
|
|
575
|
+
session_eval_reason: Reason for evaluation
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
Final session data
|
|
579
|
+
"""
|
|
580
|
+
logger.debug(
|
|
581
|
+
f"[Session] aend_session() called - "
|
|
582
|
+
f"session_id={_truncate_id(session_id)}, is_successful={is_successful}, "
|
|
583
|
+
f"session_eval={session_eval}"
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
updates: Dict[str, Any] = {
|
|
587
|
+
"is_finished": True
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if is_successful is not None:
|
|
591
|
+
updates["is_successful"] = is_successful
|
|
592
|
+
|
|
593
|
+
if session_eval is not None:
|
|
594
|
+
updates["session_eval"] = session_eval
|
|
595
|
+
|
|
596
|
+
if session_eval_reason is not None:
|
|
597
|
+
updates["session_eval_reason"] = session_eval_reason
|
|
598
|
+
|
|
599
|
+
if is_successful_reason is not None:
|
|
600
|
+
updates["is_successful_reason"] = is_successful_reason
|
|
601
|
+
|
|
602
|
+
return await self.aupdate(session_id, **updates)
|
|
603
|
+
|
|
604
|
+
async def alist(
|
|
605
|
+
self,
|
|
606
|
+
agent_id: Optional[str] = None,
|
|
607
|
+
experiment_id: Optional[str] = None,
|
|
608
|
+
limit: int = 100,
|
|
609
|
+
offset: int = 0
|
|
610
|
+
) -> Dict[str, Any]:
|
|
611
|
+
"""List sessions with optional filters (asynchronous).
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
agent_id: Filter by agent ID
|
|
615
|
+
experiment_id: Filter by experiment ID
|
|
616
|
+
limit: Maximum number of sessions
|
|
617
|
+
offset: Pagination offset
|
|
618
|
+
|
|
619
|
+
Returns:
|
|
620
|
+
List of sessions and pagination info
|
|
621
|
+
"""
|
|
622
|
+
params: Dict[str, Any] = {
|
|
623
|
+
"limit": limit,
|
|
624
|
+
"offset": offset
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if agent_id:
|
|
628
|
+
params["agent_id"] = agent_id
|
|
629
|
+
|
|
630
|
+
if experiment_id:
|
|
631
|
+
params["experiment_id"] = experiment_id
|
|
632
|
+
|
|
633
|
+
return await self.http.aget("sessions", params)
|