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.
- 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/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 +395 -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.0.0.dist-info}/METADATA +1 -1
- {lucidicai-2.1.3.dist-info → lucidicai-3.0.0.dist-info}/RECORD +38 -29
- {lucidicai-2.1.3.dist-info → lucidicai-3.0.0.dist-info}/WHEEL +0 -0
- {lucidicai-2.1.3.dist-info → lucidicai-3.0.0.dist-info}/top_level.txt +0 -0
lucidicai/__init__.py
CHANGED
|
@@ -1,48 +1,28 @@
|
|
|
1
|
-
"""Lucidic AI SDK -
|
|
1
|
+
"""Lucidic AI SDK - Instance-based client for AI observability.
|
|
2
2
|
|
|
3
|
-
This
|
|
4
|
-
|
|
3
|
+
This SDK provides observability for AI applications, tracking workflows,
|
|
4
|
+
costs, and performance across multiple LLM providers.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
from .sdk import event as event_module
|
|
9
|
-
from .sdk import error_boundary
|
|
10
|
-
from .core.config import get_config
|
|
6
|
+
Example:
|
|
7
|
+
from lucidicai import LucidicAI
|
|
11
8
|
|
|
12
|
-
|
|
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
|
-
)
|
|
9
|
+
client = LucidicAI(api_key="...", agent_id="...", providers=["openai"])
|
|
22
10
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
11
|
+
with client.create_session(session_name="My Session") as session:
|
|
12
|
+
@client.event
|
|
13
|
+
def my_function():
|
|
14
|
+
# LLM calls are automatically tracked
|
|
15
|
+
pass
|
|
16
|
+
my_function()
|
|
28
17
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
)
|
|
18
|
+
client.close()
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Main client class
|
|
22
|
+
from .client import LucidicAI
|
|
43
23
|
|
|
44
|
-
#
|
|
45
|
-
from .
|
|
24
|
+
# Session object
|
|
25
|
+
from .session_obj import Session
|
|
46
26
|
|
|
47
27
|
# Error types
|
|
48
28
|
from .core.errors import (
|
|
@@ -54,360 +34,22 @@ from .core.errors import (
|
|
|
54
34
|
FeatureFlagError,
|
|
55
35
|
)
|
|
56
36
|
|
|
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
|
-
from .sdk.shutdown_manager import get_shutdown_manager
|
|
104
|
-
|
|
105
|
-
# Use provided session_id or fall back to context
|
|
106
|
-
if not session_id:
|
|
107
|
-
session_id = get_session_id()
|
|
108
|
-
if not session_id:
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
# Flush events if requested
|
|
112
|
-
if wait_for_flush:
|
|
113
|
-
flush(timeout_seconds=5.0)
|
|
114
|
-
|
|
115
|
-
# End session via API
|
|
116
|
-
resources = get_resources()
|
|
117
|
-
if resources and 'sessions' in resources:
|
|
118
|
-
resources['sessions'].end_session(
|
|
119
|
-
session_id,
|
|
120
|
-
is_successful=is_successful,
|
|
121
|
-
session_eval=session_eval,
|
|
122
|
-
is_successful_reason=is_successful_reason,
|
|
123
|
-
session_eval_reason=session_eval_reason
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
# Clear session context
|
|
127
|
-
clear_active_session()
|
|
128
|
-
|
|
129
|
-
# unregister from shutdown manager
|
|
130
|
-
get_shutdown_manager().unregister_session(session_id)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def _get_session():
|
|
134
|
-
"""Get the current session object."""
|
|
135
|
-
from .sdk.init import get_session_id
|
|
136
|
-
return get_session_id()
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def _create_experiment(
|
|
140
|
-
experiment_name,
|
|
141
|
-
LLM_boolean_evaluators=None,
|
|
142
|
-
LLM_numeric_evaluators=None,
|
|
143
|
-
description=None,
|
|
144
|
-
tags=None,
|
|
145
|
-
api_key=None,
|
|
146
|
-
agent_id=None,
|
|
147
|
-
):
|
|
148
|
-
"""Create a new experiment."""
|
|
149
|
-
from .sdk.init import get_http
|
|
150
|
-
from .core.config import SDKConfig, get_config
|
|
151
|
-
|
|
152
|
-
# Get or create HTTP client
|
|
153
|
-
http = get_http()
|
|
154
|
-
config = get_config()
|
|
155
|
-
|
|
156
|
-
if not http:
|
|
157
|
-
config = SDKConfig.from_env(api_key=api_key, agent_id=agent_id)
|
|
158
|
-
from .api.client import HttpClient
|
|
159
|
-
http = HttpClient(config)
|
|
160
|
-
|
|
161
|
-
# Use provided agent_id or fall back to config
|
|
162
|
-
final_agent_id = agent_id or config.agent_id
|
|
163
|
-
if not final_agent_id:
|
|
164
|
-
raise ValueError("Agent ID is required for creating experiments")
|
|
165
|
-
|
|
166
|
-
evaluator_names = []
|
|
167
|
-
if LLM_boolean_evaluators:
|
|
168
|
-
evaluator_names.extend(LLM_boolean_evaluators)
|
|
169
|
-
if LLM_numeric_evaluators:
|
|
170
|
-
evaluator_names.extend(LLM_numeric_evaluators)
|
|
171
|
-
|
|
172
|
-
# Create experiment via API (matching TypeScript exactly)
|
|
173
|
-
response = http.post('createexperiment', {
|
|
174
|
-
'agent_id': final_agent_id,
|
|
175
|
-
'experiment_name': experiment_name,
|
|
176
|
-
'description': description or '',
|
|
177
|
-
'tags': tags or [],
|
|
178
|
-
'evaluator_names': evaluator_names
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
return response.get('experiment_id')
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def _get_prompt(
|
|
185
|
-
prompt_name,
|
|
186
|
-
variables=None,
|
|
187
|
-
cache_ttl=300,
|
|
188
|
-
label='production'
|
|
189
|
-
):
|
|
190
|
-
"""Get a prompt from the prompt database."""
|
|
191
|
-
from .sdk.init import get_http
|
|
192
|
-
|
|
193
|
-
http = get_http()
|
|
194
|
-
if not http:
|
|
195
|
-
return ""
|
|
196
|
-
|
|
197
|
-
# Get prompt from API
|
|
198
|
-
try:
|
|
199
|
-
response = http.get('getprompt', {
|
|
200
|
-
'prompt_name': prompt_name,
|
|
201
|
-
'label': label
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
# TypeScript SDK expects 'prompt_content' field
|
|
205
|
-
prompt = response.get('prompt_content', '')
|
|
206
|
-
|
|
207
|
-
# Replace variables if provided
|
|
208
|
-
if variables:
|
|
209
|
-
for key, value in variables.items():
|
|
210
|
-
prompt = prompt.replace(f"{{{key}}}", str(value))
|
|
211
|
-
|
|
212
|
-
return prompt
|
|
213
|
-
except Exception:
|
|
214
|
-
return ""
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def _get_dataset(dataset_id, api_key=None, agent_id=None):
|
|
218
|
-
"""Get a dataset by ID."""
|
|
219
|
-
from .sdk.features.dataset import get_dataset as __get_dataset
|
|
220
|
-
return __get_dataset(dataset_id, api_key, agent_id)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def _get_dataset_items(dataset_id, api_key=None, agent_id=None):
|
|
224
|
-
"""Get dataset items."""
|
|
225
|
-
from .sdk.features.dataset import get_dataset_items as __get_dataset_items
|
|
226
|
-
return __get_dataset_items(dataset_id, api_key, agent_id)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def _list_datasets(api_key=None, agent_id=None):
|
|
230
|
-
"""List all datasets."""
|
|
231
|
-
from .sdk.features.dataset import list_datasets as __list_datasets
|
|
232
|
-
return __list_datasets(api_key, agent_id)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def _create_dataset(name, description=None, tags=None, suggested_flag_config=None, api_key=None, agent_id=None):
|
|
236
|
-
"""Create a new dataset."""
|
|
237
|
-
from .sdk.features.dataset import create_dataset as __create_dataset
|
|
238
|
-
return __create_dataset(name, description, tags, suggested_flag_config, api_key, agent_id)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def _update_dataset(dataset_id, name=None, description=None, tags=None, suggested_flag_config=None, api_key=None, agent_id=None):
|
|
242
|
-
"""Update dataset metadata."""
|
|
243
|
-
from .sdk.features.dataset import update_dataset as __update_dataset
|
|
244
|
-
return __update_dataset(dataset_id, name, description, tags, suggested_flag_config, api_key, agent_id)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def _delete_dataset(dataset_id, api_key=None, agent_id=None):
|
|
248
|
-
"""Delete a dataset."""
|
|
249
|
-
from .sdk.features.dataset import delete_dataset as __delete_dataset
|
|
250
|
-
return __delete_dataset(dataset_id, api_key, agent_id)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
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):
|
|
254
|
-
"""Create a dataset item."""
|
|
255
|
-
from .sdk.features.dataset import create_dataset_item as __create_dataset_item
|
|
256
|
-
return __create_dataset_item(dataset_id, name, input_data, expected_output, description, tags, metadata, flag_overrides, api_key, agent_id)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def _get_dataset_item(dataset_id, item_id, api_key=None, agent_id=None):
|
|
260
|
-
"""Get a specific dataset item."""
|
|
261
|
-
from .sdk.features.dataset import get_dataset_item as __get_dataset_item
|
|
262
|
-
return __get_dataset_item(dataset_id, item_id, api_key, agent_id)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
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):
|
|
266
|
-
"""Update a dataset item."""
|
|
267
|
-
from .sdk.features.dataset import update_dataset_item as __update_dataset_item
|
|
268
|
-
return __update_dataset_item(dataset_id, item_id, name, input_data, expected_output, description, tags, metadata, flag_overrides, api_key, agent_id)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def _delete_dataset_item(dataset_id, item_id, api_key=None, agent_id=None):
|
|
272
|
-
"""Delete a dataset item."""
|
|
273
|
-
from .sdk.features.dataset import delete_dataset_item as __delete_dataset_item
|
|
274
|
-
return __delete_dataset_item(dataset_id, item_id, api_key, agent_id)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
def _list_dataset_item_sessions(dataset_id, item_id, api_key=None, agent_id=None):
|
|
278
|
-
"""List all sessions for a dataset item."""
|
|
279
|
-
from .sdk.features.dataset import list_dataset_item_sessions as __list_dataset_item_sessions
|
|
280
|
-
return __list_dataset_item_sessions(dataset_id, item_id, api_key, agent_id)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
# Feature flags
|
|
284
|
-
from .sdk.features.feature_flag import (
|
|
285
|
-
get_feature_flag,
|
|
286
|
-
get_bool_flag,
|
|
287
|
-
get_int_flag,
|
|
288
|
-
get_float_flag,
|
|
289
|
-
get_string_flag,
|
|
290
|
-
get_json_flag,
|
|
291
|
-
clear_feature_flag_cache,
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
# Error boundary utilities
|
|
295
|
-
is_silent_mode = error_boundary.is_silent_mode
|
|
296
|
-
get_error_history = error_boundary.get_error_history
|
|
297
|
-
clear_error_history = error_boundary.clear_error_history
|
|
298
|
-
|
|
299
37
|
# Version
|
|
300
|
-
__version__ = "
|
|
301
|
-
|
|
302
|
-
# Apply error boundary wrapping to all SDK functions
|
|
303
|
-
from .sdk.error_boundary import wrap_sdk_function
|
|
304
|
-
|
|
305
|
-
# Wrap main SDK functions
|
|
306
|
-
init = wrap_sdk_function(_init, "init")
|
|
307
|
-
get_session_id = wrap_sdk_function(_get_session_id, "init")
|
|
308
|
-
clear_state = wrap_sdk_function(_clear_state, "init")
|
|
309
|
-
create_event = wrap_sdk_function(_create_event, "event")
|
|
310
|
-
create_error_event = wrap_sdk_function(_create_error_event, "event")
|
|
311
|
-
flush = wrap_sdk_function(_flush, "event")
|
|
312
|
-
|
|
313
|
-
# Wrap session functions
|
|
314
|
-
update_session = wrap_sdk_function(_update_session, "session")
|
|
315
|
-
end_session = wrap_sdk_function(_end_session, "session")
|
|
316
|
-
get_session = wrap_sdk_function(_get_session, "session")
|
|
317
|
-
|
|
318
|
-
# Wrap feature functions
|
|
319
|
-
create_experiment = wrap_sdk_function(_create_experiment, "experiment")
|
|
320
|
-
get_prompt = wrap_sdk_function(_get_prompt, "prompt")
|
|
321
|
-
|
|
322
|
-
# Dataset management - complete CRUD
|
|
323
|
-
list_datasets = wrap_sdk_function(_list_datasets, "dataset")
|
|
324
|
-
create_dataset = wrap_sdk_function(_create_dataset, "dataset")
|
|
325
|
-
get_dataset = wrap_sdk_function(_get_dataset, "dataset")
|
|
326
|
-
update_dataset = wrap_sdk_function(_update_dataset, "dataset")
|
|
327
|
-
delete_dataset = wrap_sdk_function(_delete_dataset, "dataset")
|
|
328
|
-
|
|
329
|
-
# Dataset item management
|
|
330
|
-
create_dataset_item = wrap_sdk_function(_create_dataset_item, "dataset")
|
|
331
|
-
get_dataset_item = wrap_sdk_function(_get_dataset_item, "dataset")
|
|
332
|
-
update_dataset_item = wrap_sdk_function(_update_dataset_item, "dataset")
|
|
333
|
-
delete_dataset_item = wrap_sdk_function(_delete_dataset_item, "dataset")
|
|
334
|
-
get_dataset_items = wrap_sdk_function(_get_dataset_items, "dataset")
|
|
335
|
-
list_dataset_item_sessions = wrap_sdk_function(_list_dataset_item_sessions, "dataset")
|
|
38
|
+
__version__ = "3.0.0"
|
|
336
39
|
|
|
337
40
|
# All exports
|
|
338
41
|
__all__ = [
|
|
339
|
-
# Main
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
'update_session',
|
|
344
|
-
'end_session',
|
|
345
|
-
'get_session',
|
|
346
|
-
'create_event',
|
|
347
|
-
'create_error_event',
|
|
348
|
-
'flush',
|
|
349
|
-
|
|
350
|
-
# Decorators
|
|
351
|
-
'event',
|
|
352
|
-
'step',
|
|
353
|
-
|
|
354
|
-
# Features
|
|
355
|
-
'create_experiment',
|
|
356
|
-
'get_prompt',
|
|
357
|
-
|
|
358
|
-
# Dataset management
|
|
359
|
-
'list_datasets',
|
|
360
|
-
'create_dataset',
|
|
361
|
-
'get_dataset',
|
|
362
|
-
'update_dataset',
|
|
363
|
-
'delete_dataset',
|
|
364
|
-
'create_dataset_item',
|
|
365
|
-
'get_dataset_item',
|
|
366
|
-
'update_dataset_item',
|
|
367
|
-
'delete_dataset_item',
|
|
368
|
-
'get_dataset_items',
|
|
369
|
-
'list_dataset_item_sessions',
|
|
370
|
-
|
|
371
|
-
# Feature flags
|
|
372
|
-
'get_feature_flag',
|
|
373
|
-
'get_bool_flag',
|
|
374
|
-
'get_int_flag',
|
|
375
|
-
'get_float_flag',
|
|
376
|
-
'get_string_flag',
|
|
377
|
-
'get_json_flag',
|
|
378
|
-
'clear_feature_flag_cache',
|
|
379
|
-
|
|
380
|
-
# Context management
|
|
381
|
-
'set_active_session',
|
|
382
|
-
'clear_active_session',
|
|
383
|
-
'bind_session',
|
|
384
|
-
'bind_session_async',
|
|
385
|
-
'session',
|
|
386
|
-
'session_async',
|
|
387
|
-
'run_session',
|
|
388
|
-
'run_in_session',
|
|
389
|
-
'thread_worker_with_session',
|
|
390
|
-
'current_session_id',
|
|
391
|
-
'current_parent_event_id',
|
|
392
|
-
|
|
393
|
-
# Thread-local session management (advanced)
|
|
394
|
-
'set_thread_session',
|
|
395
|
-
'clear_thread_session',
|
|
396
|
-
'get_thread_session',
|
|
397
|
-
|
|
42
|
+
# Main client
|
|
43
|
+
"LucidicAI",
|
|
44
|
+
# Session object
|
|
45
|
+
"Session",
|
|
398
46
|
# Error types
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
# Error boundary
|
|
407
|
-
'is_silent_mode',
|
|
408
|
-
'get_error_history',
|
|
409
|
-
'clear_error_history',
|
|
410
|
-
|
|
47
|
+
"LucidicError",
|
|
48
|
+
"LucidicNotInitializedError",
|
|
49
|
+
"APIKeyVerificationError",
|
|
50
|
+
"InvalidOperationError",
|
|
51
|
+
"PromptError",
|
|
52
|
+
"FeatureFlagError",
|
|
411
53
|
# Version
|
|
412
|
-
|
|
413
|
-
]
|
|
54
|
+
"__version__",
|
|
55
|
+
]
|
lucidicai/api/client.py
CHANGED
|
@@ -43,6 +43,7 @@ class HttpClient:
|
|
|
43
43
|
# Lazy-initialized clients
|
|
44
44
|
self._sync_client: Optional[httpx.Client] = None
|
|
45
45
|
self._async_client: Optional[httpx.AsyncClient] = None
|
|
46
|
+
self._async_client_loop: Optional[asyncio.AbstractEventLoop] = None
|
|
46
47
|
|
|
47
48
|
def _build_headers(self) -> Dict[str, str]:
|
|
48
49
|
"""Build default headers for requests."""
|
|
@@ -75,8 +76,34 @@ class HttpClient:
|
|
|
75
76
|
|
|
76
77
|
@property
|
|
77
78
|
def async_client(self) -> httpx.AsyncClient:
|
|
78
|
-
"""Get or create the asynchronous HTTP client.
|
|
79
|
-
|
|
79
|
+
"""Get or create the asynchronous HTTP client.
|
|
80
|
+
|
|
81
|
+
The client is recreated if the event loop has changed, since
|
|
82
|
+
httpx.AsyncClient is tied to a specific event loop.
|
|
83
|
+
"""
|
|
84
|
+
# Check if we need to recreate the client
|
|
85
|
+
current_loop = None
|
|
86
|
+
try:
|
|
87
|
+
current_loop = asyncio.get_running_loop()
|
|
88
|
+
except RuntimeError:
|
|
89
|
+
pass # No running loop
|
|
90
|
+
|
|
91
|
+
# Recreate client if: no client, client closed, or event loop changed
|
|
92
|
+
needs_new_client = (
|
|
93
|
+
self._async_client is None or
|
|
94
|
+
self._async_client.is_closed or
|
|
95
|
+
(current_loop is not None and self._async_client_loop is not current_loop)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if needs_new_client:
|
|
99
|
+
# Close old client if it exists and isn't already closed
|
|
100
|
+
if self._async_client is not None and not self._async_client.is_closed:
|
|
101
|
+
try:
|
|
102
|
+
# Can't await in a property, so we just let it be garbage collected
|
|
103
|
+
pass
|
|
104
|
+
except Exception:
|
|
105
|
+
pass
|
|
106
|
+
|
|
80
107
|
transport = httpx.AsyncHTTPTransport(**self._transport_kwargs)
|
|
81
108
|
self._async_client = httpx.AsyncClient(
|
|
82
109
|
base_url=self.base_url,
|
|
@@ -85,6 +112,8 @@ class HttpClient:
|
|
|
85
112
|
limits=self._limits,
|
|
86
113
|
transport=transport,
|
|
87
114
|
)
|
|
115
|
+
self._async_client_loop = current_loop
|
|
116
|
+
|
|
88
117
|
return self._async_client
|
|
89
118
|
|
|
90
119
|
def _add_timestamp(self, data: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
|
@@ -1 +1,16 @@
|
|
|
1
|
-
"""API resource modules."""
|
|
1
|
+
"""API resource modules."""
|
|
2
|
+
from .session import SessionResource
|
|
3
|
+
from .event import EventResource
|
|
4
|
+
from .dataset import DatasetResource
|
|
5
|
+
from .experiment import ExperimentResource
|
|
6
|
+
from .prompt import PromptResource
|
|
7
|
+
from .feature_flag import FeatureFlagResource
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"SessionResource",
|
|
11
|
+
"EventResource",
|
|
12
|
+
"DatasetResource",
|
|
13
|
+
"ExperimentResource",
|
|
14
|
+
"PromptResource",
|
|
15
|
+
"FeatureFlagResource",
|
|
16
|
+
]
|