lucidicai 2.1.3__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.
Files changed (38) hide show
  1. lucidicai/__init__.py +32 -390
  2. lucidicai/api/client.py +31 -2
  3. lucidicai/api/resources/__init__.py +16 -1
  4. lucidicai/api/resources/dataset.py +422 -82
  5. lucidicai/api/resources/event.py +399 -27
  6. lucidicai/api/resources/experiment.py +108 -0
  7. lucidicai/api/resources/feature_flag.py +78 -0
  8. lucidicai/api/resources/prompt.py +84 -0
  9. lucidicai/api/resources/session.py +545 -38
  10. lucidicai/client.py +395 -480
  11. lucidicai/core/config.py +73 -48
  12. lucidicai/core/errors.py +3 -3
  13. lucidicai/sdk/bound_decorators.py +321 -0
  14. lucidicai/sdk/context.py +20 -2
  15. lucidicai/sdk/decorators.py +283 -74
  16. lucidicai/sdk/event.py +538 -36
  17. lucidicai/sdk/event_builder.py +2 -4
  18. lucidicai/sdk/features/dataset.py +391 -1
  19. lucidicai/sdk/features/feature_flag.py +344 -3
  20. lucidicai/sdk/init.py +49 -347
  21. lucidicai/sdk/session.py +502 -0
  22. lucidicai/sdk/shutdown_manager.py +103 -46
  23. lucidicai/session_obj.py +321 -0
  24. lucidicai/telemetry/context_capture_processor.py +13 -6
  25. lucidicai/telemetry/extract.py +60 -63
  26. lucidicai/telemetry/litellm_bridge.py +3 -44
  27. lucidicai/telemetry/lucidic_exporter.py +143 -131
  28. lucidicai/telemetry/openai_agents_instrumentor.py +2 -2
  29. lucidicai/telemetry/openai_patch.py +7 -6
  30. lucidicai/telemetry/telemetry_manager.py +183 -0
  31. lucidicai/telemetry/utils/model_pricing.py +21 -30
  32. lucidicai/telemetry/utils/provider.py +77 -0
  33. lucidicai/utils/images.py +27 -11
  34. lucidicai/utils/serialization.py +27 -0
  35. {lucidicai-2.1.3.dist-info → lucidicai-3.0.0.dist-info}/METADATA +1 -1
  36. {lucidicai-2.1.3.dist-info → lucidicai-3.0.0.dist-info}/RECORD +38 -29
  37. {lucidicai-2.1.3.dist-info → lucidicai-3.0.0.dist-info}/WHEEL +0 -0
  38. {lucidicai-2.1.3.dist-info → lucidicai-3.0.0.dist-info}/top_level.txt +0 -0
@@ -1,36 +1,62 @@
1
1
  """Dataset resource API operations."""
2
+ import logging
2
3
  from typing import Any, Dict, List, Optional
3
4
 
4
5
  from ..client import HttpClient
5
6
 
7
+ logger = logging.getLogger("Lucidic")
8
+
6
9
 
7
10
  class DatasetResource:
8
11
  """Handle dataset-related API operations."""
9
12
 
10
- def __init__(self, http: HttpClient):
13
+ def __init__(
14
+ self,
15
+ http: HttpClient,
16
+ agent_id: Optional[str] = None,
17
+ production: bool = False,
18
+ ):
11
19
  """Initialize dataset resource.
12
20
 
13
21
  Args:
14
22
  http: HTTP client instance
23
+ agent_id: Default agent ID for datasets
24
+ production: Whether to suppress errors in production mode
15
25
  """
16
26
  self.http = http
27
+ self._agent_id = agent_id
28
+ self._production = production
29
+
30
+ # ==================== Dataset Methods ====================
17
31
 
18
- def list_datasets(self, agent_id=None):
32
+ def list(self, agent_id: Optional[str] = None) -> Dict[str, Any]:
19
33
  """List all datasets for agent.
20
34
 
21
35
  Args:
22
- agent_id: Optional agent ID to filter by
36
+ agent_id: Optional agent ID to filter by (uses default if not provided)
23
37
 
24
38
  Returns:
25
39
  Dictionary with num_datasets and datasets list
26
40
  """
