lucidicai 2.0.2__tar.gz → 2.1.1__tar.gz
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-2.0.2 → lucidicai-2.1.1}/PKG-INFO +1 -1
- {lucidicai-2.0.2 → lucidicai-2.1.1}/README.md +3 -3
- lucidicai-2.1.1/lucidicai/__init__.py +409 -0
- lucidicai-2.1.1/lucidicai/api/__init__.py +1 -0
- lucidicai-2.1.1/lucidicai/api/client.py +218 -0
- lucidicai-2.1.1/lucidicai/api/resources/__init__.py +1 -0
- lucidicai-2.1.1/lucidicai/api/resources/dataset.py +192 -0
- lucidicai-2.1.1/lucidicai/api/resources/event.py +88 -0
- lucidicai-2.1.1/lucidicai/api/resources/session.py +126 -0
- lucidicai-2.1.1/lucidicai/core/__init__.py +1 -0
- lucidicai-2.1.1/lucidicai/core/config.py +223 -0
- {lucidicai-2.0.2/lucidicai → lucidicai-2.1.1/lucidicai/core}/errors.py +16 -16
- lucidicai-2.1.1/lucidicai/core/types.py +35 -0
- lucidicai-2.1.1/lucidicai/sdk/__init__.py +1 -0
- {lucidicai-2.0.2/lucidicai → lucidicai-2.1.1/lucidicai/sdk}/context.py +93 -6
- {lucidicai-2.0.2/lucidicai → lucidicai-2.1.1/lucidicai/sdk}/decorators.py +68 -27
- lucidicai-2.1.1/lucidicai/sdk/error_boundary.py +299 -0
- lucidicai-2.1.1/lucidicai/sdk/event.py +126 -0
- lucidicai-2.1.1/lucidicai/sdk/event_builder.py +304 -0
- lucidicai-2.1.1/lucidicai/sdk/features/__init__.py +1 -0
- lucidicai-2.1.1/lucidicai/sdk/features/dataset.py +605 -0
- {lucidicai-2.0.2/lucidicai → lucidicai-2.1.1/lucidicai/sdk/features}/feature_flag.py +93 -54
- lucidicai-2.1.1/lucidicai/sdk/init.py +361 -0
- lucidicai-2.1.1/lucidicai/sdk/shutdown_manager.py +302 -0
- lucidicai-2.1.1/lucidicai/telemetry/context_bridge.py +82 -0
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai/telemetry/context_capture_processor.py +25 -9
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai/telemetry/litellm_bridge.py +20 -24
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai/telemetry/lucidic_exporter.py +99 -60
- lucidicai-2.1.1/lucidicai/telemetry/openai_patch.py +295 -0
- lucidicai-2.1.1/lucidicai/telemetry/openai_uninstrument.py +87 -0
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai/telemetry/telemetry_init.py +16 -1
- lucidicai-2.1.1/lucidicai/utils/__init__.py +1 -0
- lucidicai-2.1.1/lucidicai/utils/images.py +337 -0
- lucidicai-2.1.1/lucidicai/utils/logger.py +168 -0
- lucidicai-2.1.1/lucidicai/utils/queue.py +393 -0
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai.egg-info/PKG-INFO +1 -1
- lucidicai-2.1.1/lucidicai.egg-info/SOURCES.txt +45 -0
- {lucidicai-2.0.2 → lucidicai-2.1.1}/setup.py +1 -1
- lucidicai-2.0.2/lucidicai/__init__.py +0 -941
- lucidicai-2.0.2/lucidicai/client.py +0 -513
- lucidicai-2.0.2/lucidicai/constants.py +0 -29
- lucidicai-2.0.2/lucidicai/dataset.py +0 -114
- lucidicai-2.0.2/lucidicai/event.py +0 -53
- lucidicai-2.0.2/lucidicai/event_queue.py +0 -466
- lucidicai-2.0.2/lucidicai/image_upload.py +0 -111
- lucidicai-2.0.2/lucidicai/lru.py +0 -19
- lucidicai-2.0.2/lucidicai/session.py +0 -51
- lucidicai-2.0.2/lucidicai/singleton.py +0 -53
- lucidicai-2.0.2/lucidicai/streaming.py +0 -225
- lucidicai-2.0.2/lucidicai/telemetry/utils/image_storage.py +0 -45
- lucidicai-2.0.2/lucidicai/telemetry/utils/text_storage.py +0 -53
- lucidicai-2.0.2/lucidicai/telemetry/utils/universal_image_interceptor.py +0 -365
- lucidicai-2.0.2/lucidicai.egg-info/SOURCES.txt +0 -34
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai/telemetry/__init__.py +0 -0
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai/telemetry/extract.py +0 -0
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai/telemetry/openai_agents_instrumentor.py +0 -0
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai/telemetry/utils/__init__.py +0 -0
- {lucidicai-2.0.2/lucidicai → lucidicai-2.1.1/lucidicai/telemetry/utils}/model_pricing.py +0 -0
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai.egg-info/dependency_links.txt +0 -0
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai.egg-info/requires.txt +0 -0
- {lucidicai-2.0.2 → lucidicai-2.1.1}/lucidicai.egg-info/top_level.txt +0 -0
- {lucidicai-2.0.2 → lucidicai-2.1.1}/setup.cfg +0 -0
|
@@ -83,7 +83,7 @@ lai.init(
|
|
|
83
83
|
auto_end=True, # Optional: Auto-end session on exit (default: True)
|
|
84
84
|
masking_function=my_mask_func, # Optional: Custom PII masking
|
|
85
85
|
tags=["customer-support", "v1.2"], # Optional: Session tags
|
|
86
|
-
|
|
86
|
+
evaluators=[...], # Optional: Evaluation criteria
|
|
87
87
|
experiment_id="...", # Optional: Link to experiment
|
|
88
88
|
capture_uncaught=True # Optional: Capture crash events (default: True)
|
|
89
89
|
)
|
|
@@ -413,8 +413,8 @@ Create experiments to group and analyze multiple sessions:
|
|
|
413
413
|
# Create an experiment
|
|
414
414
|
experiment_id = lai.create_experiment(
|
|
415
415
|
experiment_name="Prompt Optimization Test",
|
|
416
|
-
|
|
417
|
-
|
|
416
|
+
LLM_boolean_evaluators=["response_quality", "latency"],
|
|
417
|
+
LLM_numeric_evaluators=["coherence", "relevance"],
|
|
418
418
|
description="Testing different prompt strategies",
|
|
419
419
|
tags=["A/B-test", "prompts"]
|
|
420
420
|
)
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"""Lucidic AI SDK - Clean Export-Only Entry Point
|
|
2
|
+
|
|
3
|
+
This file only contains exports, with all logic moved to appropriate modules.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Import core modules
|
|
7
|
+
from .sdk import init as init_module
|
|
8
|
+
from .sdk import event as event_module
|
|
9
|
+
from .sdk import error_boundary
|
|
10
|
+
from .core.config import get_config
|
|
11
|
+
|
|
12
|
+
# Import raw functions
|
|
13
|
+
from .sdk.init import (
|
|
14
|
+
init as _init,
|
|
15
|
+
get_session_id as _get_session_id,
|
|
16
|
+
clear_state as _clear_state,
|
|
17
|
+
# Thread-local session management (advanced users)
|
|
18
|
+
set_thread_session,
|
|
19
|
+
clear_thread_session,
|
|
20
|
+
get_thread_session,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from .sdk.event import (
|
|
24
|
+
create_event as _create_event,
|
|
25
|
+
create_error_event as _create_error_event,
|
|
26
|
+
flush as _flush,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Context management exports
|
|
30
|
+
from .sdk.context import (
|
|
31
|
+
set_active_session,
|
|
32
|
+
clear_active_session,
|
|
33
|
+
bind_session,
|
|
34
|
+
bind_session_async,
|
|
35
|
+
session,
|
|
36
|
+
session_async,
|
|
37
|
+
run_session,
|
|
38
|
+
run_in_session,
|
|
39
|
+
thread_worker_with_session, # Thread isolation helper
|
|
40
|
+
current_session_id,
|
|
41
|
+
current_parent_event_id,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Decorators
|
|
45
|
+
from .sdk.decorators import event, event as step # step is deprecated alias
|
|
46
|
+
|
|
47
|
+
# Error types
|
|
48
|
+
from .core.errors import (
|
|
49
|
+
LucidicError,
|
|
50
|
+
LucidicNotInitializedError,
|
|
51
|
+
APIKeyVerificationError,
|
|
52
|
+
InvalidOperationError,
|
|
53
|
+
PromptError,
|
|
54
|
+
FeatureFlagError,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Import functions that need to be implemented
|
|
58
|
+
def _update_session(
|
|
59
|
+
task=None,
|
|
60
|
+
session_eval=None,
|
|
61
|
+
session_eval_reason=None,
|
|
62
|
+
is_successful=None,
|
|
63
|
+
is_successful_reason=None,
|
|
64
|
+
session_id=None # Accept explicit session_id
|
|
65
|
+
):
|
|
66
|
+
"""Update the current session."""
|
|
67
|
+
from .sdk.init import get_resources, get_session_id
|
|
68
|
+
|
|
69
|
+
# Use provided session_id or fall back to context
|
|
70
|
+
if not session_id:
|
|
71
|
+
session_id = get_session_id()
|
|
72
|
+
if not session_id:
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
resources = get_resources()
|
|
76
|
+
if resources and 'sessions' in resources:
|
|
77
|
+
updates = {}
|
|
78
|
+
if task is not None:
|
|
79
|
+
updates['task'] = task
|
|
80
|
+
if session_eval is not None:
|
|
81
|
+
updates['session_eval'] = session_eval
|
|
82
|
+
if session_eval_reason is not None:
|
|
83
|
+
updates['session_eval_reason'] = session_eval_reason
|
|
84
|
+
if is_successful is not None:
|
|
85
|
+
updates['is_successful'] = is_successful
|
|
86
|
+
if is_successful_reason is not None:
|
|
87
|
+
updates['is_successful_reason'] = is_successful_reason
|
|
88
|
+
|
|
89
|
+
if updates:
|
|
90
|
+
resources['sessions'].update_session(session_id, updates)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _end_session(
|
|
94
|
+
session_eval=None,
|
|
95
|
+
session_eval_reason=None,
|
|
96
|
+
is_successful=None,
|
|
97
|
+
is_successful_reason=None,
|
|
98
|
+
wait_for_flush=True,
|
|
99
|
+
session_id=None # Accept explicit session_id
|
|
100
|
+
):
|
|
101
|
+
"""End the current session."""
|
|
102
|
+
from .sdk.init import get_resources, get_session_id, get_event_queue
|
|
103
|
+
|
|
104
|
+
# Use provided session_id or fall back to context
|
|
105
|
+
if not session_id:
|
|
106
|
+
session_id = get_session_id()
|
|
107
|
+
if not session_id:
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
# Flush events if requested
|
|
111
|
+
if wait_for_flush:
|
|
112
|
+
flush(timeout_seconds=5.0)
|
|
113
|
+
|
|
114
|
+
# End session via API
|
|
115
|
+
resources = get_resources()
|
|
116
|
+
if resources and 'sessions' in resources:
|
|
117
|
+
resources['sessions'].end_session(
|
|
118
|
+
session_id,
|
|
119
|
+
is_successful=is_successful,
|
|
120
|
+
session_eval=session_eval,
|
|
121
|
+
is_successful_reason=is_successful_reason,
|
|
122
|
+
session_eval_reason=session_eval_reason
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Clear session context
|
|
126
|
+
clear_active_session()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _get_session():
|
|
130
|
+
"""Get the current session object."""
|
|
131
|
+
from .sdk.init import get_session_id
|
|
132
|
+
return get_session_id()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _create_experiment(
|
|
136
|
+
experiment_name,
|
|
137
|
+
LLM_boolean_evaluators=None,
|
|
138
|
+
LLM_numeric_evaluators=None,
|
|
139
|
+
description=None,
|
|
140
|
+
tags=None,
|
|
141
|
+
api_key=None,
|
|
142
|
+
agent_id=None,
|
|
143
|
+
):
|
|
144
|
+
"""Create a new experiment."""
|
|
145
|
+
from .sdk.init import get_http
|
|
146
|
+
from .core.config import SDKConfig, get_config
|
|
147
|
+
|
|
148
|
+
# Get or create HTTP client
|
|
149
|
+
http = get_http()
|
|
150
|
+
config = get_config()
|
|
151
|
+
|
|
152
|
+
if not http:
|
|
153
|
+
config = SDKConfig.from_env(api_key=api_key, agent_id=agent_id)
|
|
154
|
+
from .api.client import HttpClient
|
|
155
|
+
http = HttpClient(config)
|
|
156
|
+
|
|
157
|
+
# Use provided agent_id or fall back to config
|
|
158
|
+
final_agent_id = agent_id or config.agent_id
|
|
159
|
+
if not final_agent_id:
|
|
160
|
+
raise ValueError("Agent ID is required for creating experiments")
|
|
161
|
+
|
|
162
|
+
evaluator_names = []
|
|
163
|
+
if LLM_boolean_evaluators:
|
|
164
|
+
evaluator_names.extend(LLM_boolean_evaluators)
|
|
165
|
+
if LLM_numeric_evaluators:
|
|
166
|
+
evaluator_names.extend(LLM_numeric_evaluators)
|
|
167
|
+
|
|
168
|
+
# Create experiment via API (matching TypeScript exactly)
|
|
169
|
+
response = http.post('createexperiment', {
|
|
170
|
+
'agent_id': final_agent_id,
|
|
171
|
+
'experiment_name': experiment_name,
|
|
172
|
+
'description': description or '',
|
|
173
|
+
'tags': tags or [],
|
|
174
|
+
'evaluator_names': evaluator_names
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
return response.get('experiment_id')
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _get_prompt(
|
|
181
|
+
prompt_name,
|
|
182
|
+
variables=None,
|
|
183
|
+
cache_ttl=300,
|
|
184
|
+
label='production'
|
|
185
|
+
):
|
|
186
|
+
"""Get a prompt from the prompt database."""
|
|
187
|
+
from .sdk.init import get_http
|
|
188
|
+
|
|
189
|
+
http = get_http()
|
|
190
|
+
if not http:
|
|
191
|
+
return ""
|
|
192
|
+
|
|
193
|
+
# Get prompt from API
|
|
194
|
+
try:
|
|
195
|
+
response = http.get('getprompt', {
|
|
196
|
+
'prompt_name': prompt_name,
|
|
197
|
+
'label': label
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
# TypeScript SDK expects 'prompt_content' field
|
|
201
|
+
prompt = response.get('prompt_content', '')
|
|
202
|
+
|
|
203
|
+
# Replace variables if provided
|
|
204
|
+
if variables:
|
|
205
|
+
for key, value in variables.items():
|
|
206
|
+
prompt = prompt.replace(f"{{{key}}}", str(value))
|
|
207
|
+
|
|
208
|
+
return prompt
|
|
209
|
+
except Exception:
|
|
210
|
+
return ""
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _get_dataset(dataset_id, api_key=None, agent_id=None):
|
|
214
|
+
"""Get a dataset by ID."""
|
|
215
|
+
from .sdk.features.dataset import get_dataset as __get_dataset
|
|
216
|
+
return __get_dataset(dataset_id, api_key, agent_id)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _get_dataset_items(dataset_id, api_key=None, agent_id=None):
|
|
220
|
+
"""Get dataset items."""
|
|
221
|
+
from .sdk.features.dataset import get_dataset_items as __get_dataset_items
|
|
222
|
+
return __get_dataset_items(dataset_id, api_key, agent_id)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _list_datasets(api_key=None, agent_id=None):
|
|
226
|
+
"""List all datasets."""
|
|
227
|
+
from .sdk.features.dataset import list_datasets as __list_datasets
|
|
228
|
+
return __list_datasets(api_key, agent_id)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _create_dataset(name, description=None, tags=None, suggested_flag_config=None, api_key=None, agent_id=None):
|
|
232
|
+
"""Create a new dataset."""
|
|
233
|
+
from .sdk.features.dataset import create_dataset as __create_dataset
|
|
234
|
+
return __create_dataset(name, description, tags, suggested_flag_config, api_key, agent_id)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _update_dataset(dataset_id, name=None, description=None, tags=None, suggested_flag_config=None, api_key=None, agent_id=None):
|
|
238
|
+
"""Update dataset metadata."""
|
|
239
|
+
from .sdk.features.dataset import update_dataset as __update_dataset
|
|
240
|
+
return __update_dataset(dataset_id, name, description, tags, suggested_flag_config, api_key, agent_id)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _delete_dataset(dataset_id, api_key=None, agent_id=None):
|
|
244
|
+
"""Delete a dataset."""
|
|
245
|
+
from .sdk.features.dataset import delete_dataset as __delete_dataset
|
|
246
|
+
return __delete_dataset(dataset_id, api_key, agent_id)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _create_dataset_item(dataset_id, name, input_data, expected_output=None, description=None, tags=None, metadata=None, flag_overrides=None, api_key=None, agent_id=None):
|
|
250
|
+
"""Create a dataset item."""
|
|
251
|
+
from .sdk.features.dataset import create_dataset_item as __create_dataset_item
|
|
252
|
+
return __create_dataset_item(dataset_id, name, input_data, expected_output, description, tags, metadata, flag_overrides, api_key, agent_id)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _get_dataset_item(dataset_id, item_id, api_key=None, agent_id=None):
|
|
256
|
+
"""Get a specific dataset item."""
|
|
257
|
+
from .sdk.features.dataset import get_dataset_item as __get_dataset_item
|
|
258
|
+
return __get_dataset_item(dataset_id, item_id, api_key, agent_id)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _update_dataset_item(dataset_id, item_id, name=None, input_data=None, expected_output=None, description=None, tags=None, metadata=None, flag_overrides=None, api_key=None, agent_id=None):
|
|
262
|
+
"""Update a dataset item."""
|
|
263
|
+
from .sdk.features.dataset import update_dataset_item as __update_dataset_item
|
|
264
|
+
return __update_dataset_item(dataset_id, item_id, name, input_data, expected_output, description, tags, metadata, flag_overrides, api_key, agent_id)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _delete_dataset_item(dataset_id, item_id, api_key=None, agent_id=None):
|
|
268
|
+
"""Delete a dataset item."""
|
|
269
|
+
from .sdk.features.dataset import delete_dataset_item as __delete_dataset_item
|
|
270
|
+
return __delete_dataset_item(dataset_id, item_id, api_key, agent_id)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _list_dataset_item_sessions(dataset_id, item_id, api_key=None, agent_id=None):
|
|
274
|
+
"""List all sessions for a dataset item."""
|
|
275
|
+
from .sdk.features.dataset import list_dataset_item_sessions as __list_dataset_item_sessions
|
|
276
|
+
return __list_dataset_item_sessions(dataset_id, item_id, api_key, agent_id)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# Feature flags
|
|
280
|
+
from .sdk.features.feature_flag import (
|
|
281
|
+
get_feature_flag,
|
|
282
|
+
get_bool_flag,
|
|
283
|
+
get_int_flag,
|
|
284
|
+
get_float_flag,
|
|
285
|
+
get_string_flag,
|
|
286
|
+
get_json_flag,
|
|
287
|
+
clear_feature_flag_cache,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
# Error boundary utilities
|
|
291
|
+
is_silent_mode = error_boundary.is_silent_mode
|
|
292
|
+
get_error_history = error_boundary.get_error_history
|
|
293
|
+
clear_error_history = error_boundary.clear_error_history
|
|
294
|
+
|
|
295
|
+
# Version
|
|
296
|
+
__version__ = "2.1.1"
|
|
297
|
+
|
|
298
|
+
# Apply error boundary wrapping to all SDK functions
|
|
299
|
+
from .sdk.error_boundary import wrap_sdk_function
|
|
300
|
+
|
|
301
|
+
# Wrap main SDK functions
|
|
302
|
+
init = wrap_sdk_function(_init, "init")
|
|
303
|
+
get_session_id = wrap_sdk_function(_get_session_id, "init")
|
|
304
|
+
clear_state = wrap_sdk_function(_clear_state, "init")
|
|
305
|
+
create_event = wrap_sdk_function(_create_event, "event")
|
|
306
|
+
create_error_event = wrap_sdk_function(_create_error_event, "event")
|
|
307
|
+
flush = wrap_sdk_function(_flush, "event")
|
|
308
|
+
|
|
309
|
+
# Wrap session functions
|
|
310
|
+
update_session = wrap_sdk_function(_update_session, "session")
|
|
311
|
+
end_session = wrap_sdk_function(_end_session, "session")
|
|
312
|
+
get_session = wrap_sdk_function(_get_session, "session")
|
|
313
|
+
|
|
314
|
+
# Wrap feature functions
|
|
315
|
+
create_experiment = wrap_sdk_function(_create_experiment, "experiment")
|
|
316
|
+
get_prompt = wrap_sdk_function(_get_prompt, "prompt")
|
|
317
|
+
|
|
318
|
+
# Dataset management - complete CRUD
|
|
319
|
+
list_datasets = wrap_sdk_function(_list_datasets, "dataset")
|
|
320
|
+
create_dataset = wrap_sdk_function(_create_dataset, "dataset")
|
|
321
|
+
get_dataset = wrap_sdk_function(_get_dataset, "dataset")
|
|
322
|
+
update_dataset = wrap_sdk_function(_update_dataset, "dataset")
|
|
323
|
+
delete_dataset = wrap_sdk_function(_delete_dataset, "dataset")
|
|
324
|
+
|
|
325
|
+
# Dataset item management
|
|
326
|
+
create_dataset_item = wrap_sdk_function(_create_dataset_item, "dataset")
|
|
327
|
+
get_dataset_item = wrap_sdk_function(_get_dataset_item, "dataset")
|
|
328
|
+
update_dataset_item = wrap_sdk_function(_update_dataset_item, "dataset")
|
|
329
|
+
delete_dataset_item = wrap_sdk_function(_delete_dataset_item, "dataset")
|
|
330
|
+
get_dataset_items = wrap_sdk_function(_get_dataset_items, "dataset")
|
|
331
|
+
list_dataset_item_sessions = wrap_sdk_function(_list_dataset_item_sessions, "dataset")
|
|
332
|
+
|
|
333
|
+
# All exports
|
|
334
|
+
__all__ = [
|
|
335
|
+
# Main functions
|
|
336
|
+
'init',
|
|
337
|
+
'get_session_id',
|
|
338
|
+
'clear_state',
|
|
339
|
+
'update_session',
|
|
340
|
+
'end_session',
|
|
341
|
+
'get_session',
|
|
342
|
+
'create_event',
|
|
343
|
+
'create_error_event',
|
|
344
|
+
'flush',
|
|
345
|
+
|
|
346
|
+
# Decorators
|
|
347
|
+
'event',
|
|
348
|
+
'step',
|
|
349
|
+
|
|
350
|
+
# Features
|
|
351
|
+
'create_experiment',
|
|
352
|
+
'get_prompt',
|
|
353
|
+
|
|
354
|
+
# Dataset management
|
|
355
|
+
'list_datasets',
|
|
356
|
+
'create_dataset',
|
|
357
|
+
'get_dataset',
|
|
358
|
+
'update_dataset',
|
|
359
|
+
'delete_dataset',
|
|
360
|
+
'create_dataset_item',
|
|
361
|
+
'get_dataset_item',
|
|
362
|
+
'update_dataset_item',
|
|
363
|
+
'delete_dataset_item',
|
|
364
|
+
'get_dataset_items',
|
|
365
|
+
'list_dataset_item_sessions',
|
|
366
|
+
|
|
367
|
+
# Feature flags
|
|
368
|
+
'get_feature_flag',
|
|
369
|
+
'get_bool_flag',
|
|
370
|
+
'get_int_flag',
|
|
371
|
+
'get_float_flag',
|
|
372
|
+
'get_string_flag',
|
|
373
|
+
'get_json_flag',
|
|
374
|
+
'clear_feature_flag_cache',
|
|
375
|
+
|
|
376
|
+
# Context management
|
|
377
|
+
'set_active_session',
|
|
378
|
+
'clear_active_session',
|
|
379
|
+
'bind_session',
|
|
380
|
+
'bind_session_async',
|
|
381
|
+
'session',
|
|
382
|
+
'session_async',
|
|
383
|
+
'run_session',
|
|
384
|
+
'run_in_session',
|
|
385
|
+
'thread_worker_with_session',
|
|
386
|
+
'current_session_id',
|
|
387
|
+
'current_parent_event_id',
|
|
388
|
+
|
|
389
|
+
# Thread-local session management (advanced)
|
|
390
|
+
'set_thread_session',
|
|
391
|
+
'clear_thread_session',
|
|
392
|
+
'get_thread_session',
|
|
393
|
+
|
|
394
|
+
# Error types
|
|
395
|
+
'LucidicError',
|
|
396
|
+
'LucidicNotInitializedError',
|
|
397
|
+
'APIKeyVerificationError',
|
|
398
|
+
'InvalidOperationError',
|
|
399
|
+
'PromptError',
|
|
400
|
+
'FeatureFlagError',
|
|
401
|
+
|
|
402
|
+
# Error boundary
|
|
403
|
+
'is_silent_mode',
|
|
404
|
+
'get_error_history',
|
|
405
|
+
'clear_error_history',
|
|
406
|
+
|
|
407
|
+
# Version
|
|
408
|
+
'__version__',
|
|
409
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""API client and resources."""
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Pure HTTP client for Lucidic API communication.
|
|
2
|
+
|
|
3
|
+
This module contains only the HTTP client logic, separated from
|
|
4
|
+
session management and other concerns.
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
from urllib.parse import urlencode
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
from requests.adapters import HTTPAdapter
|
|
12
|
+
from urllib3.util import Retry
|
|
13
|
+
|
|
14
|
+
from ..core.config import SDKConfig, get_config
|
|
15
|
+
from ..core.errors import APIKeyVerificationError
|
|
16
|
+
from ..utils.logger import debug, info, warning, error, mask_sensitive, truncate_data
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HttpClient:
|
|
20
|
+
"""HTTP client for API communication."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: Optional[SDKConfig] = None):
|
|
23
|
+
"""Initialize the HTTP client.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
config: SDK configuration (uses global if not provided)
|
|
27
|
+
"""
|
|
28
|
+
self.config = config or get_config()
|
|
29
|
+
self.base_url = self.config.network.base_url
|
|
30
|
+
|
|
31
|
+
# Create session with connection pooling
|
|
32
|
+
self.session = requests.Session()
|
|
33
|
+
|
|
34
|
+
# Configure retries
|
|
35
|
+
retry_cfg = Retry(
|
|
36
|
+
total=self.config.network.max_retries,
|
|
37
|
+
backoff_factor=self.config.network.backoff_factor,
|
|
38
|
+
status_forcelist=[502, 503, 504],
|
|
39
|
+
allowed_methods=["GET", "POST", "PUT", "DELETE"],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Configure adapter with connection pooling
|
|
43
|
+
adapter = HTTPAdapter(
|
|
44
|
+
max_retries=retry_cfg,
|
|
45
|
+
pool_connections=self.config.network.connection_pool_size,
|
|
46
|
+
pool_maxsize=self.config.network.connection_pool_maxsize
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
self.session.mount("https://", adapter)
|
|
50
|
+
self.session.mount("http://", adapter)
|
|
51
|
+
|
|
52
|
+
# Set headers
|
|
53
|
+
self._update_headers()
|
|
54
|
+
|
|
55
|
+
# Verify API key if configured
|
|
56
|
+
if self.config.api_key:
|
|
57
|
+
self._verify_api_key()
|
|
58
|
+
|
|
59
|
+
def _update_headers(self) -> None:
|
|
60
|
+
"""Update session headers with authentication."""
|
|
61
|
+
headers = {
|
|
62
|
+
"User-Agent": "lucidic-sdk/2.0",
|
|
63
|
+
"Content-Type": "application/json"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if self.config.api_key:
|
|
67
|
+
headers["Authorization"] = f"Api-Key {self.config.api_key}"
|
|
68
|
+
|
|
69
|
+
if self.config.agent_id:
|
|
70
|
+
headers["x-agent-id"] = self.config.agent_id
|
|
71
|
+
|
|
72
|
+
self.session.headers.update(headers)
|
|
73
|
+
|
|
74
|
+
def _verify_api_key(self) -> None:
|
|
75
|
+
"""Verify the API key with the backend."""
|
|
76
|
+
debug("[HTTP] Verifying API key")
|
|
77
|
+
try:
|
|
78
|
+
response = self.get("verifyapikey")
|
|
79
|
+
# Backend returns 200 OK for valid key, check if we got a response
|
|
80
|
+
if response is None:
|
|
81
|
+
raise APIKeyVerificationError("No response from API")
|
|
82
|
+
info("[HTTP] API key verified successfully")
|
|
83
|
+
except APIKeyVerificationError:
|
|
84
|
+
raise
|
|
85
|
+
except requests.RequestException as e:
|
|
86
|
+
raise APIKeyVerificationError(f"Could not verify API key: {e}")
|
|
87
|
+
|
|
88
|
+
def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
89
|
+
"""Make a GET request.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
endpoint: API endpoint (without base URL)
|
|
93
|
+
params: Query parameters
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Response data as dictionary
|
|
97
|
+
"""
|
|
98
|
+
return self.request("GET", endpoint, params=params)
|
|
99
|
+
|
|
100
|
+
def post(self, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
101
|
+
"""Make a POST request.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
endpoint: API endpoint (without base URL)
|
|
105
|
+
data: Request body data
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Response data as dictionary
|
|
109
|
+
"""
|
|
110
|
+
# Add current_time to all POST requests like TypeScript SDK does
|
|
111
|
+
from datetime import datetime, timezone
|
|
112
|
+
if data is None:
|
|
113
|
+
data = {}
|
|
114
|
+
data["current_time"] = datetime.now(timezone.utc).isoformat()
|
|
115
|
+
return self.request("POST", endpoint, json=data)
|
|
116
|
+
|
|
117
|
+
def put(self, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
118
|
+
"""Make a PUT request.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
endpoint: API endpoint (without base URL)
|
|
122
|
+
data: Request body data
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Response data as dictionary
|
|
126
|
+
"""
|
|
127
|
+
# Add current_time to all PUT requests like TypeScript SDK does
|
|
128
|
+
from datetime import datetime, timezone
|
|
129
|
+
if data is None:
|
|
130
|
+
data = {}
|
|
131
|
+
data["current_time"] = datetime.now(timezone.utc).isoformat()
|
|
132
|
+
return self.request("PUT", endpoint, json=data)
|
|
133
|
+
|
|
134
|
+
def delete(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
135
|
+
"""Make a DELETE request.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
endpoint: API endpoint (without base URL)
|
|
139
|
+
params: Query parameters
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Response data as dictionary
|
|
143
|
+
"""
|
|
144
|
+
return self.request("DELETE", endpoint, params=params)
|
|
145
|
+
|
|
146
|
+
def request(
|
|
147
|
+
self,
|
|
148
|
+
method: str,
|
|
149
|
+
endpoint: str,
|
|
150
|
+
params: Optional[Dict[str, Any]] = None,
|
|
151
|
+
json: Optional[Dict[str, Any]] = None,
|
|
152
|
+
**kwargs
|
|
153
|
+
) -> Dict[str, Any]:
|
|
154
|
+
"""Make an HTTP request.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
method: HTTP method
|
|
158
|
+
endpoint: API endpoint (without base URL)
|
|
159
|
+
params: Query parameters
|
|
160
|
+
json: Request body (for POST/PUT)
|
|
161
|
+
**kwargs: Additional arguments for requests
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Response data as dictionary
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
requests.RequestException: On HTTP errors
|
|
168
|
+
"""
|
|
169
|
+
url = f"{self.base_url}/{endpoint}"
|
|
170
|
+
|
|
171
|
+
# Log request details
|
|
172
|
+
debug(f"[HTTP] {method} {url}")
|
|
173
|
+
if params:
|
|
174
|
+
debug(f"[HTTP] Query params: {mask_sensitive(params)}")
|
|
175
|
+
if json:
|
|
176
|
+
debug(f"[HTTP] Request body: {truncate_data(mask_sensitive(json))}")
|
|
177
|
+
|
|
178
|
+
response = self.session.request(
|
|
179
|
+
method=method,
|
|
180
|
+
url=url,
|
|
181
|
+
params=params,
|
|
182
|
+
json=json,
|
|
183
|
+
timeout=self.config.network.timeout,
|
|
184
|
+
**kwargs
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Raise for HTTP errors with more detail
|
|
188
|
+
if not response.ok:
|
|
189
|
+
# Try to get error details from response
|
|
190
|
+
try:
|
|
191
|
+
error_data = response.json()
|
|
192
|
+
error_msg = error_data.get('detail', response.text)
|
|
193
|
+
except:
|
|
194
|
+
error_msg = response.text
|
|
195
|
+
|
|
196
|
+
error(f"[HTTP] Error {response.status_code}: {error_msg}")
|
|
197
|
+
|
|
198
|
+
response.raise_for_status()
|
|
199
|
+
|
|
200
|
+
# Parse JSON response
|
|
201
|
+
try:
|
|
202
|
+
data = response.json()
|
|
203
|
+
except ValueError:
|
|
204
|
+
# For empty responses (like verifyapikey), return success
|
|
205
|
+
if response.status_code == 200 and not response.text:
|
|
206
|
+
data = {"success": True}
|
|
207
|
+
else:
|
|
208
|
+
# Return text if not JSON
|
|
209
|
+
data = {"response": response.text}
|
|
210
|
+
|
|
211
|
+
debug(f"[HTTP] Response ({response.status_code}): {truncate_data(data)}")
|
|
212
|
+
|
|
213
|
+
return data
|
|
214
|
+
|
|
215
|
+
def close(self) -> None:
|
|
216
|
+
"""Close the HTTP session."""
|
|
217
|
+
if self.session:
|
|
218
|
+
self.session.close()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""API resource modules."""
|