lmnr 0.7.6__py3-none-any.whl → 0.7.8__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.
@@ -1,5 +1,4 @@
1
1
  from functools import wraps
2
- import logging
3
2
  import pydantic
4
3
  import orjson
5
4
  import types
@@ -60,7 +59,7 @@ def json_dumps(data: dict) -> str:
60
59
  ).decode("utf-8")
61
60
  except Exception:
62
61
  # Log the exception and return a placeholder if serialization completely fails
63
- logging.warning("Failed to serialize data to JSON, type: %s", type(data))
62
+ logger.info("Failed to serialize data to JSON, type: %s", type(data))
64
63
  return "{}" # Return an empty JSON object as a fallback
65
64
 
66
65
 
@@ -7,14 +7,13 @@ from opentelemetry.trace import SpanKind, Status, StatusCode, Tracer
7
7
  from lmnr.opentelemetry_lib.litellm.utils import model_as_dict, set_span_attribute
8
8
  from lmnr.opentelemetry_lib.tracing import TracerWrapper
9
9
 
10
- from lmnr.opentelemetry_lib.tracing.context import get_event_attributes_from_context
10
+ from lmnr.opentelemetry_lib.tracing.context import (
11
+ get_current_context,
12
+ get_event_attributes_from_context,
13
+ )
11
14
  from lmnr.opentelemetry_lib.utils.package_check import is_package_installed
12
15
  from lmnr.sdk.log import get_default_logger
13
16
 
14
- from lmnr.opentelemetry_lib.opentelemetry.instrumentation.openai import (
15
- OpenAIInstrumentor,
16
- )
17
-
18
17
  logger = get_default_logger(__name__)
19
18
 
20
19
  SUPPORTED_CALL_TYPES = ["completion", "acompletion"]
@@ -46,6 +45,10 @@ try:
46
45
  raise ValueError("Laminar must be initialized before LiteLLM callback")
47
46
 
48
47
  if is_package_installed("openai"):
48
+ from lmnr.opentelemetry_lib.opentelemetry.instrumentation.openai import (
49
+ OpenAIInstrumentor,
50
+ )
51
+
49
52
  openai_instrumentor = OpenAIInstrumentor()
50
53
  if (
51
54
  openai_instrumentor
@@ -117,6 +120,7 @@ try:
117
120
  attributes={
118
121
  "lmnr.internal.provider": "litellm",
119
122
  },
123
+ context=get_current_context(),
120
124
  )
121
125
  try:
122
126
  model = kwargs.get("model", "unknown")
