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.
- lucidicai/__init__.py +367 -899
- lucidicai/api/__init__.py +1 -0
- lucidicai/api/client.py +218 -0
- lucidicai/api/resources/__init__.py +1 -0
- lucidicai/api/resources/dataset.py +192 -0
- lucidicai/api/resources/event.py +88 -0
- lucidicai/api/resources/session.py +126 -0
- lucidicai/core/__init__.py +1 -0
- lucidicai/core/config.py +223 -0
- lucidicai/core/errors.py +60 -0
- lucidicai/core/types.py +35 -0
- lucidicai/sdk/__init__.py +1 -0
- lucidicai/sdk/context.py +231 -0
- lucidicai/sdk/decorators.py +187 -0
- lucidicai/sdk/error_boundary.py +299 -0
- lucidicai/sdk/event.py +126 -0
- lucidicai/sdk/event_builder.py +304 -0
- lucidicai/sdk/features/__init__.py +1 -0
- lucidicai/sdk/features/dataset.py +605 -0
- lucidicai/sdk/features/feature_flag.py +383 -0
- lucidicai/sdk/init.py +361 -0
- lucidicai/sdk/shutdown_manager.py +302 -0
- lucidicai/telemetry/context_bridge.py +82 -0
- lucidicai/telemetry/context_capture_processor.py +25 -9
- lucidicai/telemetry/litellm_bridge.py +20 -24
- lucidicai/telemetry/lucidic_exporter.py +99 -60
- lucidicai/telemetry/openai_patch.py +295 -0
- lucidicai/telemetry/openai_uninstrument.py +87 -0
- lucidicai/telemetry/telemetry_init.py +16 -1
- lucidicai/telemetry/utils/model_pricing.py +278 -0
- lucidicai/utils/__init__.py +1 -0
- lucidicai/utils/images.py +337 -0
- lucidicai/utils/logger.py +168 -0
- lucidicai/utils/queue.py +393 -0
- {lucidicai-2.0.2.dist-info → lucidicai-2.1.1.dist-info}/METADATA +1 -1
- {lucidicai-2.0.2.dist-info → lucidicai-2.1.1.dist-info}/RECORD +38 -9
- {lucidicai-2.0.2.dist-info → lucidicai-2.1.1.dist-info}/WHEEL +0 -0
- {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."""
|