lucidicai 2.0.2__py3-none-any.whl → 2.1.1__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 +367 -899
  2. lucidicai/api/__init__.py +1 -0
  3. lucidicai/api/client.py +218 -0
  4. lucidicai/api/resources/__init__.py +1 -0
  5. lucidicai/api/resources/dataset.py +192 -0
  6. lucidicai/api/resources/event.py +88 -0
  7. lucidicai/api/resources/session.py +126 -0
  8. lucidicai/core/__init__.py +1 -0
  9. lucidicai/core/config.py +223 -0
  10. lucidicai/core/errors.py +60 -0
  11. lucidicai/core/types.py +35 -0
  12. lucidicai/sdk/__init__.py +1 -0
  13. lucidicai/sdk/context.py +231 -0
  14. lucidicai/sdk/decorators.py +187 -0
  15. lucidicai/sdk/error_boundary.py +299 -0
  16. lucidicai/sdk/event.py +126 -0
  17. lucidicai/sdk/event_builder.py +304 -0
  18. lucidicai/sdk/features/__init__.py +1 -0
  19. lucidicai/sdk/features/dataset.py +605 -0
  20. lucidicai/sdk/features/feature_flag.py +383 -0
  21. lucidicai/sdk/init.py +361 -0
  22. lucidicai/sdk/shutdown_manager.py +302 -0
  23. lucidicai/telemetry/context_bridge.py +82 -0
  24. lucidicai/telemetry/context_capture_processor.py +25 -9
  25. lucidicai/telemetry/litellm_bridge.py +20 -24
  26. lucidicai/telemetry/lucidic_exporter.py +99 -60
  27. lucidicai/telemetry/openai_patch.py +295 -0
  28. lucidicai/telemetry/openai_uninstrument.py +87 -0
  29. lucidicai/telemetry/telemetry_init.py +16 -1
  30. lucidicai/telemetry/utils/model_pricing.py +278 -0
  31. lucidicai/utils/__init__.py +1 -0
  32. lucidicai/utils/images.py +337 -0
  33. lucidicai/utils/logger.py +168 -0
  34. lucidicai/utils/queue.py +393 -0
  35. {lucidicai-2.0.2.dist-info → lucidicai-2.1.1.dist-info}/METADATA +1 -1
  36. {lucidicai-2.0.2.dist-info → lucidicai-2.1.1.dist-info}/RECORD +38 -9
  37. {lucidicai-2.0.2.dist-info → lucidicai-2.1.1.dist-info}/WHEEL +0 -0
  38. {lucidicai-2.0.2.dist-info → lucidicai-2.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,304 @@
1
+ """Event builder for flexible parameter handling and field normalization.
2
+
3
+ Inspired by TypeScript SDK's EventBuilder, this module provides a clean way
4
+ to build events from various parameter formats and normalize field names.
5
+ """
6
+ from typing import Any, Dict, Optional, List, Union
7
+ from datetime import datetime, timezone
8
+
9
+
10
+ # field mappings from TypeScript SDK
11
+ FIELD_MAPPINGS = {
12
+ 'completion': 'output',
13
+ 'response': 'output',
14
+ 'prompt': 'messages',
15
+ 'functionName': 'function_name',
16
+ 'args': 'arguments',
17
+ 'returnValue': 'return_value',
18
+ 'result': 'return_value',
19
+ 'stack': 'traceback',
20
+ 'stackTrace': 'traceback',
21
+ 'exception': 'error',
22
+ 'description': 'details',
23
+ 'message': 'details',
24
+ }
25
+
26
+
27
+ class EventBuilder:
28
+ """Build events from flexible parameters with field normalization."""
29
+
30
+ # field sets for different event types
31
+ BASE_FIELDS = {
32
+ 'type', 'event_id', 'parent_event_id', 'occurred_at',
33
+ 'duration', 'tags', 'metadata', 'screenshots'
34
+ }
35
+
36
+ LLM_FIELDS = {
37
+ 'provider', 'model', 'messages', 'prompt', 'output', 'completion',
38
+ 'response', 'input_tokens', 'output_tokens', 'cache', 'cost',
39
+ 'tool_calls', 'thinking', 'status', 'error', 'raw', 'params'
40
+ }
41
+
42
+ FUNCTION_FIELDS = {
43
+ 'function_name', 'functionName', 'arguments', 'args',
44
+ 'return_value', 'returnValue', 'result'
45
+ }
46
+
47
+ ERROR_FIELDS = {
48
+ 'error', 'traceback', 'stack', 'stackTrace', 'exception'
49
+ }
50
+
51
+ GENERIC_FIELDS = {
52
+ 'details', 'description', 'message', 'data'
53
+ }
54
+
55
+ @classmethod
56
+ def build(cls, params: Dict[str, Any]) -> Dict[str, Any]:
57
+ """Build a normalized event from flexible parameters.
58
+
59
+ Args:
60
+ params: Flexible event parameters
61
+
62
+ Returns:
63
+ Normalized event request dictionary
64
+ """
65
+ # check if already in strict format
66
+ if cls._is_strict_format(params):
67
+ return params
68
+
69
+ # normalize field names
70
+ normalized = cls._normalize_fields(params)
71
+
72
+ # detect event type if not provided
73
+ event_type = normalized.get('type') or cls._detect_type(normalized)
74
+
75
+ # build event based on type
76
+ if event_type == 'llm_generation':
77
+ return cls._build_llm_event(normalized)
78
+ elif event_type == 'function_call':
79
+ return cls._build_function_event(normalized)
80
+ elif event_type == 'error_traceback':
81
+ return cls._build_error_event(normalized)
82
+ else:
83
+ return cls._build_generic_event(normalized)
84
+
85
+ @classmethod
86
+ def _is_strict_format(cls, params: Dict[str, Any]) -> bool:
87
+ """Check if params are already in strict format."""
88
+ return 'payload' in params and isinstance(params.get('payload'), dict)
89
+
90
+ @classmethod
91
+ def _normalize_fields(cls, params: Dict[str, Any]) -> Dict[str, Any]:
92
+ """Normalize field names using mappings."""
93
+ normalized = {}
94
+ for key, value in params.items():
95
+ canonical = FIELD_MAPPINGS.get(key, key)
96
+ normalized[canonical] = value
97
+ return normalized
98
+
99
+ @classmethod
100
+ def _detect_type(cls, params: Dict[str, Any]) -> str:
101
+ """Detect event type from parameters."""
102
+ # llm generation indicators
103
+ if any(key in params for key in ['provider', 'model', 'messages', 'prompt',
104
+ 'input_tokens', 'output_tokens']):
105
+ return 'llm_generation'
106
+
107
+ # function call indicators
108
+ if 'function_name' in params or ('arguments' in params and 'error' not in params):
109
+ return 'function_call'
110
+
111
+ # error traceback indicators
112
+ if any(key in params for key in ['error', 'traceback', 'stack', 'exception']):
113
+ return 'error_traceback'
114
+
115
+ return 'generic'
116
+
117
+ @classmethod
118
+ def _extract_base_params(cls, params: Dict[str, Any]) -> Dict[str, Any]:
119
+ """Extract base event parameters."""
120
+ base = {}
121
+
122
+ # map common base fields
123
+ if 'event_id' in params:
124
+ base['client_event_id'] = params['event_id']
125
+ if 'parent_event_id' in params:
126
+ base['client_parent_event_id'] = params['parent_event_id']
127
+ if 'session_id' in params:
128
+ base['session_id'] = params['session_id']
129
+ if 'occurred_at' in params:
130
+ base['occurred_at'] = params['occurred_at']
131
+ else:
132
+ base['occurred_at'] = datetime.now(timezone.utc).isoformat()
133
+ if 'duration' in params:
134
+ base['duration'] = params['duration']
135
+ if 'tags' in params:
136
+ base['tags'] = params['tags']
137
+ if 'metadata' in params:
138
+ base['metadata'] = params['metadata']
139
+ if 'screenshots' in params:
140
+ base['screenshots'] = params['screenshots']
141
+
142
+ return base
143
+
144
+ @classmethod
145
+ def _extract_misc_fields(cls, params: Dict[str, Any], known_fields: set) -> Optional[Dict[str, Any]]:
146
+ """Extract miscellaneous fields not in known sets."""
147
+ misc = {}
148
+ all_known = known_fields | cls.BASE_FIELDS
149
+
150
+ for key, value in params.items():
151
+ if key not in all_known and value is not None:
152
+ misc[key] = value
153
+
154
+ return misc if misc else None
155
+
156
+ @classmethod
157
+ def _build_llm_event(cls, params: Dict[str, Any]) -> Dict[str, Any]:
158
+ """Build LLM generation event."""
159
+ base = cls._extract_base_params(params)
160
+ base['type'] = 'llm_generation'
161
+
162
+ # build payload
163
+ payload = {
164
+ 'request': {
165
+ 'provider': params.get('provider', 'unknown'),
166
+ 'model': params.get('model', 'unknown'),
167
+ },
168
+ 'response': {},
169
+ 'usage': {}
170
+ }
171
+
172
+ # handle messages/prompt
173
+ if 'messages' in params:
174
+ payload['request']['messages'] = params['messages']
175
+ elif 'prompt' in params:
176
+ # convert prompt to messages format
177
+ payload['request']['messages'] = [{'role': 'user', 'content': params['prompt']}]
178
+
179
+ # request params
180
+ if 'params' in params:
181
+ payload['request']['params'] = params['params']
182
+
183
+ # response fields
184
+ if 'output' in params:
185
+ payload['response']['output'] = params['output']
186
+ if 'tool_calls' in params:
187
+ payload['response']['tool_calls'] = params['tool_calls']
188
+ if 'thinking' in params:
189
+ payload['response']['thinking'] = params['thinking']
190
+ if 'raw' in params:
191
+ payload['response']['raw'] = params['raw']
192
+
193
+ # usage fields
194
+ if 'input_tokens' in params:
195
+ payload['usage']['input_tokens'] = params['input_tokens']
196
+ if 'output_tokens' in params:
197
+ payload['usage']['output_tokens'] = params['output_tokens']
198
+ if 'cache' in params:
199
+ payload['usage']['cache'] = params['cache']
200
+ if 'cost' in params:
201
+ payload['usage']['cost'] = params['cost']
202
+
203
+ # status and error
204
+ if 'status' in params:
205
+ payload['status'] = params['status']
206
+ if 'error' in params:
207
+ payload['error'] = str(params['error'])
208
+
209
+ # misc fields
210
+ misc = cls._extract_misc_fields(params, cls.LLM_FIELDS)
211
+ if misc:
212
+ payload['misc'] = misc
213
+
214
+ base['payload'] = payload
215
+ return base
216
+
217
+ @classmethod
218
+ def _build_function_event(cls, params: Dict[str, Any]) -> Dict[str, Any]:
219
+ """Build function call event."""
220
+ base = cls._extract_base_params(params)
221
+ base['type'] = 'function_call'
222
+
223
+ payload = {
224
+ 'function_name': params.get('function_name', 'unknown')
225
+ }
226
+
227
+ if 'arguments' in params:
228
+ payload['arguments'] = params['arguments']
229
+ if 'return_value' in params:
230
+ payload['return_value'] = params['return_value']
231
+ if 'error' in params:
232
+ payload['error'] = params['error']
233
+
234
+ # misc fields
235
+ misc = cls._extract_misc_fields(params, cls.FUNCTION_FIELDS)
236
+ if misc:
237
+ payload['misc'] = misc
238
+
239
+ base['payload'] = payload
240
+ return base
241
+
242
+ @classmethod
243
+ def _build_error_event(cls, params: Dict[str, Any]) -> Dict[str, Any]:
244
+ """Build error traceback event."""
245
+ base = cls._extract_base_params(params)
246
+ base['type'] = 'error_traceback'
247
+
248
+ # handle error
249
+ error_str = ''
250
+ traceback_str = ''
251
+
252
+ error_val = params.get('error')
253
+ if isinstance(error_val, Exception):
254
+ error_str = str(error_val)
255
+ import traceback
256
+ traceback_str = traceback.format_exc()
257
+ else:
258
+ error_str = str(error_val or 'Unknown error')
259
+ traceback_str = params.get('traceback', '')
260
+
261
+ payload = {
262
+ 'error': error_str,
263
+ 'traceback': traceback_str
264
+ }
265
+
266
+ # context
267
+ if 'context' in params:
268
+ payload['context'] = params['context']
269
+
270
+ # misc fields
271
+ misc = cls._extract_misc_fields(params, cls.ERROR_FIELDS)
272
+ if misc:
273
+ payload['misc'] = misc
274
+
275
+ base['payload'] = payload
276
+ return base
277
+
278
+ @classmethod
279
+ def _build_generic_event(cls, params: Dict[str, Any]) -> Dict[str, Any]:
280
+ """Build generic event."""
281
+ base = cls._extract_base_params(params)
282
+ base['type'] = params.get('type', 'generic')
283
+
284
+ # build payload from non-base fields
285
+ payload = {}
286
+
287
+ # common generic fields
288
+ if 'details' in params:
289
+ payload['details'] = params['details']
290
+ elif 'data' in params:
291
+ payload['data'] = params['data']
292
+
293
+ # include all other fields in payload
294
+ for key, value in params.items():
295
+ if key not in cls.BASE_FIELDS and key not in payload:
296
+ payload[key] = value
297
+
298
+ # misc fields
299
+ misc = cls._extract_misc_fields(params, cls.GENERIC_FIELDS)
300
+ if misc:
301
+ payload['misc'] = misc
302
+
303
+ base['payload'] = payload
304
+ return base
@@ -0,0 +1 @@
1
+ """SDK features module."""