lmnr 0.6.11__py3-none-any.whl → 0.6.13__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.
- lmnr/__init__.py +2 -0
- lmnr/opentelemetry_lib/__init__.py +1 -1
- lmnr/opentelemetry_lib/litellm/__init__.py +371 -0
- lmnr/opentelemetry_lib/litellm/utils.py +18 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +40 -32
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py +9 -3
- lmnr/sdk/evaluations.py +58 -51
- lmnr/sdk/types.py +4 -12
- lmnr/version.py +1 -1
- {lmnr-0.6.11.dist-info → lmnr-0.6.13.dist-info}/METADATA +55 -55
- {lmnr-0.6.11.dist-info → lmnr-0.6.13.dist-info}/RECORD +14 -12
- {lmnr-0.6.11.dist-info → lmnr-0.6.13.dist-info}/LICENSE +0 -0
- {lmnr-0.6.11.dist-info → lmnr-0.6.13.dist-info}/WHEEL +0 -0
- {lmnr-0.6.11.dist-info → lmnr-0.6.13.dist-info}/entry_points.txt +0 -0
lmnr/__init__.py
CHANGED
@@ -13,6 +13,7 @@ from .sdk.types import (
|
|
13
13
|
)
|
14
14
|
from .sdk.decorators import observe
|
15
15
|
from .sdk.types import LaminarSpanContext
|
16
|
+
from .opentelemetry_lib.litellm import LaminarLiteLLMCallback
|
16
17
|
from .opentelemetry_lib.tracing.attributes import Attributes
|
17
18
|
from .opentelemetry_lib.tracing.instruments import Instruments
|
18
19
|
from .opentelemetry_lib.tracing.processor import LaminarSpanProcessor
|
@@ -30,6 +31,7 @@ __all__ = [
|
|
30
31
|
"Laminar",
|
31
32
|
"LaminarClient",
|
32
33
|
"LaminarDataset",
|
34
|
+
"LaminarLiteLLMCallback",
|
33
35
|
"LaminarSpanContext",
|
34
36
|
"LaminarSpanProcessor",
|
35
37
|
"RunAgentResponseChunk",
|
@@ -7,7 +7,7 @@ from opentelemetry.sdk.resources import SERVICE_NAME
|
|
7
7
|
from lmnr.opentelemetry_lib.tracing.instruments import Instruments
|
8
8
|
from lmnr.opentelemetry_lib.tracing import TracerWrapper
|
9
9
|
|
10
|
-
MAX_MANUAL_SPAN_PAYLOAD_SIZE = 1024 * 1024 #
|
10
|
+
MAX_MANUAL_SPAN_PAYLOAD_SIZE = 1024 * 1024 * 10 # 10MB
|
11
11
|
|
12
12
|
|
13
13
|
class TracerManager:
|
@@ -0,0 +1,371 @@
|
|
1
|
+
"""LiteLLM callback logger for Laminar"""
|
2
|
+
|
3
|
+
import json
|
4
|
+
from datetime import datetime
|
5
|
+
|
6
|
+
from opentelemetry.trace import SpanKind, Status, StatusCode, Tracer
|
7
|
+
from lmnr.opentelemetry_lib.litellm.utils import model_as_dict, set_span_attribute
|
8
|
+
from lmnr.opentelemetry_lib.tracing import TracerWrapper
|
9
|
+
|
10
|
+
from lmnr.opentelemetry_lib.utils.package_check import is_package_installed
|
11
|
+
from lmnr.sdk.log import get_default_logger
|
12
|
+
|
13
|
+
logger = get_default_logger(__name__)
|
14
|
+
|
15
|
+
SUPPORTED_CALL_TYPES = ["completion", "acompletion"]
|
16
|
+
|
17
|
+
# Try to import the necessary LiteLLM components and gracefully handle ImportError
|
18
|
+
try:
|
19
|
+
if not is_package_installed("litellm"):
|
20
|
+
raise ImportError("LiteLLM is not installed")
|
21
|
+
|
22
|
+
from litellm.integrations.custom_batch_logger import CustomBatchLogger
|
23
|
+
|
24
|
+
class LaminarLiteLLMCallback(CustomBatchLogger):
|
25
|
+
"""Custom LiteLLM logger that sends logs to Laminar via OpenTelemetry spans
|
26
|
+
|
27
|
+
Usage:
|
28
|
+
import litellm
|
29
|
+
from lmnr import Laminar, LaminarLiteLLMCallback
|
30
|
+
|
31
|
+
# make sure this comes first
|
32
|
+
Laminar.initialize()
|
33
|
+
|
34
|
+
# Add the logger to LiteLLM callbacks
|
35
|
+
litellm.callbacks = [LaminarLiteLLMCallback()]
|
36
|
+
"""
|
37
|
+
|
38
|
+
def __init__(self, **kwargs):
|
39
|
+
super().__init__(**kwargs)
|
40
|
+
if not hasattr(TracerWrapper, "instance") or TracerWrapper.instance is None:
|
41
|
+
raise ValueError("Laminar must be initialized before LiteLLM callback")
|
42
|
+
|
43
|
+
def _get_tracer(self) -> Tracer:
|
44
|
+
if not hasattr(TracerWrapper, "instance") or TracerWrapper.instance is None:
|
45
|
+
raise ValueError("Laminar must be initialized before LiteLLM callback")
|
46
|
+
return TracerWrapper().get_tracer()
|
47
|
+
|
48
|
+
def log_success_event(
|
49
|
+
self, kwargs, response_obj, start_time: datetime, end_time: datetime
|
50
|
+
):
|
51
|
+
if kwargs.get("call_type") not in SUPPORTED_CALL_TYPES:
|
52
|
+
return
|
53
|
+
try:
|
54
|
+
self._create_span(
|
55
|
+
kwargs, response_obj, start_time, end_time, is_success=True
|
56
|
+
)
|
57
|
+
except Exception as e:
|
58
|
+
logger.error(f"Error in log_success_event: {e}")
|
59
|
+
|
60
|
+
def log_failure_event(
|
61
|
+
self, kwargs, response_obj, start_time: datetime, end_time: datetime
|
62
|
+
):
|
63
|
+
if kwargs.get("call_type") not in SUPPORTED_CALL_TYPES:
|
64
|
+
return
|
65
|
+
try:
|
66
|
+
self._create_span(
|
67
|
+
kwargs, response_obj, start_time, end_time, is_success=False
|
68
|
+
)
|
69
|
+
except Exception as e:
|
70
|
+
logger.error(f"Error in log_failure_event: {e}")
|
71
|
+
|
72
|
+
async def async_log_success_event(
|
73
|
+
self, kwargs, response_obj, start_time: datetime, end_time: datetime
|
74
|
+
):
|
75
|
+
self.log_success_event(kwargs, response_obj, start_time, end_time)
|
76
|
+
|
77
|
+
async def async_log_failure_event(
|
78
|
+
self, kwargs, response_obj, start_time: datetime, end_time: datetime
|
79
|
+
):
|
80
|
+
self.log_failure_event(kwargs, response_obj, start_time, end_time)
|
81
|
+
|
82
|
+
def _create_span(
|
83
|
+
self,
|
84
|
+
kwargs,
|
85
|
+
response_obj,
|
86
|
+
start_time: datetime,
|
87
|
+
end_time: datetime,
|
88
|
+
is_success: bool,
|
89
|
+
):
|
90
|
+
"""Create an OpenTelemetry span for the LiteLLM call"""
|
91
|
+
span_name = "litellm.completion"
|
92
|
+
try:
|
93
|
+
tracer = self._get_tracer()
|
94
|
+
except Exception as e:
|
95
|
+
logger.error(f"Error getting tracer: {e}")
|
96
|
+
return
|
97
|
+
span = tracer.start_span(
|
98
|
+
span_name,
|
99
|
+
kind=SpanKind.CLIENT,
|
100
|
+
start_time=int(start_time.timestamp() * 1e9),
|
101
|
+
attributes={
|
102
|
+
"lmnr.internal.provider": "litellm",
|
103
|
+
},
|
104
|
+
)
|
105
|
+
try:
|
106
|
+
model = kwargs.get("model", "unknown")
|
107
|
+
if kwargs.get("custom_llm_provider"):
|
108
|
+
set_span_attribute(
|
109
|
+
span, "gen_ai.system", kwargs["custom_llm_provider"]
|
110
|
+
)
|
111
|
+
|
112
|
+
messages = kwargs.get("messages", [])
|
113
|
+
self._process_input_messages(span, messages)
|
114
|
+
|
115
|
+
tools = kwargs.get("tools", [])
|
116
|
+
self._process_request_tool_definitions(span, tools)
|
117
|
+
|
118
|
+
set_span_attribute(span, "gen_ai.request.model", model)
|
119
|
+
|
120
|
+
# Add more attributes from kwargs
|
121
|
+
if "temperature" in kwargs:
|
122
|
+
set_span_attribute(
|
123
|
+
span, "gen_ai.request.temperature", kwargs["temperature"]
|
124
|
+
)
|
125
|
+
if "max_tokens" in kwargs:
|
126
|
+
set_span_attribute(
|
127
|
+
span, "gen_ai.request.max_tokens", kwargs["max_tokens"]
|
128
|
+
)
|
129
|
+
if "top_p" in kwargs:
|
130
|
+
set_span_attribute(span, "gen_ai.request.top_p", kwargs["top_p"])
|
131
|
+
|
132
|
+
if is_success:
|
133
|
+
span.set_status(Status(StatusCode.OK))
|
134
|
+
if kwargs.get("complete_streaming_response"):
|
135
|
+
self._process_success_response(
|
136
|
+
span,
|
137
|
+
kwargs.get("complete_streaming_response"),
|
138
|
+
)
|
139
|
+
else:
|
140
|
+
self._process_success_response(span, response_obj)
|
141
|
+
else:
|
142
|
+
span.set_status(Status(StatusCode.ERROR))
|
143
|
+
if isinstance(response_obj, Exception):
|
144
|
+
span.record_exception(response_obj)
|
145
|
+
|
146
|
+
except Exception as e:
|
147
|
+
span.record_exception(e)
|
148
|
+
logger.error(f"Error in Laminar LiteLLM instrumentation: {e}")
|
149
|
+
finally:
|
150
|
+
span.end(int(end_time.timestamp() * 1e9))
|
151
|
+
|
152
|
+
def _process_input_messages(self, span, messages):
|
153
|
+
"""Process and set message attributes on the span"""
|
154
|
+
if not isinstance(messages, list):
|
155
|
+
return
|
156
|
+
|
157
|
+
for i, message in enumerate(messages):
|
158
|
+
message_dict = model_as_dict(message)
|
159
|
+
role = message_dict.get("role", "unknown")
|
160
|
+
set_span_attribute(span, f"gen_ai.prompt.{i}.role", role)
|
161
|
+
|
162
|
+
tool_calls = message_dict.get("tool_calls", [])
|
163
|
+
self._process_tool_calls(span, tool_calls, i, is_response=False)
|
164
|
+
|
165
|
+
content = message_dict.get("content", "")
|
166
|
+
if content is None:
|
167
|
+
continue
|
168
|
+
if isinstance(content, str):
|
169
|
+
set_span_attribute(span, f"gen_ai.prompt.{i}.content", content)
|
170
|
+
elif isinstance(content, list):
|
171
|
+
set_span_attribute(
|
172
|
+
span, f"gen_ai.prompt.{i}.content", json.dumps(content)
|
173
|
+
)
|
174
|
+
else:
|
175
|
+
set_span_attribute(
|
176
|
+
span,
|
177
|
+
f"gen_ai.prompt.{i}.content",
|
178
|
+
json.dumps(model_as_dict(content)),
|
179
|
+
)
|
180
|
+
if role == "tool":
|
181
|
+
set_span_attribute(
|
182
|
+
span,
|
183
|
+
f"gen_ai.prompt.{i}.tool_call_id",
|
184
|
+
message_dict.get("tool_call_id"),
|
185
|
+
)
|
186
|
+
|
187
|
+
def _process_request_tool_definitions(self, span, tools):
|
188
|
+
"""Process and set tool definitions attributes on the span"""
|
189
|
+
if not isinstance(tools, list):
|
190
|
+
return
|
191
|
+
|
192
|
+
for i, tool in enumerate(tools):
|
193
|
+
tool_dict = model_as_dict(tool)
|
194
|
+
if tool_dict.get("type") != "function":
|
195
|
+
# TODO: parse other tool types
|
196
|
+
continue
|
197
|
+
|
198
|
+
function_dict = tool_dict.get("function", {})
|
199
|
+
function_name = function_dict.get("name", "")
|
200
|
+
function_description = function_dict.get("description", "")
|
201
|
+
function_parameters = function_dict.get("parameters", {})
|
202
|
+
set_span_attribute(
|
203
|
+
span,
|
204
|
+
f"llm.request.functions.{i}.name",
|
205
|
+
function_name,
|
206
|
+
)
|
207
|
+
set_span_attribute(
|
208
|
+
span,
|
209
|
+
f"llm.request.functions.{i}.description",
|
210
|
+
function_description,
|
211
|
+
)
|
212
|
+
set_span_attribute(
|
213
|
+
span,
|
214
|
+
f"llm.request.functions.{i}.parameters",
|
215
|
+
json.dumps(function_parameters),
|
216
|
+
)
|
217
|
+
|
218
|
+
def _process_response_usage(self, span, usage):
|
219
|
+
"""Process and set usage attributes on the span"""
|
220
|
+
usage_dict = model_as_dict(usage)
|
221
|
+
if (
|
222
|
+
not usage_dict.get("prompt_tokens")
|
223
|
+
and not usage_dict.get("completion_tokens")
|
224
|
+
and not usage_dict.get("total_tokens")
|
225
|
+
):
|
226
|
+
return
|
227
|
+
|
228
|
+
set_span_attribute(
|
229
|
+
span, "gen_ai.usage.input_tokens", usage_dict.get("prompt_tokens")
|
230
|
+
)
|
231
|
+
set_span_attribute(
|
232
|
+
span, "gen_ai.usage.output_tokens", usage_dict.get("completion_tokens")
|
233
|
+
)
|
234
|
+
set_span_attribute(
|
235
|
+
span, "llm.usage.total_tokens", usage_dict.get("total_tokens")
|
236
|
+
)
|
237
|
+
|
238
|
+
if usage_dict.get("prompt_tokens_details"):
|
239
|
+
details = usage_dict.get("prompt_tokens_details", {})
|
240
|
+
details = model_as_dict(details)
|
241
|
+
if details.get("cached_tokens"):
|
242
|
+
set_span_attribute(
|
243
|
+
span,
|
244
|
+
"gen_ai.usage.cache_read_input_tokens",
|
245
|
+
details.get("cached_tokens"),
|
246
|
+
)
|
247
|
+
# TODO: add audio/image/text token details
|
248
|
+
# TODO: add completion tokens details (reasoning tokens)
|
249
|
+
|
250
|
+
def _process_tool_calls(self, span, tool_calls, choice_index, is_response=True):
|
251
|
+
"""Process and set tool call attributes on the span"""
|
252
|
+
attr_prefix = "completion" if is_response else "prompt"
|
253
|
+
if not isinstance(tool_calls, list):
|
254
|
+
return
|
255
|
+
|
256
|
+
for j, tool_call in enumerate(tool_calls):
|
257
|
+
tool_call_dict = model_as_dict(tool_call)
|
258
|
+
|
259
|
+
tool_name = tool_call_dict.get(
|
260
|
+
"name", tool_call_dict.get("function", {}).get("name", "")
|
261
|
+
)
|
262
|
+
set_span_attribute(
|
263
|
+
span,
|
264
|
+
f"gen_ai.{attr_prefix}.{choice_index}.tool_calls.{j}.name",
|
265
|
+
tool_name,
|
266
|
+
)
|
267
|
+
|
268
|
+
call_id = tool_call_dict.get("id", "")
|
269
|
+
set_span_attribute(
|
270
|
+
span,
|
271
|
+
f"gen_ai.{attr_prefix}.{choice_index}.tool_calls.{j}.id",
|
272
|
+
call_id,
|
273
|
+
)
|
274
|
+
|
275
|
+
tool_arguments = tool_call_dict.get(
|
276
|
+
"arguments", tool_call_dict.get("function", {}).get("arguments", "")
|
277
|
+
)
|
278
|
+
if isinstance(tool_arguments, str):
|
279
|
+
set_span_attribute(
|
280
|
+
span,
|
281
|
+
f"gen_ai.{attr_prefix}.{choice_index}.tool_calls.{j}.arguments",
|
282
|
+
tool_arguments,
|
283
|
+
)
|
284
|
+
else:
|
285
|
+
set_span_attribute(
|
286
|
+
span,
|
287
|
+
f"gen_ai.{attr_prefix}.{choice_index}.tool_calls.{j}.arguments",
|
288
|
+
json.dumps(model_as_dict(tool_arguments)),
|
289
|
+
)
|
290
|
+
|
291
|
+
def _process_response_choices(self, span, choices):
|
292
|
+
"""Process and set choice attributes on the span"""
|
293
|
+
if not isinstance(choices, list):
|
294
|
+
return
|
295
|
+
|
296
|
+
for i, choice in enumerate(choices):
|
297
|
+
choice_dict = model_as_dict(choice)
|
298
|
+
message = choice_dict.get("message", choice_dict)
|
299
|
+
|
300
|
+
role = message.get("role", "unknown")
|
301
|
+
set_span_attribute(span, f"gen_ai.completion.{i}.role", role)
|
302
|
+
|
303
|
+
tool_calls = message.get("tool_calls", [])
|
304
|
+
self._process_tool_calls(span, tool_calls, i, is_response=True)
|
305
|
+
|
306
|
+
content = message.get("content", "")
|
307
|
+
if content is None:
|
308
|
+
continue
|
309
|
+
if isinstance(content, str):
|
310
|
+
set_span_attribute(span, f"gen_ai.completion.{i}.content", content)
|
311
|
+
elif isinstance(content, list):
|
312
|
+
set_span_attribute(
|
313
|
+
span, f"gen_ai.completion.{i}.content", json.dumps(content)
|
314
|
+
)
|
315
|
+
else:
|
316
|
+
set_span_attribute(
|
317
|
+
span,
|
318
|
+
f"gen_ai.completion.{i}.content",
|
319
|
+
json.dumps(model_as_dict(content)),
|
320
|
+
)
|
321
|
+
|
322
|
+
def _process_success_response(self, span, response_obj):
|
323
|
+
"""Process successful response attributes"""
|
324
|
+
response_dict = model_as_dict(response_obj)
|
325
|
+
set_span_attribute(span, "gen_ai.response.id", response_dict.get("id"))
|
326
|
+
set_span_attribute(
|
327
|
+
span, "gen_ai.response.model", response_dict.get("model")
|
328
|
+
)
|
329
|
+
|
330
|
+
if response_dict.get("usage"):
|
331
|
+
self._process_response_usage(span, response_dict.get("usage"))
|
332
|
+
|
333
|
+
if response_dict.get("cache_creation_input_tokens"):
|
334
|
+
set_span_attribute(
|
335
|
+
span,
|
336
|
+
"gen_ai.usage.cache_creation_input_tokens",
|
337
|
+
response_dict.get("cache_creation_input_tokens"),
|
338
|
+
)
|
339
|
+
if response_dict.get("cache_read_input_tokens"):
|
340
|
+
set_span_attribute(
|
341
|
+
span,
|
342
|
+
"gen_ai.usage.cache_read_input_tokens",
|
343
|
+
response_dict.get("cache_read_input_tokens"),
|
344
|
+
)
|
345
|
+
|
346
|
+
if response_dict.get("choices"):
|
347
|
+
self._process_response_choices(span, response_dict.get("choices"))
|
348
|
+
|
349
|
+
except ImportError as e:
|
350
|
+
logger.debug(f"LiteLLM callback unavailable: {e}")
|
351
|
+
|
352
|
+
# Create a no-op logger when LiteLLM is not available
|
353
|
+
class LaminarLiteLLMCallback:
|
354
|
+
"""No-op logger when LiteLLM is not available"""
|
355
|
+
|
356
|
+
def __init__(self, **kwargs):
|
357
|
+
logger.warning(
|
358
|
+
"LiteLLM is not installed. Install with: pip install litellm"
|
359
|
+
)
|
360
|
+
|
361
|
+
def log_success_event(self, *args, **kwargs):
|
362
|
+
pass
|
363
|
+
|
364
|
+
def log_failure_event(self, *args, **kwargs):
|
365
|
+
pass
|
366
|
+
|
367
|
+
async def async_log_success_event(self, *args, **kwargs):
|
368
|
+
pass
|
369
|
+
|
370
|
+
async def async_log_failure_event(self, *args, **kwargs):
|
371
|
+
pass
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
from opentelemetry.sdk.trace import Span
|
3
|
+
from opentelemetry.util.types import AttributeValue
|
4
|
+
|
5
|
+
|
6
|
+
def model_as_dict(model: BaseModel | dict) -> dict:
|
7
|
+
if isinstance(model, BaseModel) and hasattr(model, "model_dump"):
|
8
|
+
return model.model_dump()
|
9
|
+
elif isinstance(model, dict):
|
10
|
+
return model
|
11
|
+
else:
|
12
|
+
return dict(model)
|
13
|
+
|
14
|
+
|
15
|
+
def set_span_attribute(span: Span, key: str, value: AttributeValue | None):
|
16
|
+
if value is None or value == "":
|
17
|
+
return
|
18
|
+
span.set_attribute(key, value)
|
@@ -12,7 +12,6 @@ from .config import (
|
|
12
12
|
Config,
|
13
13
|
)
|
14
14
|
from .utils import (
|
15
|
-
ProcessedContentPart,
|
16
15
|
dont_throw,
|
17
16
|
get_content,
|
18
17
|
role_from_content_union,
|
@@ -130,27 +129,30 @@ def _set_request_attributes(span, args, kwargs):
|
|
130
129
|
)
|
131
130
|
|
132
131
|
tools: list[types.FunctionDeclaration] = []
|
133
|
-
|
134
|
-
|
132
|
+
arg_tools = config_dict.get("tools", kwargs.get("tools"))
|
133
|
+
if arg_tools:
|
134
|
+
for tool in arg_tools:
|
135
135
|
if isinstance(tool, types.Tool):
|
136
136
|
tools += tool.function_declarations or []
|
137
137
|
elif isinstance(tool, Callable):
|
138
138
|
tools.append(types.FunctionDeclaration.from_callable(tool))
|
139
|
+
|
139
140
|
for tool_num, tool in enumerate(tools):
|
141
|
+
tool_dict = to_dict(tool)
|
140
142
|
set_span_attribute(
|
141
143
|
span,
|
142
144
|
f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{tool_num}.name",
|
143
|
-
|
145
|
+
tool_dict.get("name"),
|
144
146
|
)
|
145
147
|
set_span_attribute(
|
146
148
|
span,
|
147
149
|
f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{tool_num}.description",
|
148
|
-
|
150
|
+
tool_dict.get("description"),
|
149
151
|
)
|
150
152
|
set_span_attribute(
|
151
153
|
span,
|
152
154
|
f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{tool_num}.parameters",
|
153
|
-
|
155
|
+
json.dumps(tool_dict.get("parameters")),
|
154
156
|
)
|
155
157
|
|
156
158
|
if should_send_prompts():
|
@@ -162,7 +164,9 @@ def _set_request_attributes(span, args, kwargs):
|
|
162
164
|
set_span_attribute(
|
163
165
|
span,
|
164
166
|
f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.content",
|
165
|
-
(get_content(process_content_union(system_instruction)) or {}).get(
|
167
|
+
(get_content(process_content_union(system_instruction)) or {}).get(
|
168
|
+
"text", ""
|
169
|
+
),
|
166
170
|
)
|
167
171
|
set_span_attribute(
|
168
172
|
span, f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.role", "system"
|
@@ -174,6 +178,7 @@ def _set_request_attributes(span, args, kwargs):
|
|
174
178
|
for content in contents:
|
175
179
|
processed_content = process_content_union(content)
|
176
180
|
content_str = get_content(processed_content)
|
181
|
+
|
177
182
|
set_span_attribute(
|
178
183
|
span,
|
179
184
|
f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.content",
|
@@ -188,26 +193,35 @@ def _set_request_attributes(span, args, kwargs):
|
|
188
193
|
if isinstance(processed_content, list)
|
189
194
|
else [processed_content]
|
190
195
|
)
|
191
|
-
|
196
|
+
tool_call_index = 0
|
197
|
+
for block in blocks:
|
192
198
|
block_dict = to_dict(block)
|
199
|
+
|
193
200
|
if not block_dict.get("function_call"):
|
194
201
|
continue
|
195
202
|
function_call = to_dict(block_dict.get("function_call", {}))
|
203
|
+
|
196
204
|
set_span_attribute(
|
197
205
|
span,
|
198
|
-
f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.tool_calls.{
|
206
|
+
f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.tool_calls.{tool_call_index}.name",
|
199
207
|
function_call.get("name"),
|
200
208
|
)
|
201
209
|
set_span_attribute(
|
202
210
|
span,
|
203
|
-
f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.tool_calls.{
|
204
|
-
|
211
|
+
f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.tool_calls.{tool_call_index}.id",
|
212
|
+
(
|
213
|
+
function_call.get("id")
|
214
|
+
if function_call.get("id") is not None
|
215
|
+
else function_call.get("name")
|
216
|
+
), # google genai doesn't support tool call ids
|
205
217
|
)
|
206
218
|
set_span_attribute(
|
207
219
|
span,
|
208
|
-
f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.tool_calls.{
|
220
|
+
f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.tool_calls.{tool_call_index}.arguments",
|
209
221
|
json.dumps(function_call.get("arguments")),
|
210
222
|
)
|
223
|
+
tool_call_index += 1
|
224
|
+
|
211
225
|
set_span_attribute(
|
212
226
|
span,
|
213
227
|
f"{gen_ai_attributes.GEN_AI_PROMPT}.{i}.role",
|
@@ -258,21 +272,8 @@ def _set_response_attributes(span, response: types.GenerateContentResponse):
|
|
258
272
|
candidates_list = candidates if isinstance(candidates, list) else [candidates]
|
259
273
|
for i, candidate in enumerate(candidates_list):
|
260
274
|
processed_content = process_content_union(candidate.content)
|
261
|
-
|
262
|
-
|
263
|
-
isinstance(item, dict) and item.get("type") == "text"
|
264
|
-
for item in processed_content
|
265
|
-
):
|
266
|
-
content_str = processed_content[0]["text"]
|
267
|
-
elif all(
|
268
|
-
isinstance(item, ProcessedContentPart) and item.content
|
269
|
-
for item in processed_content
|
270
|
-
):
|
271
|
-
content_str = processed_content[0].content
|
272
|
-
else:
|
273
|
-
content_str = get_content(processed_content)
|
274
|
-
else:
|
275
|
-
content_str = get_content(processed_content)
|
275
|
+
content_str = get_content(processed_content)
|
276
|
+
|
276
277
|
set_span_attribute(
|
277
278
|
span, f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.role", "model"
|
278
279
|
)
|
@@ -290,26 +291,33 @@ def _set_response_attributes(span, response: types.GenerateContentResponse):
|
|
290
291
|
if isinstance(processed_content, list)
|
291
292
|
else [processed_content]
|
292
293
|
)
|
293
|
-
|
294
|
+
|
295
|
+
tool_call_index = 0
|
296
|
+
for block in blocks:
|
294
297
|
block_dict = to_dict(block)
|
295
298
|
if not block_dict.get("function_call"):
|
296
299
|
continue
|
297
300
|
function_call = to_dict(block_dict.get("function_call", {}))
|
298
301
|
set_span_attribute(
|
299
302
|
span,
|
300
|
-
f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.tool_calls.{
|
303
|
+
f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.tool_calls.{tool_call_index}.name",
|
301
304
|
function_call.get("name"),
|
302
305
|
)
|
303
306
|
set_span_attribute(
|
304
307
|
span,
|
305
|
-
f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.tool_calls.{
|
306
|
-
|
308
|
+
f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.tool_calls.{tool_call_index}.id",
|
309
|
+
(
|
310
|
+
function_call.get("id")
|
311
|
+
if function_call.get("id") is not None
|
312
|
+
else function_call.get("name")
|
313
|
+
), # google genai doesn't support tool call ids
|
307
314
|
)
|
308
315
|
set_span_attribute(
|
309
316
|
span,
|
310
|
-
f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.tool_calls.{
|
317
|
+
f"{gen_ai_attributes.GEN_AI_COMPLETION}.{i}.tool_calls.{tool_call_index}.arguments",
|
311
318
|
json.dumps(function_call.get("arguments")),
|
312
319
|
)
|
320
|
+
tool_call_index += 1
|
313
321
|
|
314
322
|
|
315
323
|
@dont_throw
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import base64
|
1
2
|
import logging
|
2
3
|
import traceback
|
3
4
|
|
@@ -74,7 +75,8 @@ def to_dict(obj: BaseModel | pydantic.BaseModel | dict) -> dict[str, Any]:
|
|
74
75
|
return obj
|
75
76
|
else:
|
76
77
|
return dict(obj)
|
77
|
-
except Exception:
|
78
|
+
except Exception as e:
|
79
|
+
logging.error(f"Error converting to dict: {obj}, error: {e}")
|
78
80
|
return dict(obj)
|
79
81
|
|
80
82
|
|
@@ -96,7 +98,7 @@ def get_content(
|
|
96
98
|
else:
|
97
99
|
return None
|
98
100
|
elif isinstance(content, list):
|
99
|
-
return [get_content(item)
|
101
|
+
return [get_content(item) for item in content]
|
100
102
|
elif isinstance(content, str):
|
101
103
|
return {
|
102
104
|
"type": "text",
|
@@ -226,11 +228,15 @@ def _process_image_item(
|
|
226
228
|
content_index: int,
|
227
229
|
) -> ProcessedContentPart | dict | None:
|
228
230
|
# Convert to openai format, so backends can handle it
|
231
|
+
data = blob.get("data")
|
232
|
+
encoded_data = (
|
233
|
+
base64.b64encode(data).decode("utf-8") if isinstance(data, bytes) else data
|
234
|
+
)
|
229
235
|
return (
|
230
236
|
ProcessedContentPart(
|
231
237
|
image_url=ImageUrl(
|
232
238
|
image_url=ImageUrlInner(
|
233
|
-
url=f"data:image/{blob.get('mime_type').split('/')[1]};base64,{
|
239
|
+
url=f"data:image/{blob.get('mime_type').split('/')[1]};base64,{encoded_data}",
|
234
240
|
)
|
235
241
|
)
|
236
242
|
)
|
lmnr/sdk/evaluations.py
CHANGED
@@ -56,7 +56,11 @@ def get_average_scores(results: list[EvaluationResultDatapoint]) -> dict[str, Nu
|
|
56
56
|
|
57
57
|
average_scores = {}
|
58
58
|
for key, values in per_score_values.items():
|
59
|
-
|
59
|
+
scores = [v for v in values if v is not None]
|
60
|
+
|
61
|
+
# If there are no scores, we don't want to include the key in the average scores
|
62
|
+
if len(scores) > 0:
|
63
|
+
average_scores[key] = sum(scores) / len(scores)
|
60
64
|
|
61
65
|
return average_scores
|
62
66
|
|
@@ -97,8 +101,7 @@ class Evaluation:
|
|
97
101
|
self,
|
98
102
|
data: EvaluationDataset | list[Datapoint | dict],
|
99
103
|
executor: Any,
|
100
|
-
evaluators: dict[str, EvaluatorFunction],
|
101
|
-
human_evaluators: list[HumanEvaluator] = [],
|
104
|
+
evaluators: dict[str, EvaluatorFunction | HumanEvaluator],
|
102
105
|
name: str | None = None,
|
103
106
|
group_name: str | None = None,
|
104
107
|
concurrency_limit: int = DEFAULT_BATCH_SIZE,
|
@@ -123,17 +126,15 @@ class Evaluation:
|
|
123
126
|
executor (Callable[..., Any]): The executor function.\
|
124
127
|
Takes the data point + any additional arguments and returns\
|
125
128
|
the output to evaluate.
|
126
|
-
evaluators (dict[str, Callable[..., Any]]): Evaluator
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
If the score is a single number
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
evaluator only holds the queue name.
|
136
|
-
Defaults to an empty list.
|
129
|
+
evaluators (dict[str, Callable[..., Any] | HumanEvaluator]): Evaluator\
|
130
|
+
functions and HumanEvaluator instances with names. Each evaluator\
|
131
|
+
function takes the output of the executor _and_ the target data,\
|
132
|
+
and returns a score. The score can be a single number or a dict\
|
133
|
+
of string keys and number values. If the score is a single number,\
|
134
|
+
it will be named after the evaluator function.\
|
135
|
+
HumanEvaluator instances create empty spans for manual evaluation.\
|
136
|
+
Evaluator names must contain only letters, digits, hyphens,\
|
137
|
+
underscores, or spaces.
|
137
138
|
name (str | None, optional): Optional name of the evaluation.\
|
138
139
|
Used to identify the evaluation in the group.\
|
139
140
|
If not provided, a random name will be generated.
|
@@ -194,7 +195,6 @@ class Evaluation:
|
|
194
195
|
self.concurrency_limit = concurrency_limit
|
195
196
|
self.batch_size = concurrency_limit
|
196
197
|
self._logger = get_default_logger(self.__class__.__name__)
|
197
|
-
self.human_evaluators = human_evaluators
|
198
198
|
self.upload_tasks = []
|
199
199
|
self.base_http_url = f"{base_url}:{http_port or 443}"
|
200
200
|
|
@@ -345,24 +345,40 @@ class Evaluation:
|
|
345
345
|
# Iterate over evaluators
|
346
346
|
scores: dict[str, Numeric] = {}
|
347
347
|
for evaluator_name, evaluator in self.evaluators.items():
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
# If evaluator returns a single number, use evaluator name as key
|
362
|
-
if isinstance(value, NumericTypes):
|
363
|
-
scores[evaluator_name] = value
|
348
|
+
# Check if evaluator is a HumanEvaluator instance
|
349
|
+
if isinstance(evaluator, HumanEvaluator):
|
350
|
+
# Create an empty span for human evaluators
|
351
|
+
with L.start_as_current_span(
|
352
|
+
evaluator_name,
|
353
|
+
input={"output": output, "target": target}
|
354
|
+
) as human_evaluator_span:
|
355
|
+
human_evaluator_span.set_attribute(SPAN_TYPE, SpanType.HUMAN_EVALUATOR.value)
|
356
|
+
# Human evaluators don't execute automatically, just create the span
|
357
|
+
L.set_span_output(None)
|
358
|
+
|
359
|
+
# We don't want to save the score for human evaluators
|
360
|
+
scores[evaluator_name] = None
|
364
361
|
else:
|
365
|
-
|
362
|
+
# Regular evaluator function
|
363
|
+
with L.start_as_current_span(
|
364
|
+
evaluator_name,
|
365
|
+
input={"output": output, "target": target}
|
366
|
+
) as evaluator_span:
|
367
|
+
evaluator_span.set_attribute(SPAN_TYPE, SpanType.EVALUATOR.value)
|
368
|
+
if is_async(evaluator):
|
369
|
+
value = await evaluator(output, target)
|
370
|
+
else:
|
371
|
+
loop = asyncio.get_event_loop()
|
372
|
+
value = await loop.run_in_executor(
|
373
|
+
None, evaluator, output, target
|
374
|
+
)
|
375
|
+
L.set_span_output(value)
|
376
|
+
|
377
|
+
# If evaluator returns a single number, use evaluator name as key
|
378
|
+
if isinstance(value, NumericTypes):
|
379
|
+
scores[evaluator_name] = value
|
380
|
+
else:
|
381
|
+
scores.update(value)
|
366
382
|
|
367
383
|
trace_id = uuid.UUID(int=evaluation_span.get_span_context().trace_id)
|
368
384
|
|
@@ -373,10 +389,6 @@ class Evaluation:
|
|
373
389
|
executor_output=output,
|
374
390
|
scores=scores,
|
375
391
|
trace_id=trace_id,
|
376
|
-
# For now add all human evaluators to all result datapoints
|
377
|
-
# In the future, we will add ways to specify which human evaluators
|
378
|
-
# to add to which result datapoints, e.g. sample some randomly
|
379
|
-
human_evaluators=self.human_evaluators,
|
380
392
|
executor_span_id=executor_span_id,
|
381
393
|
index=index,
|
382
394
|
metadata=datapoint.metadata,
|
@@ -394,8 +406,7 @@ class Evaluation:
|
|
394
406
|
def evaluate(
|
395
407
|
data: EvaluationDataset | list[Datapoint | dict],
|
396
408
|
executor: ExecutorFunction,
|
397
|
-
evaluators: dict[str, EvaluatorFunction],
|
398
|
-
human_evaluators: list[HumanEvaluator] = [],
|
409
|
+
evaluators: dict[str, EvaluatorFunction | HumanEvaluator],
|
399
410
|
name: str | None = None,
|
400
411
|
group_name: str | None = None,
|
401
412
|
concurrency_limit: int = DEFAULT_BATCH_SIZE,
|
@@ -424,18 +435,15 @@ def evaluate(
|
|
424
435
|
executor (Callable[..., Any]): The executor function.\
|
425
436
|
Takes the data point + any additional arguments\
|
426
437
|
and returns the output to evaluate.
|
427
|
-
evaluators (
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
[Beta] List of instances of HumanEvaluator. For now, human\
|
437
|
-
evaluator only holds the queue name.
|
438
|
-
Defaults to an empty list.
|
438
|
+
evaluators (dict[str, Callable[..., Any] | HumanEvaluator]): Evaluator\
|
439
|
+
functions and HumanEvaluator instances with names. Each evaluator\
|
440
|
+
function takes the output of the executor _and_ the target data,\
|
441
|
+
and returns a score. The score can be a single number or a dict\
|
442
|
+
of string keys and number values. If the score is a single number,\
|
443
|
+
it will be named after the evaluator function.\
|
444
|
+
HumanEvaluator instances create empty spans for manual evaluation.\
|
445
|
+
Evaluator function names must contain only letters, digits, hyphens,\
|
446
|
+
underscores, or spaces.
|
439
447
|
name (str | None, optional): Optional name of the evaluation.\
|
440
448
|
Used to identify the evaluation in the group. If not provided, a\
|
441
449
|
random name will be generated.
|
@@ -470,7 +478,6 @@ def evaluate(
|
|
470
478
|
executor=executor,
|
471
479
|
evaluators=evaluators,
|
472
480
|
group_name=group_name,
|
473
|
-
human_evaluators=human_evaluators,
|
474
481
|
name=name,
|
475
482
|
concurrency_limit=concurrency_limit,
|
476
483
|
project_api_key=project_api_key,
|
lmnr/sdk/types.py
CHANGED
@@ -9,7 +9,7 @@ import uuid
|
|
9
9
|
|
10
10
|
from enum import Enum
|
11
11
|
from opentelemetry.trace import SpanContext, TraceFlags
|
12
|
-
from typing import Any, Awaitable, Callable, Literal
|
12
|
+
from typing import Any, Awaitable, Callable, Literal, Optional
|
13
13
|
|
14
14
|
from .utils import serialize
|
15
15
|
|
@@ -50,7 +50,7 @@ EvaluatorFunction = Callable[
|
|
50
50
|
|
51
51
|
|
52
52
|
class HumanEvaluator(pydantic.BaseModel):
|
53
|
-
|
53
|
+
pass
|
54
54
|
|
55
55
|
|
56
56
|
class InitEvaluationResponse(pydantic.BaseModel):
|
@@ -94,8 +94,7 @@ class EvaluationResultDatapoint(pydantic.BaseModel):
|
|
94
94
|
data: EvaluationDatapointData
|
95
95
|
target: EvaluationDatapointTarget
|
96
96
|
executor_output: ExecutorFunctionReturnType
|
97
|
-
scores: dict[str, Numeric]
|
98
|
-
human_evaluators: list[HumanEvaluator] = pydantic.Field(default_factory=list)
|
97
|
+
scores: dict[str, Optional[Numeric]]
|
99
98
|
trace_id: uuid.UUID
|
100
99
|
executor_span_id: uuid.UUID
|
101
100
|
metadata: EvaluationDatapointMetadata = pydantic.Field(default=None)
|
@@ -112,14 +111,6 @@ class EvaluationResultDatapoint(pydantic.BaseModel):
|
|
112
111
|
"executorOutput": str(serialize(self.executor_output))[:100],
|
113
112
|
"scores": self.scores,
|
114
113
|
"traceId": str(self.trace_id),
|
115
|
-
"humanEvaluators": [
|
116
|
-
(
|
117
|
-
v.model_dump()
|
118
|
-
if isinstance(v, pydantic.BaseModel)
|
119
|
-
else serialize(v)
|
120
|
-
)
|
121
|
-
for v in self.human_evaluators
|
122
|
-
],
|
123
114
|
"executorSpanId": str(self.executor_span_id),
|
124
115
|
"index": self.index,
|
125
116
|
"metadata": (
|
@@ -136,6 +127,7 @@ class SpanType(Enum):
|
|
136
127
|
PIPELINE = "PIPELINE" # must not be set manually
|
137
128
|
EXECUTOR = "EXECUTOR"
|
138
129
|
EVALUATOR = "EVALUATOR"
|
130
|
+
HUMAN_EVALUATOR = "HUMAN_EVALUATOR"
|
139
131
|
EVALUATION = "EVALUATION"
|
140
132
|
|
141
133
|
|
lmnr/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: lmnr
|
3
|
-
Version: 0.6.
|
3
|
+
Version: 0.6.13
|
4
4
|
Summary: Python SDK for Laminar
|
5
5
|
License: Apache-2.0
|
6
6
|
Author: lmnr.ai
|
@@ -46,61 +46,61 @@ Requires-Dist: httpx (>=0.25.0)
|
|
46
46
|
Requires-Dist: opentelemetry-api (>=1.33.0)
|
47
47
|
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc (>=1.33.0)
|
48
48
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.33.0)
|
49
|
-
Requires-Dist: opentelemetry-instrumentation-alephalpha (>=0.40.
|
50
|
-
Requires-Dist: opentelemetry-instrumentation-alephalpha (>=0.40.
|
51
|
-
Requires-Dist: opentelemetry-instrumentation-anthropic (>=0.40.
|
52
|
-
Requires-Dist: opentelemetry-instrumentation-anthropic (>=0.40.
|
53
|
-
Requires-Dist: opentelemetry-instrumentation-bedrock (>=0.40.
|
54
|
-
Requires-Dist: opentelemetry-instrumentation-bedrock (>=0.40.
|
55
|
-
Requires-Dist: opentelemetry-instrumentation-chromadb (>=0.40.
|
56
|
-
Requires-Dist: opentelemetry-instrumentation-chromadb (>=0.40.
|
57
|
-
Requires-Dist: opentelemetry-instrumentation-cohere (>=0.40.
|
58
|
-
Requires-Dist: opentelemetry-instrumentation-cohere (>=0.40.
|
59
|
-
Requires-Dist: opentelemetry-instrumentation-crewai (>=0.40.
|
60
|
-
Requires-Dist: opentelemetry-instrumentation-crewai (>=0.40.
|
61
|
-
Requires-Dist: opentelemetry-instrumentation-google-generativeai (>=0.40.
|
62
|
-
Requires-Dist: opentelemetry-instrumentation-google-generativeai (>=0.40.
|
63
|
-
Requires-Dist: opentelemetry-instrumentation-groq (>=0.40.
|
64
|
-
Requires-Dist: opentelemetry-instrumentation-groq (>=0.40.
|
65
|
-
Requires-Dist: opentelemetry-instrumentation-haystack (>=0.40.
|
66
|
-
Requires-Dist: opentelemetry-instrumentation-haystack (>=0.40.
|
67
|
-
Requires-Dist: opentelemetry-instrumentation-lancedb (>=0.40.
|
68
|
-
Requires-Dist: opentelemetry-instrumentation-lancedb (>=0.40.
|
69
|
-
Requires-Dist: opentelemetry-instrumentation-langchain (>=0.40.
|
70
|
-
Requires-Dist: opentelemetry-instrumentation-langchain (>=0.40.
|
71
|
-
Requires-Dist: opentelemetry-instrumentation-llamaindex (>=0.40.
|
72
|
-
Requires-Dist: opentelemetry-instrumentation-llamaindex (>=0.40.
|
73
|
-
Requires-Dist: opentelemetry-instrumentation-marqo (>=0.40.
|
74
|
-
Requires-Dist: opentelemetry-instrumentation-marqo (>=0.40.
|
75
|
-
Requires-Dist: opentelemetry-instrumentation-mcp (>=0.40.
|
76
|
-
Requires-Dist: opentelemetry-instrumentation-mcp (>=0.40.
|
77
|
-
Requires-Dist: opentelemetry-instrumentation-milvus (>=0.40.
|
78
|
-
Requires-Dist: opentelemetry-instrumentation-milvus (>=0.40.
|
79
|
-
Requires-Dist: opentelemetry-instrumentation-mistralai (>=0.40.
|
80
|
-
Requires-Dist: opentelemetry-instrumentation-mistralai (>=0.40.
|
81
|
-
Requires-Dist: opentelemetry-instrumentation-ollama (>=0.40.
|
82
|
-
Requires-Dist: opentelemetry-instrumentation-ollama (>=0.40.
|
83
|
-
Requires-Dist: opentelemetry-instrumentation-openai (>=0.40.
|
84
|
-
Requires-Dist: opentelemetry-instrumentation-openai (>=0.40.
|
85
|
-
Requires-Dist: opentelemetry-instrumentation-pinecone (>=0.40.
|
86
|
-
Requires-Dist: opentelemetry-instrumentation-pinecone (>=0.40.
|
87
|
-
Requires-Dist: opentelemetry-instrumentation-qdrant (>=0.40.
|
88
|
-
Requires-Dist: opentelemetry-instrumentation-qdrant (>=0.40.
|
89
|
-
Requires-Dist: opentelemetry-instrumentation-replicate (>=0.40.
|
90
|
-
Requires-Dist: opentelemetry-instrumentation-replicate (>=0.40.
|
91
|
-
Requires-Dist: opentelemetry-instrumentation-sagemaker (>=0.40.
|
92
|
-
Requires-Dist: opentelemetry-instrumentation-sagemaker (>=0.40.
|
49
|
+
Requires-Dist: opentelemetry-instrumentation-alephalpha (>=0.40.12) ; extra == "alephalpha"
|
50
|
+
Requires-Dist: opentelemetry-instrumentation-alephalpha (>=0.40.12) ; extra == "all"
|
51
|
+
Requires-Dist: opentelemetry-instrumentation-anthropic (>=0.40.12) ; extra == "all"
|
52
|
+
Requires-Dist: opentelemetry-instrumentation-anthropic (>=0.40.12) ; extra == "anthropic"
|
53
|
+
Requires-Dist: opentelemetry-instrumentation-bedrock (>=0.40.12) ; extra == "all"
|
54
|
+
Requires-Dist: opentelemetry-instrumentation-bedrock (>=0.40.12) ; extra == "bedrock"
|
55
|
+
Requires-Dist: opentelemetry-instrumentation-chromadb (>=0.40.12) ; extra == "all"
|
56
|
+
Requires-Dist: opentelemetry-instrumentation-chromadb (>=0.40.12) ; extra == "chromadb"
|
57
|
+
Requires-Dist: opentelemetry-instrumentation-cohere (>=0.40.12) ; extra == "all"
|
58
|
+
Requires-Dist: opentelemetry-instrumentation-cohere (>=0.40.12) ; extra == "cohere"
|
59
|
+
Requires-Dist: opentelemetry-instrumentation-crewai (>=0.40.12) ; extra == "all"
|
60
|
+
Requires-Dist: opentelemetry-instrumentation-crewai (>=0.40.12) ; extra == "crewai"
|
61
|
+
Requires-Dist: opentelemetry-instrumentation-google-generativeai (>=0.40.12) ; extra == "all"
|
62
|
+
Requires-Dist: opentelemetry-instrumentation-google-generativeai (>=0.40.12) ; extra == "google-generativeai"
|
63
|
+
Requires-Dist: opentelemetry-instrumentation-groq (>=0.40.12) ; extra == "all"
|
64
|
+
Requires-Dist: opentelemetry-instrumentation-groq (>=0.40.12) ; extra == "groq"
|
65
|
+
Requires-Dist: opentelemetry-instrumentation-haystack (>=0.40.12) ; extra == "all"
|
66
|
+
Requires-Dist: opentelemetry-instrumentation-haystack (>=0.40.12) ; extra == "haystack"
|
67
|
+
Requires-Dist: opentelemetry-instrumentation-lancedb (>=0.40.12) ; extra == "all"
|
68
|
+
Requires-Dist: opentelemetry-instrumentation-lancedb (>=0.40.12) ; extra == "lancedb"
|
69
|
+
Requires-Dist: opentelemetry-instrumentation-langchain (>=0.40.12) ; extra == "all"
|
70
|
+
Requires-Dist: opentelemetry-instrumentation-langchain (>=0.40.12) ; extra == "langchain"
|
71
|
+
Requires-Dist: opentelemetry-instrumentation-llamaindex (>=0.40.12) ; extra == "all"
|
72
|
+
Requires-Dist: opentelemetry-instrumentation-llamaindex (>=0.40.12) ; extra == "llamaindex"
|
73
|
+
Requires-Dist: opentelemetry-instrumentation-marqo (>=0.40.12) ; extra == "all"
|
74
|
+
Requires-Dist: opentelemetry-instrumentation-marqo (>=0.40.12) ; extra == "marqo"
|
75
|
+
Requires-Dist: opentelemetry-instrumentation-mcp (>=0.40.12) ; extra == "all"
|
76
|
+
Requires-Dist: opentelemetry-instrumentation-mcp (>=0.40.12) ; extra == "mcp"
|
77
|
+
Requires-Dist: opentelemetry-instrumentation-milvus (>=0.40.12) ; extra == "all"
|
78
|
+
Requires-Dist: opentelemetry-instrumentation-milvus (>=0.40.12) ; extra == "milvus"
|
79
|
+
Requires-Dist: opentelemetry-instrumentation-mistralai (>=0.40.12) ; extra == "all"
|
80
|
+
Requires-Dist: opentelemetry-instrumentation-mistralai (>=0.40.12) ; extra == "mistralai"
|
81
|
+
Requires-Dist: opentelemetry-instrumentation-ollama (>=0.40.12) ; extra == "all"
|
82
|
+
Requires-Dist: opentelemetry-instrumentation-ollama (>=0.40.12) ; extra == "ollama"
|
83
|
+
Requires-Dist: opentelemetry-instrumentation-openai (>=0.40.12) ; extra == "all"
|
84
|
+
Requires-Dist: opentelemetry-instrumentation-openai (>=0.40.12) ; extra == "openai"
|
85
|
+
Requires-Dist: opentelemetry-instrumentation-pinecone (>=0.40.12) ; extra == "all"
|
86
|
+
Requires-Dist: opentelemetry-instrumentation-pinecone (>=0.40.12) ; extra == "pinecone"
|
87
|
+
Requires-Dist: opentelemetry-instrumentation-qdrant (>=0.40.12) ; extra == "all"
|
88
|
+
Requires-Dist: opentelemetry-instrumentation-qdrant (>=0.40.12) ; extra == "qdrant"
|
89
|
+
Requires-Dist: opentelemetry-instrumentation-replicate (>=0.40.12) ; extra == "all"
|
90
|
+
Requires-Dist: opentelemetry-instrumentation-replicate (>=0.40.12) ; extra == "replicate"
|
91
|
+
Requires-Dist: opentelemetry-instrumentation-sagemaker (>=0.40.12) ; extra == "all"
|
92
|
+
Requires-Dist: opentelemetry-instrumentation-sagemaker (>=0.40.12) ; extra == "sagemaker"
|
93
93
|
Requires-Dist: opentelemetry-instrumentation-threading (>=0.54b0)
|
94
|
-
Requires-Dist: opentelemetry-instrumentation-together (>=0.40.
|
95
|
-
Requires-Dist: opentelemetry-instrumentation-together (>=0.40.
|
96
|
-
Requires-Dist: opentelemetry-instrumentation-transformers (>=0.40.
|
97
|
-
Requires-Dist: opentelemetry-instrumentation-transformers (>=0.40.
|
98
|
-
Requires-Dist: opentelemetry-instrumentation-vertexai (>=0.40.
|
99
|
-
Requires-Dist: opentelemetry-instrumentation-vertexai (>=0.40.
|
100
|
-
Requires-Dist: opentelemetry-instrumentation-watsonx (>=0.40.
|
101
|
-
Requires-Dist: opentelemetry-instrumentation-watsonx (>=0.40.
|
102
|
-
Requires-Dist: opentelemetry-instrumentation-weaviate (>=0.40.
|
103
|
-
Requires-Dist: opentelemetry-instrumentation-weaviate (>=0.40.
|
94
|
+
Requires-Dist: opentelemetry-instrumentation-together (>=0.40.12) ; extra == "all"
|
95
|
+
Requires-Dist: opentelemetry-instrumentation-together (>=0.40.12) ; extra == "together"
|
96
|
+
Requires-Dist: opentelemetry-instrumentation-transformers (>=0.40.12) ; extra == "all"
|
97
|
+
Requires-Dist: opentelemetry-instrumentation-transformers (>=0.40.12) ; extra == "transformers"
|
98
|
+
Requires-Dist: opentelemetry-instrumentation-vertexai (>=0.40.12) ; extra == "all"
|
99
|
+
Requires-Dist: opentelemetry-instrumentation-vertexai (>=0.40.12) ; extra == "vertexai"
|
100
|
+
Requires-Dist: opentelemetry-instrumentation-watsonx (>=0.40.12) ; extra == "all"
|
101
|
+
Requires-Dist: opentelemetry-instrumentation-watsonx (>=0.40.12) ; extra == "watsonx"
|
102
|
+
Requires-Dist: opentelemetry-instrumentation-weaviate (>=0.40.12) ; extra == "all"
|
103
|
+
Requires-Dist: opentelemetry-instrumentation-weaviate (>=0.40.12) ; extra == "weaviate"
|
104
104
|
Requires-Dist: opentelemetry-sdk (>=1.33.0)
|
105
105
|
Requires-Dist: opentelemetry-semantic-conventions (>=0.54b0)
|
106
106
|
Requires-Dist: opentelemetry-semantic-conventions-ai (>=0.4.8)
|
@@ -1,11 +1,13 @@
|
|
1
|
-
lmnr/__init__.py,sha256=
|
1
|
+
lmnr/__init__.py,sha256=gRg8Pb-0XVzC-XmiLlHI9Br5yAJXwT6srPFxXIOaZ1o,1373
|
2
2
|
lmnr/cli.py,sha256=uHgLUfN_6eINtUlcQdOtODf2tI9AiwmlhojQF4UMB5Y,6047
|
3
3
|
lmnr/opentelemetry_lib/.flake8,sha256=bCxuDlGx3YQ55QHKPiGJkncHanh9qGjQJUujcFa3lAU,150
|
4
|
-
lmnr/opentelemetry_lib/__init__.py,sha256=
|
4
|
+
lmnr/opentelemetry_lib/__init__.py,sha256=aWKsqRXUhVhu2BS555nO2JhZSsK8bTUylAVwWybquGE,2160
|
5
5
|
lmnr/opentelemetry_lib/decorators/__init__.py,sha256=45HVoYnHC1Y9D_VSkioDbqD3gm4RPC5sKoztomBI5j8,8496
|
6
|
-
lmnr/opentelemetry_lib/
|
6
|
+
lmnr/opentelemetry_lib/litellm/__init__.py,sha256=wjo46It5GdhmxPCaiA8kaKyaz3VsuRDRYUCCstvK0-Y,14858
|
7
|
+
lmnr/opentelemetry_lib/litellm/utils.py,sha256=2ozwVT-C3HIDEJ8Rekx7QYXouvNMqtEteCOHVRUgGic,539
|
8
|
+
lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py,sha256=mltiTDVCCyMuhQNuoLHvblg9O5X0ncG6xN3f1opSeQU,18613
|
7
9
|
lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/config.py,sha256=25zevJ7g3MtJP_5gju3jBH7-wg7SbDkktysuUO29ksI,245
|
8
|
-
lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py,sha256=
|
10
|
+
lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py,sha256=8SSBliRoJtiZME5RDEwt90CI2BadKPHQrtV4p6bDy_0,7669
|
9
11
|
lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py,sha256=Jyv9koZdGA4-oTaB7ATB7DaX7aNOY-3YOGL4wX0c7PM,3107
|
10
12
|
lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/utils.py,sha256=nf9sJZXnnts4gYZortEiDvwYjYqYJZTAT0zutuP_R6Y,1512
|
11
13
|
lmnr/opentelemetry_lib/tracing/__init__.py,sha256=27QogAe-aHyrVr6b56-DduUm0KEE24K1aV2e1nouTNg,6007
|
@@ -45,14 +47,14 @@ lmnr/sdk/client/synchronous/sync_client.py,sha256=IIzj-mAwHHoRuUX9KkJtrzTGi5UOyg
|
|
45
47
|
lmnr/sdk/datasets.py,sha256=P9hRxfl7-I6qhLFFGgU-r_I7RJfLtF6sL56g5fKIbAA,1708
|
46
48
|
lmnr/sdk/decorators.py,sha256=1uu9xxBYgblFqlhQqH17cZYq7babAmB1lEtvBgTsP0E,4468
|
47
49
|
lmnr/sdk/eval_control.py,sha256=KROUrDhcZTrptRZ-hxvr60_o_Gt_8u045jb4cBXcuoY,184
|
48
|
-
lmnr/sdk/evaluations.py,sha256=
|
50
|
+
lmnr/sdk/evaluations.py,sha256=fMUDueAgGv9fyTuX7n0DsS8lOzrbZNMgPorA037tgDU,21458
|
49
51
|
lmnr/sdk/laminar.py,sha256=oOVco_c9ZstT71HsquGsgbtFumXd2Ejz0rl_qpmMlTU,33996
|
50
52
|
lmnr/sdk/log.py,sha256=nt_YMmPw1IRbGy0b7q4rTtP4Yo3pQfNxqJPXK3nDSNQ,2213
|
51
|
-
lmnr/sdk/types.py,sha256=
|
53
|
+
lmnr/sdk/types.py,sha256=ZQp5SeYJNZsK3KrbSeXPY_xn6mGjW5mSw_i0Rd_Oa4k,12328
|
52
54
|
lmnr/sdk/utils.py,sha256=yrcHIhoADf9lWH9qJWZMmkRWYvd0DuxPSLP3mY6YFw0,4327
|
53
|
-
lmnr/version.py,sha256=
|
54
|
-
lmnr-0.6.
|
55
|
-
lmnr-0.6.
|
56
|
-
lmnr-0.6.
|
57
|
-
lmnr-0.6.
|
58
|
-
lmnr-0.6.
|
55
|
+
lmnr/version.py,sha256=9gMGnzWiCqbv_PzYX6c-jQFz17zO1xyIONO32wE9xfY,1322
|
56
|
+
lmnr-0.6.13.dist-info/LICENSE,sha256=67b_wJHVV1CBaWkrKFWU1wyqTPSdzH77Ls-59631COg,10411
|
57
|
+
lmnr-0.6.13.dist-info/METADATA,sha256=95yHwJlTi4CNL1ucZDk9ge2flO712qjpxikJ0jCkmiI,15186
|
58
|
+
lmnr-0.6.13.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
59
|
+
lmnr-0.6.13.dist-info/entry_points.txt,sha256=K1jE20ww4jzHNZLnsfWBvU3YKDGBgbOiYG5Y7ivQcq4,37
|
60
|
+
lmnr-0.6.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|