lucidicai 3.0.0__py3-none-any.whl → 3.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 +1 -1
- lucidicai/api/resources/evals.py +209 -0
- lucidicai/client.py +13 -0
- lucidicai/sdk/decorators.py +2 -2
- {lucidicai-3.0.0.dist-info → lucidicai-3.1.1.dist-info}/METADATA +1 -1
- {lucidicai-3.0.0.dist-info → lucidicai-3.1.1.dist-info}/RECORD +8 -7
- {lucidicai-3.0.0.dist-info → lucidicai-3.1.1.dist-info}/WHEEL +0 -0
- {lucidicai-3.0.0.dist-info → lucidicai-3.1.1.dist-info}/top_level.txt +0 -0
lucidicai/__init__.py
CHANGED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Evals resource API operations."""
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
from typing import Any, Dict, Optional, Union
|
|
5
|
+
|
|
6
|
+
from ..client import HttpClient
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("Lucidic")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _truncate_id(id_str: Optional[str]) -> str:
|
|
12
|
+
"""Truncate ID for logging."""
|
|
13
|
+
if not id_str:
|
|
14
|
+
return "None"
|
|
15
|
+
return f"{id_str[:8]}..." if len(id_str) > 8 else id_str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _infer_result_type(result: Any) -> str:
|
|
19
|
+
"""Infer result type from Python value.
|
|
20
|
+
|
|
21
|
+
Note: bool must be checked first because bool is a subclass of int in Python.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
result: The evaluation result value.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The result type string: "boolean", "number", or "string".
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
ValueError: If result is not a supported type.
|
|
31
|
+
"""
|
|
32
|
+
if isinstance(result, bool):
|
|
33
|
+
return "boolean"
|
|
34
|
+
elif isinstance(result, (int, float)):
|
|
35
|
+
return "number"
|
|
36
|
+
elif isinstance(result, str):
|
|
37
|
+
return "string"
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError(
|
|
40
|
+
f"Unsupported result type: {type(result).__name__}. "
|
|
41
|
+
"Must be bool, int, float, or str."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _validate_result_type(result: Any, result_type: str) -> bool:
|
|
46
|
+
"""Validate that result matches the specified result_type.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
result: The evaluation result value.
|
|
50
|
+
result_type: The expected type ("boolean", "number", "string").
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
True if the result matches the type, False otherwise.
|
|
54
|
+
"""
|
|
55
|
+
if result_type == "boolean":
|
|
56
|
+
return isinstance(result, bool)
|
|
57
|
+
elif result_type == "number":
|
|
58
|
+
# Check for bool first since bool is subclass of int
|
|
59
|
+
return isinstance(result, (int, float)) and not isinstance(result, bool)
|
|
60
|
+
elif result_type == "string":
|
|
61
|
+
return isinstance(result, str)
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class EvalsResource:
|
|
66
|
+
"""Handle evaluation-related API operations."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, http: HttpClient, production: bool = False):
|
|
69
|
+
"""Initialize evals resource.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
http: HTTP client instance
|
|
73
|
+
production: Whether to suppress errors in production mode
|
|
74
|
+
"""
|
|
75
|
+
self.http = http
|
|
76
|
+
self._production = production
|
|
77
|
+
|
|
78
|
+
def emit(
|
|
79
|
+
self,
|
|
80
|
+
result: Union[bool, int, float, str],
|
|
81
|
+
name: Optional[str] = None,
|
|
82
|
+
description: Optional[str] = None,
|
|
83
|
+
result_type: Optional[str] = None,
|
|
84
|
+
session_id: Optional[str] = None,
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Fire-and-forget evaluation submission that returns instantly.
|
|
87
|
+
|
|
88
|
+
This function returns immediately while the actual evaluation
|
|
89
|
+
submission happens in a background thread. Perfect for non-blocking
|
|
90
|
+
evaluation logging.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
result: The evaluation result. Can be bool, int, float, or str.
|
|
94
|
+
name: Optional name for the evaluation. If not provided, the backend
|
|
95
|
+
will generate a default name based on the result type.
|
|
96
|
+
description: Optional description of the evaluation.
|
|
97
|
+
result_type: Optional explicit result type ("boolean", "number", "string").
|
|
98
|
+
If not provided, it will be inferred from the result value.
|
|
99
|
+
session_id: Optional session ID. If not provided, uses the current
|
|
100
|
+
session from context.
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
# Basic usage - type inferred
|
|
104
|
+
client.evals.emit(result=True)
|
|
105
|
+
client.evals.emit(result=0.95)
|
|
106
|
+
client.evals.emit(result="excellent")
|
|
107
|
+
|
|
108
|
+
# With name and description
|
|
109
|
+
client.evals.emit(
|
|
110
|
+
result=True,
|
|
111
|
+
name="task_completed",
|
|
112
|
+
description="User task was successful"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Explicit session_id
|
|
116
|
+
client.evals.emit(result=0.87, name="accuracy_score", session_id="abc-123")
|
|
117
|
+
"""
|
|
118
|
+
from ...sdk.context import current_session_id
|
|
119
|
+
|
|
120
|
+
# Capture session from context if not provided
|
|
121
|
+
captured_session_id = session_id
|
|
122
|
+
if not captured_session_id:
|
|
123
|
+
captured_session_id = current_session_id.get(None)
|
|
124
|
+
|
|
125
|
+
if not captured_session_id:
|
|
126
|
+
logger.debug("[EvalsResource] No active session for emit()")
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
# Infer or validate result_type
|
|
130
|
+
try:
|
|
131
|
+
if result_type is None:
|
|
132
|
+
inferred_type = _infer_result_type(result)
|
|
133
|
+
else:
|
|
134
|
+
# Validate that result matches the explicit type
|
|
135
|
+
if not _validate_result_type(result, result_type):
|
|
136
|
+
error_msg = (
|
|
137
|
+
f"Result type mismatch: result is {type(result).__name__} "
|
|
138
|
+
f"but result_type is '{result_type}'"
|
|
139
|
+
)
|
|
140
|
+
if self._production:
|
|
141
|
+
logger.error(f"[EvalsResource] {error_msg}")
|
|
142
|
+
return
|
|
143
|
+
else:
|
|
144
|
+
raise ValueError(error_msg)
|
|
145
|
+
inferred_type = result_type
|
|
146
|
+
except ValueError as e:
|
|
147
|
+
if self._production:
|
|
148
|
+
logger.error(f"[EvalsResource] {e}")
|
|
149
|
+
return
|
|
150
|
+
else:
|
|
151
|
+
raise
|
|
152
|
+
|
|
153
|
+
# Capture all data for background thread
|
|
154
|
+
captured_result = result
|
|
155
|
+
captured_name = name
|
|
156
|
+
captured_description = description
|
|
157
|
+
captured_type = inferred_type
|
|
158
|
+
|
|
159
|
+
def _background_emit():
|
|
160
|
+
try:
|
|
161
|
+
params: Dict[str, Any] = {
|
|
162
|
+
"session_id": captured_session_id,
|
|
163
|
+
"result": captured_result,
|
|
164
|
+
"result_type": captured_type,
|
|
165
|
+
}
|
|
166
|
+
if captured_name is not None:
|
|
167
|
+
params["name"] = captured_name
|
|
168
|
+
if captured_description is not None:
|
|
169
|
+
params["description"] = captured_description
|
|
170
|
+
|
|
171
|
+
self._create_eval(params)
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.debug(f"[EvalsResource] Background emit() failed: {e}")
|
|
174
|
+
|
|
175
|
+
# Start background thread
|
|
176
|
+
thread = threading.Thread(target=_background_emit, daemon=True)
|
|
177
|
+
thread.start()
|
|
178
|
+
|
|
179
|
+
def _create_eval(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
180
|
+
"""Send evaluation to backend API.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
params: Evaluation parameters including:
|
|
184
|
+
- session_id: Session ID
|
|
185
|
+
- result: Evaluation result value
|
|
186
|
+
- result_type: Type of result ("boolean", "number", "string")
|
|
187
|
+
- name: Optional evaluation name
|
|
188
|
+
- description: Optional description
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
API response (typically empty for 201 Created)
|
|
192
|
+
"""
|
|
193
|
+
session_id = params.get("session_id")
|
|
194
|
+
name = params.get("name")
|
|
195
|
+
result_type = params.get("result_type")
|
|
196
|
+
logger.debug(
|
|
197
|
+
f"[Evals] _create_eval() called - "
|
|
198
|
+
f"session_id={_truncate_id(session_id)}, name={name!r}, "
|
|
199
|
+
f"result_type={result_type!r}"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
response = self.http.post("sdk/evals", params)
|
|
203
|
+
|
|
204
|
+
logger.debug(
|
|
205
|
+
f"[Evals] _create_eval() response - "
|
|
206
|
+
f"session_id={_truncate_id(session_id)}, "
|
|
207
|
+
f"response_keys={list(response.keys()) if response else 'None'}"
|
|
208
|
+
)
|
|
209
|
+
return response
|
lucidicai/client.py
CHANGED
|
@@ -30,6 +30,7 @@ from .api.resources.dataset import DatasetResource
|
|
|
30
30
|
from .api.resources.experiment import ExperimentResource
|
|
31
31
|
from .api.resources.prompt import PromptResource
|
|
32
32
|
from .api.resources.feature_flag import FeatureFlagResource
|
|
33
|
+
from .api.resources.evals import EvalsResource
|
|
33
34
|
from .core.config import SDKConfig
|
|
34
35
|
from .core.errors import LucidicError
|
|
35
36
|
from .session_obj import Session
|
|
@@ -145,6 +146,7 @@ class LucidicAI:
|
|
|
145
146
|
"experiments": ExperimentResource(self._http, self._config.agent_id, self._production),
|
|
146
147
|
"prompts": PromptResource(self._http, self._production),
|
|
147
148
|
"feature_flags": FeatureFlagResource(self._http, self._config.agent_id, self._production),
|
|
149
|
+
"evals": EvalsResource(self._http, self._production),
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
# Active sessions for this client
|
|
@@ -271,6 +273,17 @@ class LucidicAI:
|
|
|
271
273
|
"""
|
|
272
274
|
return self._resources["datasets"]
|
|
273
275
|
|
|
276
|
+
@property
|
|
277
|
+
def evals(self) -> EvalsResource:
|
|
278
|
+
"""Access evals resource for submitting evaluation results.
|
|
279
|
+
|
|
280
|
+
Example:
|
|
281
|
+
client.evals.emit(result=True, name="task_success")
|
|
282
|
+
client.evals.emit(result=0.95, name="accuracy")
|
|
283
|
+
client.evals.emit(result="excellent", name="quality")
|
|
284
|
+
"""
|
|
285
|
+
return self._resources["evals"]
|
|
286
|
+
|
|
274
287
|
# ==================== Decorators ====================
|
|
275
288
|
|
|
276
289
|
def event(
|
lucidicai/sdk/decorators.py
CHANGED
|
@@ -51,7 +51,7 @@ def _emit_event_to_client(
|
|
|
51
51
|
"session_id": session_id,
|
|
52
52
|
**event_data,
|
|
53
53
|
}
|
|
54
|
-
response = client._resources["events"].
|
|
54
|
+
response = client._resources["events"].create(**event_payload)
|
|
55
55
|
return response.get("event_id") if response else None
|
|
56
56
|
except Exception as e:
|
|
57
57
|
debug(f"[Decorator] Failed to emit event: {e}")
|
|
@@ -81,7 +81,7 @@ async def _aemit_event_to_client(
|
|
|
81
81
|
"session_id": session_id,
|
|
82
82
|
**event_data,
|
|
83
83
|
}
|
|
84
|
-
response = await client._resources["events"].
|
|
84
|
+
response = await client._resources["events"].acreate(**event_payload)
|
|
85
85
|
return response.get("event_id") if response else None
|
|
86
86
|
except Exception as e:
|
|
87
87
|
debug(f"[Decorator] Failed to emit async event: {e}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
lucidicai/__init__.py,sha256=
|
|
1
|
+
lucidicai/__init__.py,sha256=83k3iIyPlLhhZ_fRyy54x4Ry9WbRhNTK1bczhvHSj_A,1180
|
|
2
2
|
lucidicai/action.py,sha256=sPRd1hTIVXDqnvG9ZXWEipUFh0bsXcE0Fm7RVqmVccM,237
|
|
3
|
-
lucidicai/client.py,sha256=
|
|
3
|
+
lucidicai/client.py,sha256=WnkUeo_Z0uP4xh66gNC6MJhYdyhRpjC61OBjHEJLHq4,14674
|
|
4
4
|
lucidicai/constants.py,sha256=zN8O7TjoRHRlaGa9CZUWppS73rhzKGwaEkF9XMTV0Cg,1160
|
|
5
5
|
lucidicai/context.py,sha256=ruEXAndSv0gQ-YEXLlC4Fx6NNbaylfp_dZxbpwmLZSA,4622
|
|
6
6
|
lucidicai/dataset.py,sha256=wu25X02JyWkht_yQabgQpGZFfzbNTxG6tf5k9ol8Amo,4005
|
|
@@ -22,6 +22,7 @@ lucidicai/api/__init__.py,sha256=UOYuFZupG0TgzMAxbLNgpodDXhDRXBgMva8ZblgBN9Y,31
|
|
|
22
22
|
lucidicai/api/client.py,sha256=0Ia5cXo5OKifhP-63TPAWPuy2bWCzR9VN4BpWIMo34w,13212
|
|
23
23
|
lucidicai/api/resources/__init__.py,sha256=DDgviDW3Su-G1ofkZGlaJMc2pzYJqrbBnEruNg1whCM,416
|
|
24
24
|
lucidicai/api/resources/dataset.py,sha256=I6g9ah4vaqEH1jyeouBn7xvC0oAuDNPeyl-bmtNj-T0,17400
|
|
25
|
+
lucidicai/api/resources/evals.py,sha256=_3nLE6dMLht844mWw7kl_hctjv5JIuC6MP06YWUgLnI,7235
|
|
25
26
|
lucidicai/api/resources/event.py,sha256=GTIU5sIbLNTWAHk4rB120xWTRkhnraz9JNfamEygyNo,14267
|
|
26
27
|
lucidicai/api/resources/experiment.py,sha256=fOIKJ5d89bHJBVZ3wjbhY_6XF3kLHz9TE3BVPA5pNpA,3563
|
|
27
28
|
lucidicai/api/resources/feature_flag.py,sha256=ii412DIkZCEAhrXdGydcpQKveqGlFq4NlgdmWQnU83c,2259
|
|
@@ -51,7 +52,7 @@ lucidicai/providers/universal_image_interceptor.py,sha256=7d-hw4xihRwvvA1AP8-vqY
|
|
|
51
52
|
lucidicai/sdk/__init__.py,sha256=UrkV9FYbZkBxaX9qwxGbCJdXp-JqMpn0_u-huO9Y-ec,32
|
|
52
53
|
lucidicai/sdk/bound_decorators.py,sha256=SzmNZwORhArXL9D8T8BpPltT-jQ-tVpy71t8bJWOIU0,12151
|
|
53
54
|
lucidicai/sdk/context.py,sha256=y58_C9JlBML_xFPUbmAn6WuxsnM03bECJ2pKBWz0TuQ,10386
|
|
54
|
-
lucidicai/sdk/decorators.py,sha256
|
|
55
|
+
lucidicai/sdk/decorators.py,sha256=FV0259ki6x26d2-YKZJwkUNzHain-QGghq94OzAk2Os,15652
|
|
55
56
|
lucidicai/sdk/error_boundary.py,sha256=IPr5wS9rS7ZQNgEaBwK53UaixAm6L2rijKKFfxcxjUI,9190
|
|
56
57
|
lucidicai/sdk/event.py,sha256=hpBJfqKteOuQKoZfhxQfbeVOrdmR8wCcQc8P6658VRo,22658
|
|
57
58
|
lucidicai/sdk/event_builder.py,sha256=Z376RKlStM7IBcAm5LKgTDh3x_fjmcvkWltUrjZ6RAc,10304
|
|
@@ -90,7 +91,7 @@ lucidicai/utils/images.py,sha256=z8mlIKgFfrIbuk-l4L2rB62uw_uPO79sHPXPY7eLu2A,128
|
|
|
90
91
|
lucidicai/utils/logger.py,sha256=R3B3gSee64F6UVHUrShihBq_O7W7bgfrBiVDXTO3Isg,4777
|
|
91
92
|
lucidicai/utils/queue.py,sha256=8DQwnGw7pINEJ0dNSkB0PhdPW-iBQQ-YZg23poe4umE,17323
|
|
92
93
|
lucidicai/utils/serialization.py,sha256=KdOREZd7XBxFBAZ86DePMfYPzSVyKr4RcgUa82aFxrs,820
|
|
93
|
-
lucidicai-3.
|
|
94
|
-
lucidicai-3.
|
|
95
|
-
lucidicai-3.
|
|
96
|
-
lucidicai-3.
|
|
94
|
+
lucidicai-3.1.1.dist-info/METADATA,sha256=OCfPUqhEyRmBCQWTOoKUwbw3hR7T6JNZY9XtxyUbXxE,902
|
|
95
|
+
lucidicai-3.1.1.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
|
|
96
|
+
lucidicai-3.1.1.dist-info/top_level.txt,sha256=vSSdM3lclF4I5tyVC0xxUk8eIRnnYXMe1hW-eO91HUo,10
|
|
97
|
+
lucidicai-3.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|