27
- params = {}
28
- if agent_id:
29
- params["agent_id"] = agent_id
30
- return self.http.get("sdk/datasets", params)
31
-
32
- def create_dataset(self, name, description=None, tags=None,
33
- suggested_flag_config=None, agent_id=None):
41
+ try:
42
+ params = {}
43
+ if agent_id or self._agent_id:
44
+ params["agent_id"] = agent_id or self._agent_id
45
+ return self.http.get("sdk/datasets", params)
46
+ except Exception as e:
47
+ if self._production:
48
+ logger.error(f"[DatasetResource] Failed to list datasets: {e}")
49
+ return {"num_datasets": 0, "datasets": []}
50
+ raise
51
+
52
+ def create(
53
+ self,
54
+ name: str,
55
+ description: Optional[str] = None,
56
+ tags: Optional[List[str]] = None,
57
+ suggested_flag_config: Optional[Dict[str, Any]] = None,
58
+ agent_id: Optional[str] = None,
59
+ ) -> Dict[str, Any]:
34
60
  """Create new dataset.
35
61
 
36
62
  Args:
@@ -38,24 +64,29 @@ class DatasetResource:
38
64
  description: Optional description
39
65
  tags: Optional list of tags
40
66
  suggested_flag_config: Optional flag configuration
41
- agent_id: Optional agent ID
67
+ agent_id: Optional agent ID (uses default if not provided)
42
68
 
43
69
  Returns:
44
70
  Dictionary with dataset_id
45
71
  """
46
- data = {"name": name}
47
- if description is not None:
48
- data["description"] = description
49
- if tags is not None:
50
- data["tags"] = tags
51
- if suggested_flag_config is not None:
52
- data["suggested_flag_config"] = suggested_flag_config
53
- if agent_id is not None:
54
- data["agent_id"] = agent_id
55
- return self.http.post("sdk/datasets/create", data)
56
-
57
- def get_dataset(self, dataset_id):
58
- """Get dataset with all items - uses existing endpoint.
72
+ try:
73
+ data: Dict[str, Any] = {"name": name}
74
+ if description is not None:
75
+ data["description"] = description
76
+ if tags is not None:
77
+ data["tags"] = tags
78
+ if suggested_flag_config is not None:
79
+ data["suggested_flag_config"] = suggested_flag_config
80
+ data["agent_id"] = agent_id or self._agent_id
81
+ return self.http.post("sdk/datasets/create", data)
82
+ except Exception as e:
83
+ if self._production:
84
+ logger.error(f"[DatasetResource] Failed to create dataset: {e}")
85
+ return {}
86
+ raise
87
+
88
+ def get(self, dataset_id: str) -> Dict[str, Any]:
89
+ """Get dataset with all items.
59
90
 
60
91
  Args:
61
92
  dataset_id: Dataset UUID
@@ -63,9 +94,15 @@ class DatasetResource:
63
94
  Returns:
64
95
  Full dataset data including all items
65
96
  """
66
- return self.http.get("getdataset", {"dataset_id": dataset_id})
67
-
68
- def update_dataset(self, dataset_id, **kwargs):
97
+ try:
98
+ return self.http.get("getdataset", {"dataset_id": dataset_id})
99
+ except Exception as e:
100
+ if self._production:
101
+ logger.error(f"[DatasetResource] Failed to get dataset: {e}")
102
+ return {}
103
+ raise
104
+
105
+ def update(self, dataset_id: str, **kwargs) -> Dict[str, Any]:
69
106
  """Update dataset metadata.
70
107
 
71
108
  Args:
@@ -75,11 +112,17 @@ class DatasetResource:
75
112
  Returns:
76
113
  Updated dataset data
