lucidicai 2.0.1__tar.gz → 2.0.2__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.
Files changed (36) hide show
  1. {lucidicai-2.0.1 → lucidicai-2.0.2}/PKG-INFO +1 -1
  2. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/__init__.py +24 -0
  3. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/client.py +4 -1
  4. lucidicai-2.0.2/lucidicai/dataset.py +114 -0
  5. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/errors.py +6 -0
  6. lucidicai-2.0.2/lucidicai/feature_flag.py +344 -0
  7. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai.egg-info/PKG-INFO +1 -1
  8. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai.egg-info/SOURCES.txt +2 -0
  9. {lucidicai-2.0.1 → lucidicai-2.0.2}/setup.py +1 -1
  10. {lucidicai-2.0.1 → lucidicai-2.0.2}/README.md +0 -0
  11. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/constants.py +0 -0
  12. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/context.py +0 -0
  13. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/decorators.py +0 -0
  14. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/event.py +0 -0
  15. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/event_queue.py +0 -0
  16. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/image_upload.py +0 -0
  17. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/lru.py +0 -0
  18. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/model_pricing.py +0 -0
  19. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/session.py +0 -0
  20. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/singleton.py +0 -0
  21. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/streaming.py +0 -0
  22. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/telemetry/__init__.py +0 -0
  23. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/telemetry/context_capture_processor.py +0 -0
  24. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/telemetry/extract.py +0 -0
  25. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/telemetry/litellm_bridge.py +0 -0
  26. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/telemetry/lucidic_exporter.py +0 -0
  27. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/telemetry/openai_agents_instrumentor.py +0 -0
  28. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/telemetry/telemetry_init.py +0 -0
  29. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/telemetry/utils/__init__.py +0 -0
  30. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/telemetry/utils/image_storage.py +0 -0
  31. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/telemetry/utils/text_storage.py +0 -0
  32. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai/telemetry/utils/universal_image_interceptor.py +0 -0
  33. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai.egg-info/dependency_links.txt +0 -0
  34. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai.egg-info/requires.txt +0 -0
  35. {lucidicai-2.0.1 → lucidicai-2.0.2}/lucidicai.egg-info/top_level.txt +0 -0
  36. {lucidicai-2.0.1 → lucidicai-2.0.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lucidicai
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: Lucidic AI Python SDK
5
5
  Author: Andy Liang
6
6
  Author-email: andy@lucidic.ai
@@ -28,6 +28,17 @@ from .context import (
28
28
  run_session,
29
29
  run_in_session,
30
30
  )
31
+ from .dataset import get_dataset, get_dataset_items
32
+ from .feature_flag import (
33
+ get_feature_flag,
34
+ get_bool_flag,
35
+ get_int_flag,
36
+ get_float_flag,
37
+ get_string_flag,
38
+ get_json_flag,
39
+ clear_feature_flag_cache,
40
+ FeatureFlagError
41
+ )
31
42
 
32
43
  ProviderType = Literal[
33
44
  "openai",
@@ -229,6 +240,16 @@ __all__ = [
229
240
  'end_session',
230
241
  'get_prompt',
231
242
  'get_session',
243
+ 'get_dataset',
244
+ 'get_dataset_items',
245
+ 'get_feature_flag',
246
+ 'get_bool_flag',
247
+ 'get_int_flag',
248
+ 'get_float_flag',
249
+ 'get_string_flag',
250
+ 'get_json_flag',
251
+ 'clear_feature_flag_cache',
252
+ 'FeatureFlagError',
232
253
  'ProviderType',
233
254
  'APIKeyVerificationError',
234
255
  'LucidicNotInitializedError',
@@ -257,6 +278,7 @@ def init(
257
278
  experiment_id: Optional[str] = None,
258
279
  rubrics: Optional[list] = None,
259
280
  tags: Optional[list] = None,
281
+ dataset_item_id: Optional[str] = None,
260
282
  masking_function = None,
261
283
  auto_end: Optional[bool] = True,
262
284
  capture_uncaught: Optional[bool] = True,
@@ -274,6 +296,7 @@ def init(
274
296
  experiment_id: Optional experiment ID, if session is to be part of an experiment.
275
297
  rubrics: Optional rubrics for evaluation, list of strings.
276
298
  tags: Optional tags for the session, list of strings.
299
+ dataset_item_id: Optional dataset item ID to link session to a dataset item.
277
300
  masking_function: Optional function to mask sensitive data.
278
301
  auto_end: If True, automatically end the session on process exit. Defaults to True.
279
302
 
@@ -328,6 +351,7 @@ def init(
328
351
  production_monitoring=production_monitoring,
329
352
  session_id=session_id,
330
353
  experiment_id=experiment_id,
354
+ dataset_item_id=dataset_item_id,
331
355
  )
332
356
  if masking_function:
333
357
  client.masking_function = masking_function
@@ -98,6 +98,7 @@ class Client:
98
98
  production_monitoring: Optional[bool] = False,
99
99
  session_id: Optional[str] = None,
100
100
  experiment_id: Optional[str] = None,
101
+ dataset_item_id: Optional[str] = None,
101
102
  ) -> None:
102
103
  if session_id:
103
104
  # Check if it's a known session ID, maybe custom and maybe real
@@ -122,7 +123,9 @@ class Client:
122
123
  "experiment_id": experiment_id,
123
124
  "rubrics": rubrics,
124
125
  "tags": tags,
125
- "session_id": session_id
126
+ "session_id": session_id,
127
+ "dataset_item_id": dataset_item_id,
128
+ "production_monitoring": production_monitoring
126
129
  }
127
130
  data = self.make_request('initsession', 'POST', request_data)
128
131
  real_session_id = data["session_id"]
@@ -0,0 +1,114 @@
1
+ import os
2
+ import logging
3
+ from typing import Optional, Dict, List, Any
4
+ from dotenv import load_dotenv
5
+
6
+ from .client import Client
7
+ from .errors import APIKeyVerificationError
8
+
9
+ logger = logging.getLogger("Lucidic")
10
+
11
+
12
+ def get_dataset(
13
+ dataset_id: str,
14
+ api_key: Optional[str] = None,
15
+ agent_id: Optional[str] = None,
16
+ ) -> Dict[str, Any]:
17
+ """
18
+ Get a dataset by ID with all its items.
19
+
20
+ Args:
21
+ dataset_id: The ID of the dataset to retrieve (required).
22
+ api_key: API key for authentication. If not provided, will use the LUCIDIC_API_KEY environment variable.
23
+ agent_id: Agent ID. If not provided, will use the LUCIDIC_AGENT_ID environment variable.
24
+
25
+ Returns:
26
+ A dictionary containing the dataset information including:
27
+ - dataset_id: The dataset ID
28
+ - name: Dataset name
29
+ - description: Dataset description
30
+ - tags: List of tags
31
+ - created_at: Creation timestamp
32
+ - updated_at: Last update timestamp
33
+ - num_items: Number of items in the dataset
34
+ - items: List of dataset items
35
+
36
+ Raises:
37
+ APIKeyVerificationError: If API key or agent ID is missing or invalid.
38
+ ValueError: If dataset_id is not provided.
39
+ """
40
+ return # no op for now
41
+ load_dotenv()
42
+
43
+ # Validation
44
+ if not dataset_id:
45
+ raise ValueError("Dataset ID is required")
46
+
47
+ # Get credentials
48
+ if api_key is None:
49
+ api_key = os.getenv("LUCIDIC_API_KEY", None)
50
+ if api_key is None:
51
+ raise APIKeyVerificationError(
52
+ "Make sure to either pass your API key into get_dataset() or set the LUCIDIC_API_KEY environment variable."
53
+ )
54
+
55
+ if agent_id is None:
56
+ agent_id = os.getenv("LUCIDIC_AGENT_ID", None)
57
+ if agent_id is None:
58
+ raise APIKeyVerificationError(
59
+ "Lucidic agent ID not specified. Make sure to either pass your agent ID into get_dataset() or set the LUCIDIC_AGENT_ID environment variable."
60
+ )
61
+
62
+ # Get current client or create a new one
63
+ client = Client()
64
+ # If not yet initialized or still the NullClient -> create a real client
65
+ if not getattr(client, 'initialized', False):
66
+ client = Client(api_key=api_key, agent_id=agent_id)
67
+ else:
68
+ # Already initialized, check if we need to update credentials
69
+ if api_key is not None and agent_id is not None and (api_key != client.api_key or agent_id != client.agent_id):
70
+ client.set_api_key(api_key)
71
+ client.agent_id = agent_id
72
+
73
+ # Make request to get dataset
74
+ response = client.make_request(
75
+ 'getdataset',
76
+ 'GET',
77
+ {'dataset_id': dataset_id}
78
+ )
79
+
80
+ logger.info(f"Retrieved dataset {dataset_id} with {response.get('num_items', 0)} items")
81
+ return response
82
+
83
+
84
+ def get_dataset_items(
85
+ dataset_id: str,
86
+ api_key: Optional[str] = None,
87
+ agent_id: Optional[str] = None,
88
+ ) -> List[Dict[str, Any]]:
89
+ """
90
+ Convenience function to get just the items from a dataset.
91
+
92
+ Args:
93
+ dataset_id: The ID of the dataset to retrieve items from (required).
94
+ api_key: API key for authentication. If not provided, will use the LUCIDIC_API_KEY environment variable.
95
+ agent_id: Agent ID. If not provided, will use the LUCIDIC_AGENT_ID environment variable.
96
+
97
+ Returns:
98
+ A list of dataset items, where each item contains:
99
+ - dataset_item_id: The item ID
100
+ - name: Item name
101
+ - description: Item description
102
+ - tags: List of tags
103
+ - input: Input data for the item
104
+ - expected_output: Expected output data
105
+ - metadata: Additional metadata
106
+ - created_at: Creation timestamp
107
+
108
+ Raises:
109
+ APIKeyVerificationError: If API key or agent ID is missing or invalid.
110
+ ValueError: If dataset_id is not provided.
111
+ """
112
+ return # no op for now
113
+ dataset = get_dataset(dataset_id, api_key, agent_id)
114
+ return dataset.get('items', [])
@@ -23,6 +23,12 @@ class InvalidOperationError(Exception):
23
23
  super().__init__(f"An invalid Lucidic operation was attempted: {message}")
24
24
 
25
25
 
26
+ class FeatureFlagError(Exception):
27
+ """Exception for feature flag fetch failures"""
28
+ def __init__(self, message: str):
29
+ super().__init__(f"Failed to fetch feature flag: {message}")
30
+
31
+
26
32
  def install_error_handler():
27
33
  """Install global handler to create ERROR_TRACEBACK events for uncaught exceptions."""
28
34
  from .client import Client
@@ -0,0 +1,344 @@
1
+ import os
2
+ import logging
3
+ import time
4
+ from typing import Union, List, Dict, Any, Optional, overload, Tuple, Literal
5
+ from dotenv import load_dotenv
6
+
7
+ from .client import Client
8
+ from .errors import APIKeyVerificationError, FeatureFlagError
9
+
10
+ logger = logging.getLogger("Lucidic")
11
+
12
+ # Cache implementation
13
+ class FeatureFlagCache:
14
+ def __init__(self):
15
+ self._cache: Dict[str, tuple[Any, float]] = {}
16
+ self._default_ttl = 300 # 5 minutes
17
+
18
+ def get(self, key: str) -> Optional[Any]:
19
+ if key in self._cache:
20
+ value, expiry = self._cache[key]
21
+ if time.time() < expiry:
22
+ return value
23
+ else:
24
+ del self._cache[key]
25
+ return None
26
+
27
+ def set(self, key: str, value: Any, ttl: int = None):
28
+ if ttl is None:
29
+ ttl = self._default_ttl
30
+ if ttl > 0:
31
+ self._cache[key] = (value, time.time() + ttl)
32
+
33
+ def clear(self):
34
+ self._cache.clear()
35
+
36
+ # Global cache instance
37
+ _flag_cache = FeatureFlagCache()
38
+
39
+ # Sentinel value to distinguish None from missing
40
+ MISSING = object()
41
+
42
+ # Function overloads for type safety
43
+ @overload
44
+ def get_feature_flag(
45
+ flag_name: str,
46
+ default: Any = ...,
47
+ *,
48
+ return_missing: Literal[False] = False,
49
+ cache_ttl: Optional[int] = 300,
50
+ api_key: Optional[str] = None,
51
+ agent_id: Optional[str] = None,
52
+ ) -> Any:
53
+ """Get a single feature flag."""
54
+ ...
55
+
56
+ @overload
57
+ def get_feature_flag(
58
+ flag_name: str,
59
+ default: Any = ...,
60
+ *,
61
+ return_missing: Literal[True],
62
+ cache_ttl: Optional[int] = 300,
63
+ api_key: Optional[str] = None,
64
+ agent_id: Optional[str] = None,
65
+ ) -> Tuple[Any, List[str]]:
66
+ """Get a single feature flag with missing info."""
67
+ ...
68
+
69
+ @overload
70
+ def get_feature_flag(
71
+ flag_name: List[str],
72
+ defaults: Optional[Dict[str, Any]] = None,
73
+ *,
74
+ return_missing: Literal[False] = False,
75
+ cache_ttl: Optional[int] = 300,
76
+ api_key: Optional[str] = None,
77
+ agent_id: Optional[str] = None,
78
+ ) -> Dict[str, Any]:
79
+ """Get multiple feature flags."""
80
+ ...
81
+
82
+ @overload
83
+ def get_feature_flag(
84
+ flag_name: List[str],
85
+ defaults: Optional[Dict[str, Any]] = None,
86
+ *,
87
+ return_missing: Literal[True],
88
+ cache_ttl: Optional[int] = 300,
89
+ api_key: Optional[str] = None,
90
+ agent_id: Optional[str] = None,
91
+ ) -> Tuple[Dict[str, Any], List[str]]:
92
+ """Get multiple feature flags with missing info."""
93
+ ...
94
+
95
+ def get_feature_flag(
96
+ flag_name: Union[str, List[str]],
97
+ default_or_defaults: Any = MISSING,
98
+ *,
99
+ return_missing: bool = False,
100
+ cache_ttl: Optional[int] = 300,
101
+ api_key: Optional[str] = None,
102
+ agent_id: Optional[str] = None,
103
+ ) -> Union[Any, Tuple[Any, List[str]], Dict[str, Any], Tuple[Dict[str, Any], List[str]]]:
104
+ """
105
+ Get feature flag(s) from backend. Raises FeatureFlagError on failure unless default provided.
106
+
107
+ Args:
108
+ flag_name: Single flag name (str) or list of flag names
109
+ default_or_defaults:
110
+ - If flag_name is str: default value for that flag (optional)
111
+ - If flag_name is List[str]: dict of defaults {flag_name: default_value}
112
+ cache_ttl: Cache time-to-live in seconds (0 to disable, -1 for forever)
113
+ api_key: Optional API key
114
+ agent_id: Optional agent ID
115
+
116
+ Returns:
117
+ - If flag_name is str: The flag value (or tuple with missing list if return_missing=True)
118
+ - If flag_name is List[str]: Dict mapping flag_name -> value (or tuple with missing list if return_missing=True)
119
+
120
+ Raises:
121
+ FeatureFlagError: If fetch fails and no default provided
122
+ APIKeyVerificationError: If credentials missing
123
+
124
+ Examples:
125
+ # Single flag with default
126
+ retries = lai.get_feature_flag("max_retries", default=3)
127
+
128
+ # Single flag without default (can raise)
129
+ retries = lai.get_feature_flag("max_retries")
130
+
131
+ # Multiple flags
132
+ flags = lai.get_feature_flag(
133
+ ["max_retries", "timeout"],
134
+ defaults={"max_retries": 3}
135
+ )
136
+ """
137
+
138
+ return # no op for now
139
+
140
+ load_dotenv()
141
+
142
+ # Determine if single or batch
143
+ is_single = isinstance(flag_name, str)
144
+ flag_names = [flag_name] if is_single else flag_name
145
+
146
+ # Parse defaults
147
+ if is_single:
148
+ has_default = default_or_defaults is not MISSING
149
+ defaults = {flag_name: default_or_defaults} if has_default else {}
150
+ else:
151
+ defaults = default_or_defaults if default_or_defaults not in (None, MISSING) else {}
152
+
153
+ # Track missing flags
154
+ missing_flags = []
155
+
156
+ # Check cache first
157
+ uncached_flags = []
158
+ cached_results = {}
159
+
160
+ if cache_ttl != 0:
161
+ for name in flag_names:
162
+ cache_key = f"{agent_id}:{name}"
163
+ cached_value = _flag_cache.get(cache_key)
164
+ if cached_value is not None:
165
+ cached_results[name] = cached_value
166
+ else:
167
+ uncached_flags.append(name)
168
+ else:
169
+ uncached_flags = flag_names
170
+
171
+ # Fetch uncached flags if needed
172
+ if uncached_flags:
173
+ # Get credentials
174
+ if api_key is None:
175
+ api_key = os.getenv("LUCIDIC_API_KEY", None)
176
+ if api_key is None:
177
+ raise APIKeyVerificationError(
178
+ "Make sure to either pass your API key or set the LUCIDIC_API_KEY environment variable."
179
+ )
180
+
181
+ if agent_id is None:
182
+ agent_id = os.getenv("LUCIDIC_AGENT_ID", None)
183
+ if agent_id is None:
184
+ raise APIKeyVerificationError(
185
+ "Lucidic agent ID not specified. Make sure to either pass your agent ID or set the LUCIDIC_AGENT_ID environment variable."
186
+ )
187
+
188
+ # Get client
189
+ client = Client()
190
+ if not getattr(client, 'initialized', False):
191
+ client = Client(api_key=api_key, agent_id=agent_id)
192
+ else:
193
+ if api_key != client.api_key or agent_id != client.agent_id:
194
+ client.set_api_key(api_key)
195
+ client.agent_id = agent_id
196
+
197
+ try:
198
+ # Make batch API call
199
+ response = client.make_request(
200
+ 'getfeatureflags',
201
+ 'POST',
202
+ {'flag_names': uncached_flags}
203
+ )
204
+
205
+ # Process response and update cache
206
+ for name in uncached_flags:
207
+ if name in response['flags']:
208
+ if response['flags'][name]['found']:
209
+ value = response['flags'][name]['value']
210
+ cached_results[name] = value
211
+
212
+ # Cache the value
213
+ if cache_ttl != 0:
214
+ cache_key = f"{agent_id}:{name}"
215
+ _flag_cache.set(cache_key, value, ttl=cache_ttl if cache_ttl > 0 else None)
216
+ else:
217
+ # Flag not found on server
218
+ missing_flags.append(name)
219
+ logger.warning(f"Feature flag '{name}' not found on server")
220
+
221
+ except Exception as e:
222
+ # Log the error
223
+ logger.error(f"Failed to fetch feature flags: {e}")
224
+
225
+ # Check if we have defaults for missing flags
226
+ for name in uncached_flags:
227
+ if name not in cached_results:
228
+ if name in defaults:
229
+ cached_results[name] = defaults[name]
230
+ elif is_single and not return_missing:
231
+ # Single flag without default and not returning missing - raise error
232
+ raise FeatureFlagError(f"'{name}': {e}") from e
233
+
234
+ # Build final result
235
+ result = {}
236
+ for name in flag_names:
237
+ if name in cached_results:
238
+ result[name] = cached_results[name]
239
+ elif name in defaults:
240
+ result[name] = defaults[name]
241
+ else:
242
+ # No value and no default
243
+ missing_flags.append(name)
244
+ if is_single and not return_missing:
245
+ raise FeatureFlagError(f"'{name}' not found and no default provided")
246
+ else:
247
+ result[name] = None
248
+
249
+ # Return based on input type and return_missing flag
250
+ if return_missing:
251
+ return (result[flag_names[0]] if is_single else result, missing_flags)
252
+ else:
253
+ return result[flag_names[0]] if is_single else result
254
+
255
+
256
+ # Typed convenience functions
257
+ def get_bool_flag(flag_name: str, default: Optional[bool] = None, **kwargs) -> bool:
258
+ """
259
+ Get a boolean feature flag with type validation.
260
+
261
+ Raises:
262
+ FeatureFlagError: If fetch fails and no default provided
263
+ TypeError: If flag value is not a boolean
264
+ """
265
+ return # no op for now
266
+ value = get_feature_flag(flag_name, default=default if default is not None else MISSING, **kwargs)
267
+ if not isinstance(value, bool):
268
+ if default is not None:
269
+ logger.warning(f"Feature flag '{flag_name}' is not a boolean, using default")
270
+ return default
271
+ raise TypeError(f"Feature flag '{flag_name}' expected boolean, got {type(value).__name__}")
272
+ return value
273
+
274
+
275
+ def get_int_flag(flag_name: str, default: Optional[int] = None, **kwargs) -> int:
276
+ """
277
+ Get an integer feature flag with type validation.
278
+
279
+ Raises:
280
+ FeatureFlagError: If fetch fails and no default provided
281
+ TypeError: If flag value is not an integer
282
+ """
283
+ return # no op for now
284
+ value = get_feature_flag(flag_name, default=default if default is not None else MISSING, **kwargs)
285
+ if not isinstance(value, int) or isinstance(value, bool): # bool is subclass of int
286
+ if default is not None:
287
+ logger.warning(f"Feature flag '{flag_name}' is not an integer, using default")
288
+ return default
289
+ raise TypeError(f"Feature flag '{flag_name}' expected integer, got {type(value).__name__}")
290
+ return value
291
+
292
+
293
+ def get_float_flag(flag_name: str, default: Optional[float] = None, **kwargs) -> float:
294
+ """
295
+ Get a float feature flag with type validation.
296
+
297
+ Raises:
298
+ FeatureFlagError: If fetch fails and no default provided
299
+ TypeError: If flag value is not a float
300
+ """
301
+ return # no op for now
302
+ value = get_feature_flag(flag_name, default=default if default is not None else MISSING, **kwargs)
303
+ if not isinstance(value, (int, float)) or isinstance(value, bool):
304
+ if default is not None:
305
+ logger.warning(f"Feature flag '{flag_name}' is not a float, using default")
306
+ return default
307
+ raise TypeError(f"Feature flag '{flag_name}' expected float, got {type(value).__name__}")
308
+ return float(value)
309
+
310
+
311
+ def get_string_flag(flag_name: str, default: Optional[str] = None, **kwargs) -> str:
312
+ """
313
+ Get a string feature flag with type validation.
314
+
315
+ Raises:
316
+ FeatureFlagError: If fetch fails and no default provided
317
+ TypeError: If flag value is not a string
318
+ """
319
+ return # no op for now
320
+ value = get_feature_flag(flag_name, default=default if default is not None else MISSING, **kwargs)
321
+ if not isinstance(value, str):
322
+ if default is not None:
323
+ logger.warning(f"Feature flag '{flag_name}' is not a string, using default")
324
+ return default
325
+ raise TypeError(f"Feature flag '{flag_name}' expected string, got {type(value).__name__}")
326
+ return value
327
+
328
+
329
+ def get_json_flag(flag_name: str, default: Optional[dict] = None, **kwargs) -> dict:
330
+ """
331
+ Get a JSON object feature flag.
332
+
333
+ Raises:
334
+ FeatureFlagError: If fetch fails and no default provided
335
+ """
336
+ return # no op for now
337
+ value = get_feature_flag(flag_name, default=default if default is not None else MISSING, **kwargs)
338
+ return value
339
+
340
+
341
+ def clear_feature_flag_cache():
342
+ """Clear the feature flag cache."""
343
+ _flag_cache.clear()
344
+ logger.debug("Feature flag cache cleared")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lucidicai
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: Lucidic AI Python SDK
5
5
  Author: Andy Liang
6
6
  Author-email: andy@lucidic.ai
@@ -4,10 +4,12 @@ lucidicai/__init__.py
4
4
  lucidicai/client.py
5
5
  lucidicai/constants.py
6
6
  lucidicai/context.py
7
+ lucidicai/dataset.py
7
8
  lucidicai/decorators.py
8
9
  lucidicai/errors.py
9
10
  lucidicai/event.py
10
11
  lucidicai/event_queue.py
12
+ lucidicai/feature_flag.py
11
13
  lucidicai/image_upload.py
12
14
  lucidicai/lru.py
13
15
  lucidicai/model_pricing.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="lucidicai",
5
- version="2.0.1",
5
+ version="2.0.2",
6
6
  packages=find_packages(),
7
7
  install_requires=[
8
8
  "requests>=2.25.1",
File without changes
File without changes
File without changes
File without changes