lucidicai 2.1.3__py3-none-any.whl → 3.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.
- lucidicai/__init__.py +32 -390
- lucidicai/api/client.py +31 -2
- lucidicai/api/resources/__init__.py +16 -1
- lucidicai/api/resources/dataset.py +422 -82
- lucidicai/api/resources/evals.py +209 -0
- 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 +408 -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 +391 -1
- lucidicai/sdk/features/feature_flag.py +344 -3
- lucidicai/sdk/init.py +49 -347
- 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 +27 -11
- lucidicai/utils/serialization.py +27 -0
- {lucidicai-2.1.3.dist-info → lucidicai-3.1.0.dist-info}/METADATA +1 -1
- {lucidicai-2.1.3.dist-info → lucidicai-3.1.0.dist-info}/RECORD +39 -29
- {lucidicai-2.1.3.dist-info → lucidicai-3.1.0.dist-info}/WHEEL +0 -0
- {lucidicai-2.1.3.dist-info → lucidicai-3.1.0.dist-info}/top_level.txt +0 -0
lucidicai/sdk/session.py
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
"""SDK session creation and management."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import threading
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import List, Optional, Set
|
|
6
|
+
from weakref import WeakSet
|
|
7
|
+
|
|
8
|
+
from ..core.config import SDKConfig, set_config
|
|
9
|
+
from ..utils.logger import debug, info, warning, error, truncate_id
|
|
10
|
+
from .context import set_active_session, clear_active_session
|
|
11
|
+
from .shutdown_manager import get_shutdown_manager, SessionState
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Track background threads for flush()
|
|
15
|
+
_background_threads: Set[threading.Thread] = WeakSet()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _prepare_session_config(
|
|
19
|
+
api_key: Optional[str],
|
|
20
|
+
agent_id: Optional[str],
|
|
21
|
+
providers: Optional[List[str]],
|
|
22
|
+
production_monitoring: bool,
|
|
23
|
+
auto_end: bool,
|
|
24
|
+
capture_uncaught: bool,
|
|
25
|
+
) -> SDKConfig:
|
|
26
|
+
"""Prepare and validate SDK configuration.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Validated SDKConfig instance
|
|
30
|
+
"""
|
|
31
|
+
config = SDKConfig.from_env(
|
|
32
|
+
api_key=api_key,
|
|
33
|
+
agent_id=agent_id,
|
|
34
|
+
auto_end=auto_end,
|
|
35
|
+
production_monitoring=production_monitoring
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if providers:
|
|
39
|
+
config.telemetry.providers = providers
|
|
40
|
+
|
|
41
|
+
config.error_handling.capture_uncaught = capture_uncaught
|
|
42
|
+
|
|
43
|
+
# Validate configuration
|
|
44
|
+
errors = config.validate()
|
|
45
|
+
if errors:
|
|
46
|
+
raise ValueError(f"Invalid configuration: {', '.join(errors)}")
|
|
47
|
+
|
|
48
|
+
return config
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _ensure_http_and_resources_initialized(config: SDKConfig) -> None:
|
|
52
|
+
"""Ensure HTTP client and resources are initialized."""
|
|
53
|
+
from .init import get_http, get_resources, set_http, set_resources
|
|
54
|
+
from ..api.client import HttpClient
|
|
55
|
+
from ..api.resources.event import EventResource
|
|
56
|
+
from ..api.resources.session import SessionResource
|
|
57
|
+
from ..api.resources.dataset import DatasetResource
|
|
58
|
+
|
|
59
|
+
# Initialize HTTP client
|
|
60
|
+
if not get_http():
|
|
61
|
+
debug("[SDK] Initializing HTTP client")
|
|
62
|
+
set_http(HttpClient(config))
|
|
63
|
+
|
|
64
|
+
# Initialize resources
|
|
65
|
+
resources = get_resources()
|
|
66
|
+
if not resources:
|
|
67
|
+
http = get_http()
|
|
68
|
+
set_resources({
|
|
69
|
+
'events': EventResource(http),
|
|
70
|
+
'sessions': SessionResource(http),
|
|
71
|
+
'datasets': DatasetResource(http)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _build_session_params(
|
|
76
|
+
session_id: Optional[str],
|
|
77
|
+
session_name: Optional[str],
|
|
78
|
+
agent_id: str,
|
|
79
|
+
task: Optional[str],
|
|
80
|
+
tags: Optional[List],
|
|
81
|
+
experiment_id: Optional[str],
|
|
82
|
+
datasetitem_id: Optional[str],
|
|
83
|
+
evaluators: Optional[List],
|
|
84
|
+
production_monitoring: bool,
|
|
85
|
+
) -> tuple[str, dict]:
|
|
86
|
+
"""Build session parameters for API call.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Tuple of (real_session_id, session_params)
|
|
90
|
+
"""
|
|
91
|
+
# Create or retrieve session
|
|
92
|
+
if session_id:
|
|
93
|
+
real_session_id = session_id
|
|
94
|
+
else:
|
|
95
|
+
real_session_id = str(uuid.uuid4())
|
|
96
|
+
|
|
97
|
+
# Create session via API - only send non-None values
|
|
98
|
+
session_params = {
|
|
99
|
+
'session_id': real_session_id,
|
|
100
|
+
'session_name': session_name or 'Unnamed Session',
|
|
101
|
+
'agent_id': agent_id,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Only add optional fields if they have values
|
|
105
|
+
if task:
|
|
106
|
+
session_params['task'] = task
|
|
107
|
+
if tags:
|
|
108
|
+
session_params['tags'] = tags
|
|
109
|
+
if experiment_id:
|
|
110
|
+
session_params['experiment_id'] = experiment_id
|
|
111
|
+
if datasetitem_id:
|
|
112
|
+
session_params['datasetitem_id'] = datasetitem_id
|
|
113
|
+
if evaluators:
|
|
114
|
+
session_params['evaluators'] = evaluators
|
|
115
|
+
if production_monitoring:
|
|
116
|
+
session_params['production_monitoring'] = production_monitoring
|
|
117
|
+
|
|
118
|
+
return real_session_id, session_params
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _finalize_session(
|
|
122
|
+
real_session_id: str,
|
|
123
|
+
session_name: Optional[str],
|
|
124
|
+
auto_end: bool,
|
|
125
|
+
providers: Optional[List[str]],
|
|
126
|
+
) -> str:
|
|
127
|
+
"""Finalize session setup after API call."""
|
|
128
|
+
from .init import _sdk_state, _initialize_telemetry, get_resources
|
|
129
|
+
|
|
130
|
+
_sdk_state.session_id = real_session_id
|
|
131
|
+
|
|
132
|
+
info(f"[SDK] Session created: {truncate_id(real_session_id)} (name: {session_name or 'Unnamed Session'})")
|
|
133
|
+
|
|
134
|
+
# Set active session in context
|
|
135
|
+
set_active_session(real_session_id)
|
|
136
|
+
|
|
137
|
+
# Register session with shutdown manager
|
|
138
|
+
debug(f"[SDK] Registering session with shutdown manager (auto_end={auto_end})")
|
|
139
|
+
shutdown_manager = get_shutdown_manager()
|
|
140
|
+
session_state = SessionState(
|
|
141
|
+
session_id=real_session_id,
|
|
142
|
+
http_client=get_resources(),
|
|
143
|
+
auto_end=auto_end
|
|
144
|
+
)
|
|
145
|
+
shutdown_manager.register_session(real_session_id, session_state)
|
|
146
|
+
|
|
147
|
+
# Initialize telemetry if providers specified
|
|
148
|
+
if providers:
|
|
149
|
+
debug(f"[SDK] Initializing telemetry for providers: {providers}")
|
|
150
|
+
_initialize_telemetry(providers)
|
|
151
|
+
|
|
152
|
+
return real_session_id
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def create_session(
|
|
156
|
+
session_name: Optional[str] = None,
|
|
157
|
+
session_id: Optional[str] = None,
|
|
158
|
+
api_key: Optional[str] = None,
|
|
159
|
+
agent_id: Optional[str] = None,
|
|
160
|
+
task: Optional[str] = None,
|
|
161
|
+
providers: Optional[List[str]] = None,
|
|
162
|
+
production_monitoring: bool = False,
|
|
163
|
+
experiment_id: Optional[str] = None,
|
|
164
|
+
evaluators: Optional[List] = None,
|
|
165
|
+
tags: Optional[List] = None,
|
|
166
|
+
datasetitem_id: Optional[str] = None,
|
|
167
|
+
masking_function: Optional[callable] = None,
|
|
168
|
+
auto_end: bool = True,
|
|
169
|
+
capture_uncaught: bool = True,
|
|
170
|
+
) -> str:
|
|
171
|
+
"""Create a new Lucidic session (synchronous).
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
session_name: Name for the session
|
|
175
|
+
session_id: Custom session ID (optional)
|
|
176
|
+
api_key: API key (uses env if not provided)
|
|
177
|
+
agent_id: Agent ID (uses env if not provided)
|
|
178
|
+
task: Task description
|
|
179
|
+
providers: List of telemetry providers to instrument
|
|
180
|
+
production_monitoring: Enable production monitoring
|
|
181
|
+
experiment_id: Experiment ID to associate with session
|
|
182
|
+
evaluators: Evaluators to use
|
|
183
|
+
tags: Session tags
|
|
184
|
+
datasetitem_id: Dataset item ID
|
|
185
|
+
masking_function: Function to mask sensitive data
|
|
186
|
+
auto_end: Automatically end session on exit
|
|
187
|
+
capture_uncaught: Capture uncaught exceptions
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Session ID
|
|
191
|
+
|
|
192
|
+
Raises:
|
|
193
|
+
APIKeyVerificationError: If API credentials are invalid
|
|
194
|
+
"""
|
|
195
|
+
from .init import get_resources
|
|
196
|
+
|
|
197
|
+
# Prepare configuration
|
|
198
|
+
config = _prepare_session_config(
|
|
199
|
+
api_key, agent_id, providers, production_monitoring, auto_end, capture_uncaught
|
|
200
|
+
)
|
|
201
|
+
set_config(config)
|
|
202
|
+
|
|
203
|
+
# Ensure HTTP client and resources are initialized
|
|
204
|
+
_ensure_http_and_resources_initialized(config)
|
|
205
|
+
|
|
206
|
+
# Build session parameters
|
|
207
|
+
real_session_id, session_params = _build_session_params(
|
|
208
|
+
session_id, session_name, config.agent_id, task, tags,
|
|
209
|
+
experiment_id, datasetitem_id, evaluators, production_monitoring
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Create session via API (synchronous)
|
|
213
|
+
debug(f"[SDK] Creating session with params: {session_params}")
|
|
214
|
+
session_resource = get_resources()['sessions']
|
|
215
|
+
session_data = session_resource.create_session(session_params)
|
|
216
|
+
|
|
217
|
+
# Use the session_id returned by the backend
|
|
218
|
+
real_session_id = session_data.get('session_id', real_session_id)
|
|
219
|
+
|
|
220
|
+
return _finalize_session(real_session_id, session_name, auto_end, providers)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
async def acreate_session(
|
|
224
|
+
session_name: Optional[str] = None,
|
|
225
|
+
session_id: Optional[str] = None,
|
|
226
|
+
api_key: Optional[str] = None,
|
|
227
|
+
agent_id: Optional[str] = None,
|
|
228
|
+
task: Optional[str] = None,
|
|
229
|
+
providers: Optional[List[str]] = None,
|
|
230
|
+
production_monitoring: bool = False,
|
|
231
|
+
experiment_id: Optional[str] = None,
|
|
232
|
+
evaluators: Optional[List] = None,
|
|
233
|
+
tags: Optional[List] = None,
|
|
234
|
+
datasetitem_id: Optional[str] = None,
|
|
235
|
+
masking_function: Optional[callable] = None,
|
|
236
|
+
auto_end: bool = True,
|
|
237
|
+
capture_uncaught: bool = True,
|
|
238
|
+
) -> str:
|
|
239
|
+
"""Create a new Lucidic session (asynchronous).
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
session_name: Name for the session
|
|
243
|
+
session_id: Custom session ID (optional)
|
|
244
|
+
api_key: API key (uses env if not provided)
|
|
245
|
+
agent_id: Agent ID (uses env if not provided)
|
|
246
|
+
task: Task description
|
|
247
|
+
providers: List of telemetry providers to instrument
|
|
248
|
+
production_monitoring: Enable production monitoring
|
|
249
|
+
experiment_id: Experiment ID to associate with session
|
|
250
|
+
evaluators: Evaluators to use
|
|
251
|
+
tags: Session tags
|
|
252
|
+
datasetitem_id: Dataset item ID
|
|
253
|
+
masking_function: Function to mask sensitive data
|
|
254
|
+
auto_end: Automatically end session on exit
|
|
255
|
+
capture_uncaught: Capture uncaught exceptions
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Session ID
|
|
259
|
+
|
|
260
|
+
Raises:
|
|
261
|
+
APIKeyVerificationError: If API credentials are invalid
|
|
262
|
+
"""
|
|
263
|
+
from .init import get_resources
|
|
264
|
+
|
|
265
|
+
# Prepare configuration
|
|
266
|
+
config = _prepare_session_config(
|
|
267
|
+
api_key, agent_id, providers, production_monitoring, auto_end, capture_uncaught
|
|
268
|
+
)
|
|
269
|
+
set_config(config)
|
|
270
|
+
|
|
271
|
+
# Ensure HTTP client and resources are initialized
|
|
272
|
+
_ensure_http_and_resources_initialized(config)
|
|
273
|
+
|
|
274
|
+
# Build session parameters
|
|
275
|
+
real_session_id, session_params = _build_session_params(
|
|
276
|
+
session_id, session_name, config.agent_id, task, tags,
|
|
277
|
+
experiment_id, datasetitem_id, evaluators, production_monitoring
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Create session via API (asynchronous)
|
|
281
|
+
debug(f"[SDK] Creating session with params: {session_params}")
|
|
282
|
+
session_resource = get_resources()['sessions']
|
|
283
|
+
session_data = await session_resource.acreate_session(session_params)
|
|
284
|
+
|
|
285
|
+
# Use the session_id returned by the backend
|
|
286
|
+
real_session_id = session_data.get('session_id', real_session_id)
|
|
287
|
+
|
|
288
|
+
return _finalize_session(real_session_id, session_name, auto_end, providers)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def emit_session(
|
|
292
|
+
session_name: Optional[str] = None,
|
|
293
|
+
session_id: Optional[str] = None,
|
|
294
|
+
api_key: Optional[str] = None,
|
|
295
|
+
agent_id: Optional[str] = None,
|
|
296
|
+
task: Optional[str] = None,
|
|
297
|
+
providers: Optional[List[str]] = None,
|
|
298
|
+
production_monitoring: bool = False,
|
|
299
|
+
experiment_id: Optional[str] = None,
|
|
300
|
+
evaluators: Optional[List] = None,
|
|
301
|
+
tags: Optional[List] = None,
|
|
302
|
+
datasetitem_id: Optional[str] = None,
|
|
303
|
+
masking_function: Optional[callable] = None,
|
|
304
|
+
auto_end: bool = True,
|
|
305
|
+
capture_uncaught: bool = True,
|
|
306
|
+
) -> str:
|
|
307
|
+
"""Fire-and-forget session creation that returns instantly.
|
|
308
|
+
|
|
309
|
+
This function returns immediately with a session ID, while the actual
|
|
310
|
+
session creation happens in a background thread. Perfect for reducing
|
|
311
|
+
initialization latency.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
session_name: Name for the session
|
|
315
|
+
session_id: Custom session ID (optional)
|
|
316
|
+
api_key: API key (uses env if not provided)
|
|
317
|
+
agent_id: Agent ID (uses env if not provided)
|
|
318
|
+
task: Task description
|
|
319
|
+
providers: List of telemetry providers to instrument
|
|
320
|
+
production_monitoring: Enable production monitoring
|
|
321
|
+
experiment_id: Experiment ID to associate with session
|
|
322
|
+
evaluators: Evaluators to use
|
|
323
|
+
tags: Session tags
|
|
324
|
+
datasetitem_id: Dataset item ID
|
|
325
|
+
masking_function: Function to mask sensitive data
|
|
326
|
+
auto_end: Automatically end session on exit
|
|
327
|
+
capture_uncaught: Capture uncaught exceptions
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Session ID - returned immediately
|
|
331
|
+
"""
|
|
332
|
+
from .init import _sdk_state
|
|
333
|
+
|
|
334
|
+
# Pre-generate session ID for instant return
|
|
335
|
+
real_session_id = session_id or str(uuid.uuid4())
|
|
336
|
+
|
|
337
|
+
# Immediately set session state for subsequent operations
|
|
338
|
+
_sdk_state.session_id = real_session_id
|
|
339
|
+
set_active_session(real_session_id)
|
|
340
|
+
|
|
341
|
+
# Run async session creation in background thread
|
|
342
|
+
def _run():
|
|
343
|
+
try:
|
|
344
|
+
# Create new event loop for this thread
|
|
345
|
+
loop = asyncio.new_event_loop()
|
|
346
|
+
asyncio.set_event_loop(loop)
|
|
347
|
+
try:
|
|
348
|
+
loop.run_until_complete(
|
|
349
|
+
acreate_session(
|
|
350
|
+
session_name=session_name,
|
|
351
|
+
session_id=real_session_id, # Use pre-generated ID
|
|
352
|
+
api_key=api_key,
|
|
353
|
+
agent_id=agent_id,
|
|
354
|
+
task=task,
|
|
355
|
+
providers=providers,
|
|
356
|
+
production_monitoring=production_monitoring,
|
|
357
|
+
experiment_id=experiment_id,
|
|
358
|
+
evaluators=evaluators,
|
|
359
|
+
tags=tags,
|
|
360
|
+
datasetitem_id=datasetitem_id,
|
|
361
|
+
masking_function=masking_function,
|
|
362
|
+
auto_end=auto_end,
|
|
363
|
+
capture_uncaught=capture_uncaught,
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
finally:
|
|
367
|
+
loop.close()
|
|
368
|
+
except Exception as e:
|
|
369
|
+
error(f"[Session] Background emit failed for {truncate_id(real_session_id)}: {e}")
|
|
370
|
+
|
|
371
|
+
thread = threading.Thread(
|
|
372
|
+
target=_run,
|
|
373
|
+
daemon=True,
|
|
374
|
+
name=f"emit-session-{truncate_id(real_session_id)}"
|
|
375
|
+
)
|
|
376
|
+
_background_threads.add(thread)
|
|
377
|
+
thread.start()
|
|
378
|
+
|
|
379
|
+
info(f"[Session] Emitted session {truncate_id(real_session_id)} (name: {session_name or 'Unnamed Session'}, fire-and-forget)")
|
|
380
|
+
return real_session_id
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def flush_sessions(timeout: float = 5.0) -> None:
|
|
384
|
+
"""Wait for all background session creations to complete.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
timeout: Maximum time to wait in seconds (default: 5.0)
|
|
388
|
+
"""
|
|
389
|
+
import time
|
|
390
|
+
|
|
391
|
+
start_time = time.time()
|
|
392
|
+
|
|
393
|
+
# Wait for background threads
|
|
394
|
+
threads = list(_background_threads)
|
|
395
|
+
for thread in threads:
|
|
396
|
+
if thread.is_alive():
|
|
397
|
+
remaining = timeout - (time.time() - start_time)
|
|
398
|
+
if remaining > 0:
|
|
399
|
+
thread.join(timeout=remaining)
|
|
400
|
+
if thread.is_alive():
|
|
401
|
+
warning(f"[Session] Thread {thread.name} did not complete within timeout")
|
|
402
|
+
|
|
403
|
+
debug(f"[Session] Flush completed in {time.time() - start_time:.2f}s")
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def end_session(
|
|
407
|
+
session_id: Optional[str] = None,
|
|
408
|
+
is_successful: Optional[bool] = None,
|
|
409
|
+
is_successful_reason: Optional[str] = None,
|
|
410
|
+
session_eval: Optional[float] = None,
|
|
411
|
+
session_eval_reason: Optional[str] = None,
|
|
412
|
+
) -> None:
|
|
413
|
+
"""End the current or specified session.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
session_id: Session ID to end (uses current if not provided)
|
|
417
|
+
is_successful: Whether session was successful
|
|
418
|
+
is_successful_reason: Reason for success or failure
|
|
419
|
+
session_eval: Session evaluation score
|
|
420
|
+
session_eval_reason: Reason for evaluation
|
|
421
|
+
"""
|
|
422
|
+
from .init import get_session_id, get_resources
|
|
423
|
+
|
|
424
|
+
# Get session ID
|
|
425
|
+
if not session_id:
|
|
426
|
+
session_id = get_session_id()
|
|
427
|
+
|
|
428
|
+
if not session_id:
|
|
429
|
+
warning("[Session] No active session to end")
|
|
430
|
+
return
|
|
431
|
+
|
|
432
|
+
# End session via API
|
|
433
|
+
resources = get_resources()
|
|
434
|
+
if resources and 'sessions' in resources:
|
|
435
|
+
try:
|
|
436
|
+
resources['sessions'].end_session(
|
|
437
|
+
session_id=session_id,
|
|
438
|
+
is_successful=is_successful,
|
|
439
|
+
is_successful_reason=is_successful_reason,
|
|
440
|
+
session_eval=session_eval,
|
|
441
|
+
session_eval_reason=session_eval_reason
|
|
442
|
+
)
|
|
443
|
+
info(f"[Session] Ended session {truncate_id(session_id)}")
|
|
444
|
+
except Exception as e:
|
|
445
|
+
error(f"[Session] Failed to end session {truncate_id(session_id)}: {e}")
|
|
446
|
+
|
|
447
|
+
# Unregister session from shutdown manager
|
|
448
|
+
shutdown_manager = get_shutdown_manager()
|
|
449
|
+
shutdown_manager.unregister_session(session_id)
|
|
450
|
+
|
|
451
|
+
# Clear active session
|
|
452
|
+
clear_active_session()
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
async def aend_session(
|
|
457
|
+
session_id: Optional[str] = None,
|
|
458
|
+
is_successful: Optional[bool] = None,
|
|
459
|
+
is_successful_reason: Optional[str] = None,
|
|
460
|
+
session_eval: Optional[float] = None,
|
|
461
|
+
session_eval_reason: Optional[str] = None,
|
|
462
|
+
) -> None:
|
|
463
|
+
"""End the current or specified session (asynchronous).
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
session_id: Session ID to end (uses current if not provided)
|
|
467
|
+
is_successful: Whether session was successful
|
|
468
|
+
is_successful_reason: Reason for success or failure
|
|
469
|
+
session_eval: Session evaluation score
|
|
470
|
+
session_eval_reason: Reason for evaluation
|
|
471
|
+
"""
|
|
472
|
+
from .init import get_session_id, get_resources
|
|
473
|
+
|
|
474
|
+
# Get session ID
|
|
475
|
+
if not session_id:
|
|
476
|
+
session_id = get_session_id()
|
|
477
|
+
|
|
478
|
+
if not session_id:
|
|
479
|
+
warning("[Session] No active session to end")
|
|
480
|
+
return
|
|
481
|
+
|
|
482
|
+
# End session via API
|
|
483
|
+
resources = get_resources()
|
|
484
|
+
if resources and 'sessions' in resources:
|
|
485
|
+
try:
|
|
486
|
+
await resources['sessions'].aend_session(
|
|
487
|
+
session_id=session_id,
|
|
488
|
+
is_successful=is_successful,
|
|
489
|
+
is_successful_reason=is_successful_reason,
|
|
490
|
+
session_eval=session_eval,
|
|
491
|
+
session_eval_reason=session_eval_reason
|
|
492
|
+
)
|
|
493
|
+
info(f"[Session] Ended session {truncate_id(session_id)}")
|
|
494
|
+
except Exception as e:
|
|
495
|
+
error(f"[Session] Failed to end session {truncate_id(session_id)}: {e}")
|
|
496
|
+
|
|
497
|
+
# Unregister session from shutdown manager
|
|
498
|
+
shutdown_manager = get_shutdown_manager()
|
|
499
|
+
shutdown_manager.unregister_session(session_id)
|
|
500
|
+
|
|
501
|
+
# Clear active session
|
|
502
|
+
clear_active_session()
|