77
114
  """
78
- data = {"dataset_id": dataset_id}
79
- data.update(kwargs)
80
- return self.http.put("sdk/datasets/update", data)
81
-
82
- def delete_dataset(self, dataset_id):
115
+ try:
116
+ data = {"dataset_id": dataset_id}
117
+ data.update(kwargs)
118
+ return self.http.put("sdk/datasets/update", data)
119
+ except Exception as e:
120
+ if self._production:
121
+ logger.error(f"[DatasetResource] Failed to update dataset: {e}")
122
+ return {}
123
+ raise
124
+
125
+ def delete(self, dataset_id: str) -> Dict[str, Any]:
83
126
  """Delete dataset and all items.
84
127
 
85
128
  Args:
@@ -88,11 +131,27 @@ class DatasetResource:
88
131
  Returns:
89
132
  Success message
90
133
  """
91
- return self.http.delete("sdk/datasets/delete", {"dataset_id": dataset_id})
92
-
93
- def create_item(self, dataset_id, name, input_data,
94
- expected_output=None, description=None,
95
- tags=None, metadata=None, flag_overrides=None):
134
+ try:
135
+ return self.http.delete("sdk/datasets/delete", {"dataset_id": dataset_id})
136
+ except Exception as e:
137
+ if self._production:
138
+ logger.error(f"[DatasetResource] Failed to delete dataset: {e}")
139
+ return {}
140
+ raise
141
+
142
+ # ==================== Dataset Item Methods ====================
143
+
144
+ def create_item(
145
+ self,
146
+ dataset_id: str,
147
+ name: str,
148
+ input_data: Dict[str, Any],
149
+ expected_output: Optional[Any] = None,
150
+ description: Optional[str] = None,
151
+ tags: Optional[List[str]] = None,
152
+ metadata: Optional[Dict[str, Any]] = None,
153
+ flag_overrides: Optional[Dict[str, Any]] = None,
154
+ ) -> Dict[str, Any]:
96
155
  """Create dataset item.
97
156
 
98
157
  Args:
@@ -108,27 +167,32 @@ class DatasetResource:
108
167
  Returns:
109
168
  Dictionary with datasetitem_id
110
169
  """
111
- data = {
112
- "dataset_id": dataset_id,
113
- "name": name,
114
- "input": input_data
115
- }
116
-
117
- # Add optional fields if provided
118
- if expected_output is not None:
119
- data["expected_output"] = expected_output
120
- if description is not None:
121
- data["description"] = description
122
- if tags is not None:
123
- data["tags"] = tags
124
- if metadata is not None:
125
- data["metadata"] = metadata
126
- if flag_overrides is not None:
127
- data["flag_overrides"] = flag_overrides
128
-
129
- return self.http.post("sdk/datasets/items/create", data)
130
-
131
- def get_item(self, dataset_id, item_id):
170
+ try:
171
+ data: Dict[str, Any] = {
172
+ "dataset_id": dataset_id,
173
+ "name": name,
174
+ "input": input_data
175
+ }
176
+
177
+ if expected_output is not None:
178
+ data["expected_output"] = expected_output
179
+ if description is not None:
180
+ data["description"] = description
181
+ if tags is not None:
182
+ data["tags"] = tags
183
+ if metadata is not None:
184
+ data["metadata"] = metadata
185
+ if flag_overrides is not None:
186
+ data["flag_overrides"] = flag_overrides
187
+
188
+ return self.http.post("sdk/datasets/items/create", data)
189
+ except Exception as e:
190
+ if self._production:
191
+ logger.error(f"[DatasetResource] Failed to create item: {e}")
192
+ return {}
193
+ raise
194
+
195
+ def get_item(self, dataset_id: str, item_id: str) -> Dict[str, Any]:
132
196
  """Get specific dataset item.
133
197
 
134
198
  Args:
@@ -138,12 +202,18 @@ class DatasetResource:
138
202
  Returns:
139
203
  Dataset item data