@@ -375,6 +375,7 @@ def _build_from_streaming_response(
375
375
  # total token count in every chunk is greater by prompt token count than it should be,
376
376
  # thus this awkward logic here
377
377
  if aggregated_usage_metadata.get("prompt_token_count") is None:
378
+ # or 0, not .get(key, 0), because sometimes the value is explicitly None
378
379
  aggregated_usage_metadata["prompt_token_count"] = (
379
380
  usage_dict.get("prompt_token_count") or 0
380
381
  )
@@ -417,14 +418,15 @@ async def _abuild_from_streaming_response(
417
418
  aggregated_usage_metadata = defaultdict(int)
418
419
  model_version = None
419
420
  async for chunk in response:
421
+ if chunk.model_version:
422
+ model_version = chunk.model_version
423
+
420
424
  if chunk.candidates:
421
425
  # Currently gemini throws an error if you pass more than one candidate
422
426
  # with streaming
423
427
  if chunk.candidates and len(chunk.candidates) > 0:
424
428
  final_parts += chunk.candidates[0].content.parts or []
425
429
  role = chunk.candidates[0].content.role or role
426
- if chunk.model_version:
427
- model_version = chunk.model_version
428
430
  if chunk.usage_metadata:
429
431
  usage_dict = to_dict(chunk.usage_metadata)
430
432
  # prompt token count is sent in every chunk
@@ -432,11 +434,12 @@ async def _abuild_from_streaming_response(
432
434
  # total token count in every chunk is greater by prompt token count than it should be,
433
435
  # thus this awkward logic here
434
436
  if aggregated_usage_metadata.get("prompt_token_count") is None:
435
- aggregated_usage_metadata["prompt_token_count"] = usage_dict.get(
436
- "prompt_token_count"
437
+ # or 0, not .get(key, 0), because sometimes the value is explicitly None
438
+ aggregated_usage_metadata["prompt_token_count"] = (
439
+ usage_dict.get("prompt_token_count") or 0
437
440
  )
438
- aggregated_usage_metadata["total_token_count"] = usage_dict.get(
439
- "total_token_count"
441
+ aggregated_usage_metadata["total_token_count"] = (
442
+ usage_dict.get("total_token_count") or 0
440
443
  )
441
444
  aggregated_usage_metadata["candidates_token_count"] += (
442
445
  usage_dict.get("candidates_token_count") or 0
@@ -12,6 +12,7 @@ from .shared.config import Config
12
12
 
13
13
  import openai
14
14
 
15
+
15
16
  _OPENAI_VERSION = version("openai")
16
17
 
17
18
  LMNR_TRACE_CONTENT = "LMNR_TRACE_CONTENT"
@@ -22,6 +23,7 @@ def is_openai_v1():
22
23
 
23
24
 
24
25
  def is_azure_openai(instance):
26
+
25
27
  return is_openai_v1() and isinstance(
26
28
  instance._client, (openai.AsyncAzureOpenAI, openai.AzureOpenAI)
27
29
  )
@@ -0,0 +1,389 @@
1
+ """OpenTelemetry OpenHands AI instrumentation"""
2
+
3
+ import sys
4
+ from typing import Collection
5
+
6
+ from lmnr.opentelemetry_lib.decorators import json_dumps
7
+ from lmnr.opentelemetry_lib.tracing.attributes import (
8
+ ASSOCIATION_PROPERTIES,
9
+ SESSION_ID,
10
+ USER_ID,
11
+ )
12
+ from lmnr.opentelemetry_lib.utils.wrappers import _with_tracer_wrapper
13
+ from lmnr.sdk.log import get_default_logger
14
+ from lmnr.sdk.utils import get_input_from_func_args
15
+ from lmnr import Laminar
16
+ from lmnr.version import __version__
17
+
18
+ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
19
+ from opentelemetry.instrumentation.utils import unwrap
20
+ from opentelemetry.trace import get_tracer, Tracer
21
+ from wrapt import wrap_function_wrapper
22
+
23
+ logger = get_default_logger(__name__)
24
+
25
+ _instruments = ("openhands-ai >= 0.9.0", "openhands-aci >= 0.1.0")
26
+ parent_spans = {}
27
+
28
+
29
+ def is_message_action(event) -> bool:
30
+ """Check if event has action attribute equal to 'message'."""
31
+ return event and hasattr(event, "action") and event.action == "message"
32
+
33
+
34
+ def is_user_message(event) -> bool:
35
+ """Check if event is a message action from user source."""
36
+ return (
37
+ is_message_action(event) and hasattr(event, "source") and event.source == "user"
38
+ )
39
+
40
+
41
+ def is_agent_message(event) -> bool:
42
+ """Check if event is a message action from agent source."""
43
+ return (
44
+ is_message_action(event)
45
+ and hasattr(event, "source")
46
+ and event.source == "agent"
47
+ )
48
+
49
+
50
+ def is_agent_state_changed_to(event, state: str) -> bool:
51
+ """Check if event is an agent_state_changed observation with specific state."""
52
+ return (
53
+ event
54
+ and hasattr(event, "observation")
55
+ and event.observation == "agent_state_changed"
56
+ and hasattr(event, "agent_state")
57
+ and event.agent_state == state
58
+ )
59
+
60
+
61
+ def get_handle_action_action(event) -> str:
62
+ """Get the action of the handle_action event."""
63
+ if event and hasattr(event, "action"):
64
+ try:
65
+ return event.action.value
66
+ except Exception:
67
+ return event.action
68
+ return None
69
+
70
+
71
+ WRAPPED_METHODS = [
72
+ {
73
+ "package": "openhands.agenthub.codeact_agent.codeact_agent",
74
+ "object": "CodeActAgent",
75
+ "methods": [
76
+ {"method": "step"},
77
+ {"method": "response_to_actions"},
78
+ ],
79
+ },
80
+ {
81
+ "package": "openhands.controller.agent_controller",
82
+ "object": "AgentController",
83
+ "methods": [
84
+ {"method": "_step", "async": True},
85
+ {
86
+ "method": "_handle_action",
87
+ "async": True,
88
+ "span_type": "TOOL",
89
+ },
90
+ {"method": "_handle_observation", "async": True},
91
+ {"method": "_handle_message_action", "async": True},
92
+ {"method": "on_event"},
93
+ {"method": "save_state"},
94
+ {"method": "get_trajectory"},
95
+ {"method": "start_delegate"},
96
+ {"method": "end_delegate"},
97
+ {"method": "_is_stuck"},
98
+ ],
99
+ },
100
+ ]
101
+
102
+
103
+ @_with_tracer_wrapper
104
+ def _wrap_on_event(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
105
+ """Wrapper for on_event."""
106
+ controller_id = instance.id
107
+ user_id = instance.user_id
108
+ event = kwargs.get("event", args[0] if len(args) > 0 else None)
109
+ start_event = False
110
+ finish_event = False
111
+ user_message = ""
112
+ agent_message = ""
113
+ span_name = to_wrap.get("span_name")
114
+ span_type = to_wrap.get("span_type", "DEFAULT")
115
+ if event and hasattr(event, "action") and event.action == "system":
116
+ return wrapped(*args, **kwargs)
117
+
118
+ event_type = None
119
+ subtype = None
120
+ if event and hasattr(event, "action"):
121
+ event_type = "action"
122
+ try:
123
+ subtype = event.action.value
124
+ except Exception:
125
+ subtype = event.action
126
+ elif event and hasattr(event, "observation"):
127
+ event_type = "observation"
128
+ try:
129
+ subtype = event.observation.value
130
+ except Exception:
131
+ subtype = event.observation
132
+ if event_type and subtype:
133
+ span_name = f"event.{event_type}.{subtype}"
134
+ span_type = "EVENT"
135
+
136
+ # start trace on user message
137
+ if is_user_message(event):
138
+ user_message = event.content if hasattr(event, "content") else ""
139
+ start_event = True
140
+ # end trace on agent state change to finished or error
141
+ if is_agent_state_changed_to(event, "stopped") or is_agent_state_changed_to(
142
+ event, "awaiting_user_input"
143
+ ):
144
+ finish_event = True
145
+
146
+ if is_agent_state_changed_to(event, "user_rejected"):
147
+ agent_message = "<user_rejected>"
148
+
149
+ if is_agent_message(event):
150
+ agent_message = event.content if hasattr(event, "content") else ""
151
+
152
+ if start_event:
153
+ if controller_id in parent_spans:
154
+ logger.debug(
155
+ "Received a message, but already have a span for this trace. Resetting span."
156
+ )
157
+ parent_spans[controller_id].end()
158
+ del parent_spans[controller_id]
159
+ parent_span = Laminar.start_span("conversation.turn", span_type="DEFAULT")
160
+ if user_id:
161
+ parent_span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{USER_ID}", user_id)
162
+ if user_message:
163
+ parent_span.set_attribute("lmnr.span.input", user_message)
164
+ parent_span.set_attribute(
165
+ f"{ASSOCIATION_PROPERTIES}.{SESSION_ID}", controller_id
166
+ )
167
+ parent_spans[controller_id] = parent_span
168
+
169
+ if controller_id in parent_spans:
170
+ with Laminar.use_span(parent_spans[controller_id]):
171
+ result = _wrap_sync_method_inner(
172
+ tracer,
173
+ {**to_wrap, "span_name": span_name, "span_type": span_type},
174
+ wrapped,
175
+ instance,
176
+ args,
177
+ kwargs,
178
+ )
179
+ if agent_message:
180
+ parent_spans[controller_id].set_attribute(
181
+ "lmnr.span.output", agent_message
182
+ )
183
+ if finish_event:
184
+ parent_spans[controller_id].end()
185
+ del parent_spans[controller_id]
186
+ return result
187
+
188
+ return wrapped(*args, **kwargs)
189
+
190
+
191
+ @_with_tracer_wrapper
192
+ async def _wrap_handle_action(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
193
+ """Wrapper for on_event."""
194
+ event = kwargs.get("event", args[0] if len(args) > 0 else None)
195
+ if event and hasattr(event, "action"):
196
+ if event.action == "system":
197
+ return await wrapped(*args, **kwargs)
198
+ action_name = get_handle_action_action(event)
199
+ if action_name and action_name != "message":
200
+ to_wrap["span_name"] = f"action.{action_name}"
201
+ controller_id = instance.id
202
+ if controller_id not in parent_spans:
203
+ return await wrapped(*args, **kwargs)
204
+ return await _wrap_async_method_inner(
205
+ tracer, to_wrap, wrapped, instance, args, kwargs
206
+ )
207
+
208
+
209
+ def _wrap_sync_method_inner(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
210
+ """Wrapper for synchronous methods."""
211
+ span_name = to_wrap.get("span_name")
212
+
213
+ with Laminar.start_as_current_span(
214
+ span_name,
215
+ span_type=to_wrap.get("span_type", "DEFAULT"),
216
+ input=json_dumps(
217
+ get_input_from_func_args(
218
+ wrapped, to_wrap.get("object") is not None, args, kwargs
219
+ )
220
+ ),
221
+ ) as span:
222
+ try:
223
+ result = wrapped(*args, **kwargs)
224
+
225
+ # Capture output
226
+ if not to_wrap.get("ignore_output"):
227
+ span.set_attribute("lmnr.span.output", json_dumps(result))
228
+ return result
229
+
230
+ except Exception as e:
231
+ span.record_exception(e)
232
+ raise
233
+
234
+
235
+ @_with_tracer_wrapper
236
+ def _wrap_sync_method(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
237
+ instance_id = None
238
+ if to_wrap.get("object") == "AgentController":
239
+ instance_id = instance.id
240
+ if to_wrap.get("object") == "ActionExecutionClient" and hasattr(instance, "sid"):
241
+ instance_id = instance.sid
242
+ if instance_id is not None and instance_id not in parent_spans:
243
+ return wrapped(*args, **kwargs)
244
+ return _wrap_sync_method_inner(tracer, to_wrap, wrapped, instance, args, kwargs)
245
+
246
+
247
+ async def _wrap_async_method_inner(
248
+ tracer: Tracer, to_wrap, wrapped, instance, args, kwargs
249
+ ):
250
+ """Wrapper for asynchronous methods."""
251
+ span_name = to_wrap.get("span_name")
252
+ instance_id = None
253
+ if to_wrap.get("object") == "AgentController":
254
+ instance_id = instance.id
255
+ if to_wrap.get("object") == "ActionExecutionClient" and hasattr(instance, "sid"):
256
+ instance_id = instance.sid
257
+ if instance_id is not None and instance_id not in parent_spans:
258
+ return await wrapped(*args, **kwargs)
259
+
260
+ with Laminar.start_as_current_span(
261
+ span_name,
262
+ span_type=to_wrap.get("span_type", "DEFAULT"),
263
+ input=json_dumps(
264
+ get_input_from_func_args(
265
+ wrapped, to_wrap.get("object") is not None, args, kwargs
266
+ )
267
+ ),
268
+ ) as span:
269
+ try:
270
+ result = await wrapped(*args, **kwargs)
271
+
272
+ # Capture output
273
+ if not to_wrap.get("ignore_output"):
274
+ span.set_attribute("lmnr.span.output", json_dumps(result))
275
+ return result
276
+
277
+ except Exception as e:
278
+ span.record_exception(e)
279
+ raise
280
+
281
+
282
+ @_with_tracer_wrapper
283
+ async def _wrap_async_method(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
284
+ """Wrapper for asynchronous methods."""
285
+ return await _wrap_async_method_inner(
286
+ tracer, to_wrap, wrapped, instance, args, kwargs
287
+ )
288
+
289
+
290
+ class OpenHandsInstrumentor(BaseInstrumentor):
291
+ """An instrumentor for OpenHands AI."""
292
+
293
+ def __init__(self):
294
+ super().__init__()
295
+
296
+ def instrumentation_dependencies(self) -> Collection[str]:
297
+ return _instruments
298
+
299
+ def _instrument(self, **kwargs):
300
+ """Instrument OpenHands AI methods."""
301
+ tracer_provider = kwargs.get("tracer_provider")
302
+ tracer = get_tracer(__name__, __version__, tracer_provider)
303
+
304
+ for wrapped_config in WRAPPED_METHODS:
305
+ wrap_package = wrapped_config.get("package")
306
+
307
+ wrap_object = wrapped_config.get("object")
308
+ methods = wrapped_config.get("methods", [])
309
+
310
+ for method_config in methods:
311
+
312
+ wrap_method = method_config.get("method")
313
+ async_wrap = method_config.get("async", False)
314
+ windows_only = method_config.get("windows_only", False)
315
+ if windows_only and sys.platform != "win32":
316
+ continue
317
+
318
+ # Create the method configuration for the wrapper
319
+ method_wrapper_config = {
320
+ "package": wrap_package,
321
+ "object": wrap_object,
322
+ "method": wrap_method,
323
+ "span_name": method_config.get(
324
+ "span_name",
325
+ f"{wrap_object}.{wrap_method}" if wrap_object else wrap_method,
326
+ ),
327
+ "span_type": method_config.get("span_type", "DEFAULT"),
328
+ "async": async_wrap,
329
+ }
330
+
331
+ # Determine the target for wrapping
332
+ if wrap_object:
333
+ target = f"{wrap_object}.{wrap_method}"
334
+ else:
335
+ target = wrap_method
336
+
337
+ if wrap_object == "AgentController" and wrap_method == "on_event":
338
+ wrap_function_wrapper(
339
+ wrap_package,
340
+ target,
341
+ _wrap_on_event(tracer, method_wrapper_config),
342
+ )
343
+ continue
344
+ if wrap_object == "AgentController" and wrap_method == "_handle_action":
345
+ wrap_function_wrapper(
346
+ wrap_package,
347
+ target,
348
+ _wrap_handle_action(tracer, method_wrapper_config),
349
+ )
350
+ continue
351
+
352
+ try:
353
+ if async_wrap:
354
+ wrap_function_wrapper(
355
+ wrap_package,
356
+ target,
357
+ _wrap_async_method(tracer, method_wrapper_config),
358
+ )
359
+ else:
360
+ wrap_function_wrapper(
361
+ wrap_package,
362
+ target,
363
+ _wrap_sync_method(tracer, method_wrapper_config),
364
+ )
365
+ except (ModuleNotFoundError, AttributeError) as e:
366
+ logger.debug(f"Could not instrument {wrap_package}.{target}: {e}")
367
+
368
+ def _uninstrument(self, **kwargs):
369
+ """Remove OpenHands AI instrumentation."""
370
+ for wrapped_config in WRAPPED_METHODS:
371
+ wrap_package = wrapped_config.get("package")
372
+ wrap_object = wrapped_config.get("object")
373
+ methods = wrapped_config.get("methods", [])
374
+
375
+ for method_config in methods:
376
+ wrap_method = method_config.get("method")
377
+
378
+ # Determine the module path for unwrapping
379
+ if wrap_object:
380
+ module_path = f"{wrap_package}.{wrap_object}"
381
+ else:
382
+ module_path = wrap_package
383
+
384
+ try:
385
+ unwrap(module_path, wrap_method)
386
+ except (AttributeError, ValueError) as e:
387
+ logger.debug(
388
+ f"Could not uninstrument {module_path}.{wrap_method}: {e}"
389
+ )
@@ -268,6 +268,15 @@ class OpenAIInstrumentorInitializer(InstrumentorInitializer):
268
268
  )
269
269
 
270
270
 
271
+ class OpenHandsAIInstrumentorInitializer(InstrumentorInitializer):
272
+ def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
273
+ if not is_package_installed("openhands-ai"):
274
+ return None
275
+ from ..opentelemetry.instrumentation.openhands_ai import OpenHandsInstrumentor
276
+
277
+ return OpenHandsInstrumentor()
278
+
279
+
271
280
  class OpenTelemetryInstrumentorInitializer(InstrumentorInitializer):
272
281
  def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
273
282
  from ..opentelemetry.instrumentation.opentelemetry import (
@@ -33,6 +33,7 @@ class Instruments(Enum):
33
33
  MISTRAL = "mistral"
34
34
  OLLAMA = "ollama"
35
35
  OPENAI = "openai"
36
+ OPENHANDS = "openhands"
36
37
  # Patch OpenTelemetry to fix DataDog's broken Span context
37
38
  # See lmnr.opentelemetry_lib.opentelemetry.instrumentation.opentelemetry
38
39
  # for more details.
@@ -75,6 +76,7 @@ INSTRUMENTATION_INITIALIZERS: dict[
75
76
  Instruments.MISTRAL: initializers.MistralInstrumentorInitializer(),
76
77
  Instruments.OLLAMA: initializers.OllamaInstrumentorInitializer(),
77
78
  Instruments.OPENAI: initializers.OpenAIInstrumentorInitializer(),
79
+ Instruments.OPENHANDS: initializers.OpenHandsAIInstrumentorInitializer(),
78
80
  Instruments.OPENTELEMETRY: initializers.OpenTelemetryInstrumentorInitializer(),
79
81
  Instruments.PATCHRIGHT: initializers.PatchrightInstrumentorInitializer(),
80
82
  Instruments.PINECONE: initializers.PineconeInstrumentorInitializer(),
@@ -0,0 +1,11 @@
1
+ # TODO: Remove the same thing from openai, anthropic, etc, and use this instead
2
+
3
+
4
+ def _with_tracer_wrapper(func):
5
+ def _with_tracer(tracer, to_wrap):
6
+ def wrapper(wrapped, instance, args, kwargs):
7
+ return func(tracer, to_wrap, wrapped, instance, args, kwargs)
8
+
9
+ return wrapper
10
+
11
+ return _with_tracer
@@ -154,6 +154,26 @@ async def _wrap_bring_to_front_async(
154
154
  await take_full_snapshot_async(instance)
155
155
 
156
156
 
157
+ @with_tracer_and_client_wrapper
158
+ def _wrap_browser_new_page_sync(
159
+ tracer: Tracer, client: LaminarClient, to_wrap, wrapped, instance, args, kwargs
160
+ ):
161
+ page = wrapped(*args, **kwargs)
162
+ session_id = str(uuid.uuid4().hex)
163
+ start_recording_events_sync(page, session_id, client)
164
+ return page
165
+
166
+
167
+ @with_tracer_and_client_wrapper
168
+ async def _wrap_browser_new_page_async(
169
+ tracer: Tracer, client: AsyncLaminarClient, to_wrap, wrapped, instance, args, kwargs
170
+ ):
171
+ page = await wrapped(*args, **kwargs)
172
+ session_id = str(uuid.uuid4().hex)
173
+ await start_recording_events_async(page, session_id, client)
174
+ return page
175
+
176
+
157
177
  WRAPPED_METHODS = [
158
178
  {
159
179
  "package": "playwright.sync_api",
@@ -191,6 +211,12 @@ WRAPPED_METHODS = [
191
211
  "method": "bring_to_front",
192
212
  "wrapper": _wrap_bring_to_front_sync,
193
213
  },
214
+ {
215
+ "package": "playwright.sync_api",
216
+ "object": "Browser",
217
+ "method": "new_page",
218
+ "wrapper": _wrap_browser_new_page_sync,
219
+ },
194
220
  ]
195
221
 
196
222
  WRAPPED_METHODS_ASYNC = [
@@ -230,6 +256,12 @@ WRAPPED_METHODS_ASYNC = [
230
256
  "method": "bring_to_front",
231
257
  "wrapper": _wrap_bring_to_front_async,
232
258
  },
259
+ {
260
+ "package": "playwright.async_api",
261
+ "object": "Browser",
262
+ "method": "new_page",
263
+ "wrapper": _wrap_browser_new_page_async,
264
+ },
233
265
  ]
234
266
 
235
267
 
lmnr/sdk/utils.py CHANGED
@@ -132,13 +132,13 @@ def is_otel_attribute_value_type(value: typing.Any) -> bool:
132
132
 
133
133
  def format_id(id_value: str | int | uuid.UUID) -> str:
134
134
  """Format trace/span/evaluation ID to a UUID string, or return valid UUID strings as-is.
135
-
135
+
136
136
  Args:
137
137
  id_value: The ID in various formats (UUID, int, or valid UUID string)
138
-
138
+
139
139
  Returns:
140
140
  str: UUID string representation
141
-
141
+
142
142
  Raises:
143
143
  ValueError: If id_value cannot be converted to a valid UUID
144
144
  """
lmnr/version.py CHANGED
@@ -3,7 +3,7 @@ import httpx
3
3
  from packaging import version
4
4
 
5
5
 
6
- __version__ = "0.7.6"
6
+ __version__ = "0.7.8"
7
7
  PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}"
8
8
 
9
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lmnr
3
- Version: 0.7.6
3
+ Version: 0.7.8
4
4
  Summary: Python SDK for Laminar
5
5
  Author: lmnr.ai
6
6
  Author-email: lmnr.ai <founders@lmnr.ai>
@@ -2,8 +2,8 @@ lmnr/__init__.py,sha256=8be7b56ab62735fd54ca90a0642784c6153ed1d6e0f12734619ca061
2
2
  lmnr/cli.py,sha256=b8780b51f37fe9e20db5495c41d3ad3837f6b48f408b09a58688d017850c0796,6047
3
3
  lmnr/opentelemetry_lib/.flake8,sha256=6c2c6e0e51b1dd8439e501ca3e21899277076a787da868d0254ba37056b79405,150
4
4
  lmnr/opentelemetry_lib/__init__.py,sha256=1350e8d12ea2f422751ab3a80d7d32d10c27ad8e4c2989407771dc6e544d9c65,2350
5
- lmnr/opentelemetry_lib/decorators/__init__.py,sha256=e2ddd9252e0f3d82601081d655342af56413e09dc827ac07f66b51d3b575156e,11814
6
- lmnr/opentelemetry_lib/litellm/__init__.py,sha256=c72a5d23eb14f03926e713b70acf2c67063f74678c2dfd5824f1bdbc8ce1900f,15723
5
+ lmnr/opentelemetry_lib/decorators/__init__.py,sha256=b9bab9acd44c7d4d98660a8de1f4e42b9387170c62e3239fba174a8461255ead,11795
6
+ lmnr/opentelemetry_lib/litellm/__init__.py,sha256=d67d18581b42d7e5d9c724bd201044fb7a04d79e7162f5a47007c0a1f413141a,15852
7
7
  lmnr/opentelemetry_lib/litellm/utils.py,sha256=da8cf0553f82dc7203109f117a4c7b4185e8baf34caad12d7823875515201a27,539
8
8
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py,sha256=2604189b7598edb5404ddbcd0775bdf2dc506dd5e6319eef4e4724e39c420301,23276
9
9
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/config.py,sha256=972919b821b9b7e5dc7cd191ba7e78b30b6efa5d63514e8cb301996d6386392c,369
@@ -13,7 +13,7 @@ lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/span_utils.py,sha
13
13
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/streaming.py,sha256=7ca9f49e4d9a3bac292d13a8ee9827fdfb8a46d13ebdcbbfbac9c5584d11eaf3,13441
14
14
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/utils.py,sha256=0044f02da8b99322fdbf3f8f6663f04ff5d1295ddae92a635fd16eb685d5fbb6,5386
15
15
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/version.py,sha256=5aacde4ca55ef50ed07a239ad8a86889e0621b1cc72be19bd93be7c9e20910a9,23
16
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py,sha256=a47d4d1234e0278d1538748130a79c03d6cb3486976cb5d19578fe1b90f28e7b,20524
16
+ lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py,sha256=1eab6773a3c89c207fe0b508555062872cbf37674183a0eb4ed7e20785cef870,20701
17
17
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/config.py,sha256=db9cdebc9ee0dccb493ffe608eede3047efec20ed26c3924b72b2e50edbd92c2,245
18
18
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/schema_utils.py,sha256=857a6bc52f8bfd4da72786173615d31faaf3f9378f8f6150ffe8f6f9c4bb78f9,685
19
19
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py,sha256=f1248196246826d899304e510c4c2df74088d8169d28f1d0aed578a7a6c3cbfd,7669
@@ -35,27 +35,29 @@ lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/embeddings_wr
35
35
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_emitter.py,sha256=9c96455b5ca2064dd3a9fb570d78b14ebbdf3d02f8e33255ee9e301c31336c9e,3043
36
36
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_models.py,sha256=3c27c21b1aeb02bc19a91fb8c05717ae1c10ab4b01300c664aba42e0f50cb5a3,876
37
37
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py,sha256=9650a0e4ad2d3bfb2a072590da189bcf4f807aca070945af26a9f9b99d779b77,2021
38
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py,sha256=541e94d60c94b8a8035ee74cda00ca3576a3f50a215df03d948de58665dbc25b,4649
38
+ lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py,sha256=6b7ebc420e76c266f723248463c09175f536e10166015db12075e5e05f1c0f51,4651
39
39
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v0/__init__.py,sha256=7f43421e052bd8f64d5d5b03170a3b7187c2ce038362fa15b5d1d0c43bc1a40d,6143
40
40
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/__init__.py,sha256=7bdbf691ac89efb42ade686b7dbe69bd139a84f48482a013b7b68d3baa5b9c98,13527
41
41
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py,sha256=558036c734559b3526647c1b18cfb986699e8fb322855af72ea054c2e458f721,10404
42
42
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py,sha256=4809cde003e5892822828b373aa3e43a8adbaee4ff443f198401003f43c15e8a,4366
43
43
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py,sha256=b3853c60c58a36ba64de184211ac2d112bb8b53c4af62f0fc716fb87d168fd4b,24790
44
44
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/version.py,sha256=4f39aaa913f3e49b0c174bc23028687d00bfaffc745bd3fe241e0ae6b442bed1,24
45
+ lmnr/opentelemetry_lib/opentelemetry/instrumentation/openhands_ai/__init__.py,sha256=8f5f73528e1e0ced28938cbedf5b9774b408e178ae42191284f4630d0aa99beb,13648
45
46
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/opentelemetry/__init__.py,sha256=1f86cdf738e2f68586b0a4569bb1e40edddd85c529f511ef49945ddb7b61fab5,2648
46
47
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/skyvern/__init__.py,sha256=764e4fe979fb08d7821419a3cc5c3ae89a6664b626ef928259f8f175c939eaea,6334
47
48
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/threading/__init__.py,sha256=90aa8558467d7e469fe1a6c75372c113da403557715f03b522b2fab94b287c40,6320
48
49
  lmnr/opentelemetry_lib/tracing/__init__.py,sha256=b96aee7590af1853fffc4c3d8ce9127a67e1ce589f695a99aabe6b37d70b0e48,10203
49
- lmnr/opentelemetry_lib/tracing/_instrument_initializers.py,sha256=dfeace460aa0daceb34266f93321acdce6b61847ad2113f428a8cd9493a93a55,14830
50
+ lmnr/opentelemetry_lib/tracing/_instrument_initializers.py,sha256=f66dbe208fc1bd4075c0725561f6151a4c7622e6ecfa961b7ad6674d18dfb97f,15180
50
51
  lmnr/opentelemetry_lib/tracing/attributes.py,sha256=a879e337ff4e8569a4454544d303ccbc3b04bd42e1cdb765eb563aeaa08f731d,1653
51
52
  lmnr/opentelemetry_lib/tracing/context.py,sha256=83f842be0fc29a96647cbf005c39ea761b0fb5913c4102f965411f47906a6135,4103
52
53
  lmnr/opentelemetry_lib/tracing/exporter.py,sha256=6af8e61fd873e8f5db315d9b9f1edbf46b860ba7e50140f0bdcc6864c6d35a03,2082
53
- lmnr/opentelemetry_lib/tracing/instruments.py,sha256=993c5e4a827f0828ad4ee1ac9b18a2220e1b271a051ec05974f73cf82f48d172,5512
54
+ lmnr/opentelemetry_lib/tracing/instruments.py,sha256=950c351bbc05a7665e6c0eb0ed1bafc1b4b2a037ed1086786ec8496802ecef2e,5618
54
55
  lmnr/opentelemetry_lib/tracing/processor.py,sha256=cbc70f138e70c878ef57b02a2c46ef48dd7f694a522623a82dff1623b73d1e1c,3353
55
56
  lmnr/opentelemetry_lib/tracing/tracer.py,sha256=33769a9a97385f5697eb0e0a6b1813a57ed956c7a8379d7ac2523e700e7dd528,1362
56
57
  lmnr/opentelemetry_lib/utils/__init__.py,sha256=a4d85fd06def4dde5c728734de2d4c5c36eb89c49a8aa09b8b50cb5a149e90af,604
57
58
  lmnr/opentelemetry_lib/utils/json_encoder.py,sha256=74ae9bfdac6bef42182fb56ff9bbb8c27b6f0c3bb29eda2ab0769d76a5fb3f9f,463
58
59
  lmnr/opentelemetry_lib/utils/package_check.py,sha256=f8274186c96815c996a25fae06bf913f0bb7c835507739949f37c03bbe5d9ca9,527
60
+ lmnr/opentelemetry_lib/utils/wrappers.py,sha256=f7b1134809f2c408976a71e661fee5cb1aad172f4467846994205a60bf994e9f,330
59
61
  lmnr/py.typed,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
60
62
  lmnr/sdk/__init__.py,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
61
63
  lmnr/sdk/browser/__init__.py,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
@@ -63,7 +65,7 @@ lmnr/sdk/browser/browser_use_cdp_otel.py,sha256=aa14e3ea7bd0980a4b1fc2d9a5d40d38
63
65
  lmnr/sdk/browser/browser_use_otel.py,sha256=e5549878c07bad451efef9f460ce52202284cff50075bce700ca61749102c5eb,5065
64
66
  lmnr/sdk/browser/cdp_utils.py,sha256=3f43e4b9958e752b1b9aa0a3ae6321ef9e1cb66b3f4ca07990584b2c60c17a97,26103
65
67
  lmnr/sdk/browser/patchright_otel.py,sha256=9d22ab1f28f1eddbcfd0032a14fe306bfe00bfc7f11128cb99836c4dd15fb7c8,4800
66
- lmnr/sdk/browser/playwright_otel.py,sha256=50c0a5a75155a3a7ff5db84790ffb409c9cbd0351eef212d83d923893730223b,9459
68
+ lmnr/sdk/browser/playwright_otel.py,sha256=859d220d856c8fe7104863efca0c6a3ed5464d778675e07d7f79c48f73d5e838,10416
67
69
  lmnr/sdk/browser/pw_utils.py,sha256=a75769eb977d8e56c38a0eefad09b87550b872f8d4df186b36a8c4d4af2bffaf,29021
68
70
  lmnr/sdk/browser/recorder/record.umd.min.cjs,sha256=f09c09052c2fc474efb0405e63d8d26ed2184b994513ce8aee04efdac8be155d,181235
69
71
  lmnr/sdk/browser/utils.py,sha256=4a668776d2938108d25fbcecd61c8e1710a4da3e56230d5fefca5964dd09e3c1,2371
@@ -90,9 +92,9 @@ lmnr/sdk/evaluations.py,sha256=895d0b1af77395bddfbf69388679eb03f8f4d2ef03facab27
90
92
  lmnr/sdk/laminar.py,sha256=24d680407ce694f1a7ec0e9c0524eae3deb7d638ad5caff3a591ddf7963ad480,37533
91
93
  lmnr/sdk/log.py,sha256=9edfd83263f0d4845b1b2d1beeae2b4ed3f8628de941f371a893d72b79c348d4,2213
92
94
  lmnr/sdk/types.py,sha256=f8a8368e225c4d2f82df54d92f029065afb60c3eff494c77c6e574963ed524ff,13454
93
- lmnr/sdk/utils.py,sha256=4beb884ae6fbbc7d8cf639b036b726ea6a2a658f0a6386faf5735a13d706a2d8,5039
94
- lmnr/version.py,sha256=d3747c091ad12262da3123ec8cd3a9ecdd8ee197c270773b4953d9fb342c6c0c,1321
95
- lmnr-0.7.6.dist-info/WHEEL,sha256=ab6157bc637547491fb4567cd7ddf26b04d63382916ca16c29a5c8e94c9c9ef7,79
96
- lmnr-0.7.6.dist-info/entry_points.txt,sha256=abdf3411b7dd2d7329a241f2da6669bab4e314a747a586ecdb9f888f3035003c,39
97
- lmnr-0.7.6.dist-info/METADATA,sha256=2224332cbdca13b77e0b54b575efcc7d7923957dfabc51dfbe9f3988f8bd1ef9,14196
98
- lmnr-0.7.6.dist-info/RECORD,,
95
+ lmnr/sdk/utils.py,sha256=0c5a81c305dcd3922f4b31c4f42cf83719c03888725838395adae167de92db76,5019
96
+ lmnr/version.py,sha256=fafd83a5d984558d181b537237ee4ba4e9ccbad408b74e243065597b77580152,1321
97
+ lmnr-0.7.8.dist-info/WHEEL,sha256=ab6157bc637547491fb4567cd7ddf26b04d63382916ca16c29a5c8e94c9c9ef7,79
98
+ lmnr-0.7.8.dist-info/entry_points.txt,sha256=abdf3411b7dd2d7329a241f2da6669bab4e314a747a586ecdb9f888f3035003c,39
99
+ lmnr-0.7.8.dist-info/METADATA,sha256=272987c3279723e7609210aef23cf45f706ecd6750fc45d6a0e08d409302acf0,14196
100
+ lmnr-0.7.8.dist-info/RECORD,,
File without changes