140
204
  """
141
- return self.http.get("sdk/datasets/items/get", {
142
- "dataset_id": dataset_id,
143
- "datasetitem_id": item_id
144
- })
145
-
146
- def update_item(self, dataset_id, item_id, **kwargs):
205
+ try:
206
+ return self.http.get("sdk/datasets/items/get", {
207
+ "dataset_id": dataset_id,
208
+ "datasetitem_id": item_id
209
+ })
210
+ except Exception as e:
211
+ if self._production:
212
+ logger.error(f"[DatasetResource] Failed to get item: {e}")
213
+ return {}
214
+ raise
215
+
216
+ def update_item(self, dataset_id: str, item_id: str, **kwargs) -> Dict[str, Any]:
147
217
  """Update dataset item.
148
218
 
149
219
  Args:
@@ -154,14 +224,20 @@ class DatasetResource:
154
224
  Returns:
155
225
  Updated item data
156
226
  """
157
- data = {
158
- "dataset_id": dataset_id,
159
- "datasetitem_id": item_id
160
- }
161
- data.update(kwargs)
162
- return self.http.put("sdk/datasets/items/update", data)
163
-
164
- def delete_item(self, dataset_id, item_id):
227
+ try:
228
+ data = {
229
+ "dataset_id": dataset_id,
230
+ "datasetitem_id": item_id
231
+ }
232
+ data.update(kwargs)
233
+ return self.http.put("sdk/datasets/items/update", data)
234
+ except Exception as e:
235
+ if self._production:
236
+ logger.error(f"[DatasetResource] Failed to update item: {e}")
237
+ return {}
238
+ raise
239
+
240
+ def delete_item(self, dataset_id: str, item_id: str) -> Dict[str, Any]:
165
241
  """Delete dataset item.
166
242
 
167
243
  Args:
@@ -171,12 +247,18 @@ class DatasetResource:
171
247
  Returns:
172
248
  Success message
173
249
  """
174
- return self.http.delete("sdk/datasets/items/delete", {
175
- "dataset_id": dataset_id,
176
- "datasetitem_id": item_id
177
- })
178
-
179
- def list_item_sessions(self, dataset_id, item_id):
250
+ try:
251
+ return self.http.delete("sdk/datasets/items/delete", {
252
+ "dataset_id": dataset_id,
253
+ "datasetitem_id": item_id
254
+ })
255
+ except Exception as e:
256
+ if self._production:
257
+ logger.error(f"[DatasetResource] Failed to delete item: {e}")
258
+ return {}
259
+ raise
260
+
261
+ def list_item_sessions(self, dataset_id: str, item_id: str) -> Dict[str, Any]:
180
262
  """List all sessions for a dataset item.
181
263
 
182
264
  Args:
@@ -186,7 +268,265 @@ class DatasetResource:
186
268
  Returns:
187
269
  Dictionary with num_sessions and sessions list
188
270
  """
189
- return self.http.get("sdk/datasets/items/sessions", {
190
- "dataset_id": dataset_id,
191
- "datasetitem_id": item_id
192
- })
271
+ try:
272
+ return self.http.get("sdk/datasets/items/sessions", {
273
+ "dataset_id": dataset_id,
274
+ "datasetitem_id": item_id
275
+ })
276
+ except Exception as e:
277
+ if self._production:
278
+ logger.error(f"[DatasetResource] Failed to list item sessions: {e}")
279
+ return {"num_sessions": 0, "sessions": []}
280
+ raise
281
+
282
+ # ==================== Asynchronous Dataset Methods ====================
283
+
284
+ async def alist(self, agent_id: Optional[str] = None) -> Dict[str, Any]:
285
+ """List all datasets for agent (asynchronous).
286
+
287
+ Args:
288
+ agent_id: Optional agent ID to filter by (uses default if not provided)
289
+
290
+ Returns:
291
+ Dictionary with num_datasets and datasets list
292
+ """
293
+ try:
294
+ params = {}
295
+ if agent_id or self._agent_id:
296
+ params["agent_id"] = agent_id or self._agent_id
297
+ return await self.http.aget("sdk/datasets", params)
298
+ except Exception as e:
299
+ if self._production:
300
+ logger.error(f"[DatasetResource] Failed to list datasets: {e}")
301
+ return {"num_datasets": 0, "datasets": []}
302
+ raise
303
+
304
+ async def acreate(
305
+ self,
306
+ name: str,
307
+ description: Optional[str] = None,
308
+ tags: Optional[List[str]] = None,
309
+ suggested_flag_config: Optional[Dict[str, Any]] = None,
310
+ agent_id: Optional[str] = None,
311
+ ) -> Dict[str, Any]:
312
+ """Create new dataset (asynchronous).
313
+
314
+ Args:
315
+ name: Dataset name (must be unique per agent)
316
+ description: Optional description
317
+ tags: Optional list of tags
318
+ suggested_flag_config: Optional flag configuration
319
+ agent_id: Optional agent ID (uses default if not provided)
320
+
321
+ Returns:
322
+ Dictionary with dataset_id
323
+ """
324
+ try:
325
+ data: Dict[str, Any] = {"name": name}
326
+ if description is not None:
327
+ data["description"] = description
328
+ if tags is not None:
329
+ data["tags"] = tags
330
+ if suggested_flag_config is not None:
331
+ data["suggested_flag_config"] = suggested_flag_config
332
+ data["agent_id"] = agent_id or self._agent_id
333
+ return await self.http.apost("sdk/datasets/create", data)
334
+ except Exception as e:
335
+ if self._production:
336
+ logger.error(f"[DatasetResource] Failed to create dataset: {e}")
337
+ return {}
338
+ raise
339
+
340
+ async def aget(self, dataset_id: str) -> Dict[str, Any]:
341
+ """Get dataset with all items (asynchronous).
342
+
343
+ Args:
344
+ dataset_id: Dataset UUID
345
+
346
+ Returns:
347
+ Full dataset data including all items
348
+ """
349
+ try:
350
+ return await self.http.aget("getdataset", {"dataset_id": dataset_id})
351
+ except Exception as e:
352
+ if self._production:
353
+ logger.error(f"[DatasetResource] Failed to get dataset: {e}")
354
+ return {}
355
+ raise
356
+
357
+ async def aupdate(self, dataset_id: str, **kwargs) -> Dict[str, Any]:
358
+ """Update dataset metadata (asynchronous).
359
+
360
+ Args:
361
+ dataset_id: Dataset UUID
362
+ **kwargs: Fields to update (name, description, tags, suggested_flag_config)
363
+
364
+ Returns:
365
+ Updated dataset data
366
+ """
367
+ try:
368
+ data = {"dataset_id": dataset_id}
369
+ data.update(kwargs)
370
+ return await self.http.aput("sdk/datasets/update", data)
371
+ except Exception as e:
372
+ if self._production:
373
+ logger.error(f"[DatasetResource] Failed to update dataset: {e}")
374
+ return {}
375
+ raise
376
+
377
+ async def adelete(self, dataset_id: str) -> Dict[str, Any]:
378
+ """Delete dataset and all items (asynchronous).
379
+
380
+ Args:
381
+ dataset_id: Dataset UUID
382
+
383
+ Returns:
384
+ Success message
385
+ """
386
+ try:
387
+ return await self.http.adelete("sdk/datasets/delete", {"dataset_id": dataset_id})
388
+ except Exception as e:
389
+ if self._production:
390
+ logger.error(f"[DatasetResource] Failed to delete dataset: {e}")
391
+ return {}
392
+ raise
393
+
394
+ # ==================== Asynchronous Item Methods ====================
395
+
396
+ async def acreate_item(
397
+ self,
398
+ dataset_id: str,
399
+ name: str,
400
+ input_data: Dict[str, Any],
401
+ expected_output: Optional[Any] = None,
402
+ description: Optional[str] = None,
403
+ tags: Optional[List[str]] = None,
404
+ metadata: Optional[Dict[str, Any]] = None,
405
+ flag_overrides: Optional[Dict[str, Any]] = None,
406
+ ) -> Dict[str, Any]:
407
+ """Create dataset item (asynchronous).
408
+
409
+ Args:
410
+ dataset_id: Dataset UUID
411
+ name: Item name
412
+ input_data: Input data dictionary
413
+ expected_output: Optional expected output
414
+ description: Optional description
415
+ tags: Optional list of tags
416
+ metadata: Optional metadata dictionary
417
+ flag_overrides: Optional flag overrides
418
+
419
+ Returns:
420
+ Dictionary with datasetitem_id
421
+ """
422
+ try:
423
+ data: Dict[str, Any] = {
424
+ "dataset_id": dataset_id,
425
+ "name": name,
426
+ "input": input_data
427
+ }
428
+
429
+ if expected_output is not None:
430
+ data["expected_output"] = expected_output
431
+ if description is not None:
432
+ data["description"] = description
433
+ if tags is not None:
434
+ data["tags"] = tags
435
+ if metadata is not None:
436
+ data["metadata"] = metadata
437
+ if flag_overrides is not None:
438
+ data["flag_overrides"] = flag_overrides
439
+
440
+ return await self.http.apost("sdk/datasets/items/create", data)
441
+ except Exception as e:
442
+ if self._production:
443
+ logger.error(f"[DatasetResource] Failed to create item: {e}")
444
+ return {}
445
+ raise
446
+
447
+ async def aget_item(self, dataset_id: str, item_id: str) -> Dict[str, Any]:
448
+ """Get specific dataset item (asynchronous).
449
+
450
+ Args:
451
+ dataset_id: Dataset UUID
452
+ item_id: Item UUID
453
+
454
+ Returns:
455
+ Dataset item data
456
+ """
457
+ try:
458
+ return await self.http.aget("sdk/datasets/items/get", {
459
+ "dataset_id": dataset_id,
460
+ "datasetitem_id": item_id
461
+ })
462
+ except Exception as e:
463
+ if self._production:
464
+ logger.error(f"[DatasetResource] Failed to get item: {e}")
465
+ return {}
466
+ raise
467
+
468
+ async def aupdate_item(self, dataset_id: str, item_id: str, **kwargs) -> Dict[str, Any]:
469
+ """Update dataset item (asynchronous).
470
+
471
+ Args:
472
+ dataset_id: Dataset UUID
473
+ item_id: Item UUID
474
+ **kwargs: Fields to update
475
+
476
+ Returns:
477
+ Updated item data
478
+ """
479
+ try:
480
+ data = {
481
+ "dataset_id": dataset_id,
482
+ "datasetitem_id": item_id
483
+ }
484
+ data.update(kwargs)
485
+ return await self.http.aput("sdk/datasets/items/update", data)
486
+ except Exception as e:
487
+ if self._production:
488
+ logger.error(f"[DatasetResource] Failed to update item: {e}")
489
+ return {}
490
+ raise
491
+
492
+ async def adelete_item(self, dataset_id: str, item_id: str) -> Dict[str, Any]:
493
+ """Delete dataset item (asynchronous).
494
+
495
+ Args:
496
+ dataset_id: Dataset UUID
497
+ item_id: Item UUID
498
+
499
+ Returns:
500
+ Success message
501
+ """
502
+ try:
503
+ return await self.http.adelete("sdk/datasets/items/delete", {
504
+ "dataset_id": dataset_id,
505
+ "datasetitem_id": item_id
506
+ })
507
+ except Exception as e:
508
+ if self._production:
509
+ logger.error(f"[DatasetResource] Failed to delete item: {e}")
510
+ return {}
511
+ raise
512
+
513
+ async def alist_item_sessions(self, dataset_id: str, item_id: str) -> Dict[str, Any]:
514
+ """List all sessions for a dataset item (asynchronous).
515
+
516
+ Args:
517
+ dataset_id: Dataset UUID
518
+ item_id: Item UUID
519
+
520
+ Returns:
521
+ Dictionary with num_sessions and sessions list
522
+ """
523
+ try:
524
+ return await self.http.aget("sdk/datasets/items/sessions", {
525
+ "dataset_id": dataset_id,
526
+ "datasetitem_id": item_id
527
+ })
528
+ except Exception as e:
529
+ if self._production:
530
+ logger.error(f"[DatasetResource] Failed to list item sessions: {e}")
531
+ return {"num_sessions": 0, "sessions": []}
532
+ raise