lmnr 0.6.20__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. lmnr/__init__.py +0 -4
  2. lmnr/opentelemetry_lib/decorators/__init__.py +211 -151
  3. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py +678 -0
  4. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/config.py +13 -0
  5. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_emitter.py +211 -0
  6. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_models.py +41 -0
  7. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/span_utils.py +256 -0
  8. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/streaming.py +295 -0
  9. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/utils.py +179 -0
  10. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/version.py +1 -0
  11. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +4 -0
  12. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/__init__.py +488 -0
  13. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/config.py +8 -0
  14. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_emitter.py +143 -0
  15. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_models.py +41 -0
  16. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/span_utils.py +229 -0
  17. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/utils.py +92 -0
  18. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/version.py +1 -0
  19. lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +16 -16
  20. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +3 -0
  21. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +3 -0
  22. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py +3 -3
  23. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +3 -0
  24. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +7 -0
  25. lmnr/opentelemetry_lib/opentelemetry/instrumentation/threading/__init__.py +190 -0
  26. lmnr/opentelemetry_lib/tracing/__init__.py +90 -2
  27. lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +12 -7
  28. lmnr/opentelemetry_lib/tracing/context.py +109 -0
  29. lmnr/opentelemetry_lib/tracing/processor.py +6 -7
  30. lmnr/opentelemetry_lib/tracing/tracer.py +29 -0
  31. lmnr/opentelemetry_lib/utils/package_check.py +9 -0
  32. lmnr/sdk/browser/browser_use_otel.py +9 -7
  33. lmnr/sdk/browser/patchright_otel.py +14 -26
  34. lmnr/sdk/browser/playwright_otel.py +72 -73
  35. lmnr/sdk/browser/pw_utils.py +436 -119
  36. lmnr/sdk/client/asynchronous/resources/browser_events.py +1 -0
  37. lmnr/sdk/decorators.py +39 -4
  38. lmnr/sdk/evaluations.py +23 -9
  39. lmnr/sdk/laminar.py +181 -209
  40. lmnr/sdk/types.py +0 -6
  41. lmnr/version.py +1 -1
  42. {lmnr-0.6.20.dist-info → lmnr-0.7.0.dist-info}/METADATA +10 -8
  43. {lmnr-0.6.20.dist-info → lmnr-0.7.0.dist-info}/RECORD +45 -29
  44. {lmnr-0.6.20.dist-info → lmnr-0.7.0.dist-info}/WHEEL +1 -1
  45. lmnr/opentelemetry_lib/tracing/context_properties.py +0 -65
  46. {lmnr-0.6.20.dist-info → lmnr-0.7.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,190 @@
1
+ # Copyright The OpenTelemetry Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """
15
+ Instrument threading to propagate OpenTelemetry context.
16
+
17
+ Copied from opentelemetry-instrumentation-threading at commit:
18
+ ad2fe813abb2ab0b6e25bedeebef5041ca3189f7
19
+ https://github.com/open-telemetry/opentelemetry-python-contrib/blob/ad2fe813abb2ab0b6e25bedeebef5041ca3189f7/instrumentation/opentelemetry-instrumentation-threading/src/opentelemetry/instrumentation/threading/__init__.py
20
+
21
+ Modified to use the Laminar isolated context.
22
+
23
+ Usage
24
+ -----
25
+
26
+ .. code-block:: python
27
+
28
+ from opentelemetry.instrumentation.threading import ThreadingInstrumentor
29
+
30
+ ThreadingInstrumentor().instrument()
31
+
32
+ This library provides instrumentation for the `threading` module to ensure that
33
+ the OpenTelemetry context is propagated across threads. It is important to note
34
+ that this instrumentation does not produce any telemetry data on its own. It
35
+ merely ensures that the context is correctly propagated when threads are used.
36
+
37
+
38
+ When instrumented, new threads created using threading.Thread, threading.Timer,
39
+ or within futures.ThreadPoolExecutor will have the current OpenTelemetry
40
+ context attached, and this context will be re-activated in the thread's
41
+ run method or the executor's worker thread."
42
+ """
43
+
44
+ from __future__ import annotations
45
+
46
+ import threading
47
+ from concurrent import futures
48
+ from typing import TYPE_CHECKING, Any, Callable, Collection
49
+
50
+ from wrapt import (
51
+ wrap_function_wrapper, # type: ignore[reportUnknownVariableType]
52
+ )
53
+
54
+ from lmnr.opentelemetry_lib.tracing.context import (
55
+ get_current_context,
56
+ attach_context,
57
+ detach_context,
58
+ )
59
+ from opentelemetry import context
60
+ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
61
+ from opentelemetry.instrumentation.utils import unwrap
62
+
63
+ _instruments = ()
64
+
65
+ if TYPE_CHECKING:
66
+ from typing import Protocol, TypeVar
67
+
68
+ R = TypeVar("R")
69
+
70
+ class HasOtelContext(Protocol):
71
+ _otel_context: context.Context
72
+
73
+
74
+ class ThreadingInstrumentor(BaseInstrumentor):
75
+ __WRAPPER_START_METHOD = "start"
76
+ __WRAPPER_RUN_METHOD = "run"
77
+ __WRAPPER_SUBMIT_METHOD = "submit"
78
+
79
+ def instrumentation_dependencies(self) -> Collection[str]:
80
+ return _instruments
81
+
82
+ def _instrument(self, **kwargs: Any):
83
+ self._instrument_thread()
84
+ self._instrument_timer()
85
+ self._instrument_thread_pool()
86
+
87
+ def _uninstrument(self, **kwargs: Any):
88
+ self._uninstrument_thread()
89
+ self._uninstrument_timer()
90
+ self._uninstrument_thread_pool()
91
+
92
+ @staticmethod
93
+ def _instrument_thread():
94
+ wrap_function_wrapper(
95
+ threading.Thread,
96
+ ThreadingInstrumentor.__WRAPPER_START_METHOD,
97
+ ThreadingInstrumentor.__wrap_threading_start,
98
+ )
99
+ wrap_function_wrapper(
100
+ threading.Thread,
101
+ ThreadingInstrumentor.__WRAPPER_RUN_METHOD,
102
+ ThreadingInstrumentor.__wrap_threading_run,
103
+ )
104
+
105
+ @staticmethod
106
+ def _instrument_timer():
107
+ wrap_function_wrapper(
108
+ threading.Timer,
109
+ ThreadingInstrumentor.__WRAPPER_START_METHOD,
110
+ ThreadingInstrumentor.__wrap_threading_start,
111
+ )
112
+ wrap_function_wrapper(
113
+ threading.Timer,
114
+ ThreadingInstrumentor.__WRAPPER_RUN_METHOD,
115
+ ThreadingInstrumentor.__wrap_threading_run,
116
+ )
117
+
118
+ @staticmethod
119
+ def _instrument_thread_pool():
120
+ wrap_function_wrapper(
121
+ futures.ThreadPoolExecutor,
122
+ ThreadingInstrumentor.__WRAPPER_SUBMIT_METHOD,
123
+ ThreadingInstrumentor.__wrap_thread_pool_submit,
124
+ )
125
+
126
+ @staticmethod
127
+ def _uninstrument_thread():
128
+ unwrap(threading.Thread, ThreadingInstrumentor.__WRAPPER_START_METHOD)
129
+ unwrap(threading.Thread, ThreadingInstrumentor.__WRAPPER_RUN_METHOD)
130
+
131
+ @staticmethod
132
+ def _uninstrument_timer():
133
+ unwrap(threading.Timer, ThreadingInstrumentor.__WRAPPER_START_METHOD)
134
+ unwrap(threading.Timer, ThreadingInstrumentor.__WRAPPER_RUN_METHOD)
135
+
136
+ @staticmethod
137
+ def _uninstrument_thread_pool():
138
+ unwrap(
139
+ futures.ThreadPoolExecutor,
140
+ ThreadingInstrumentor.__WRAPPER_SUBMIT_METHOD,
141
+ )
142
+
143
+ @staticmethod
144
+ def __wrap_threading_start(
145
+ call_wrapped: Callable[[], None],
146
+ instance: HasOtelContext,
147
+ args: tuple[()],
148
+ kwargs: dict[str, Any],
149
+ ) -> None:
150
+ instance._otel_context = get_current_context()
151
+ return call_wrapped(*args, **kwargs)
152
+
153
+ @staticmethod
154
+ def __wrap_threading_run(
155
+ call_wrapped: Callable[..., R],
156
+ instance: HasOtelContext,
157
+ args: tuple[Any, ...],
158
+ kwargs: dict[str, Any],
159
+ ) -> R:
160
+ token = None
161
+ try:
162
+ token = attach_context(instance._otel_context)
163
+ return call_wrapped(*args, **kwargs)
164
+ finally:
165
+ if token is not None:
166
+ detach_context(token)
167
+
168
+ @staticmethod
169
+ def __wrap_thread_pool_submit(
170
+ call_wrapped: Callable[..., R],
171
+ instance: futures.ThreadPoolExecutor,
172
+ args: tuple[Callable[..., Any], ...],
173
+ kwargs: dict[str, Any],
174
+ ) -> R:
175
+ # obtain the original function and wrapped kwargs
176
+ original_func = args[0]
177
+ otel_context = get_current_context()
178
+
179
+ def wrapped_func(*func_args: Any, **func_kwargs: Any) -> R:
180
+ token = None
181
+ try:
182
+ token = attach_context(otel_context)
183
+ return original_func(*func_args, **func_kwargs)
184
+ finally:
185
+ if token is not None:
186
+ detach_context(token)
187
+
188
+ # replace the original function with the wrapped function
189
+ new_args: tuple[Callable[..., Any], ...] = (wrapped_func,) + args[1:]
190
+ return call_wrapped(*new_args, **kwargs)
@@ -10,9 +10,22 @@ from lmnr.opentelemetry_lib.tracing.instruments import (
10
10
  Instruments,
11
11
  init_instrumentations,
12
12
  )
13
+ from lmnr.opentelemetry_lib.tracing.context import (
14
+ attach_context,
15
+ detach_context,
16
+ get_current_context,
17
+ get_token_stack,
18
+ _isolated_token_stack,
19
+ _isolated_token_stack_storage,
20
+ set_token_stack,
21
+ )
13
22
 
14
23
  from opentelemetry import trace
15
- from opentelemetry.instrumentation.threading import ThreadingInstrumentor
24
+ from opentelemetry.context import Context
25
+
26
+ # instead of importing from opentelemetry.instrumentation.threading,
27
+ # we import from our modified copy to use Laminar's isolated context.
28
+ from ..opentelemetry.instrumentation.threading import ThreadingInstrumentor
16
29
  from opentelemetry.sdk.resources import Resource
17
30
  from opentelemetry.sdk.trace import TracerProvider, SpanProcessor
18
31
  from opentelemetry.sdk.trace.export import SpanExporter
@@ -32,6 +45,7 @@ class TracerWrapper(object):
32
45
  _async_client: AsyncLaminarClient
33
46
  _resource: Resource
34
47
  _span_processor: SpanProcessor
48
+ _original_thread_init = None
35
49
 
36
50
  def __new__(
37
51
  cls,
@@ -45,7 +59,7 @@ class TracerWrapper(object):
45
59
  project_api_key: str | None = None,
46
60
  max_export_batch_size: int | None = None,
47
61
  force_http: bool = False,
48
- timeout_seconds: int = 10,
62
+ timeout_seconds: int = 30,
49
63
  set_global_tracer_provider: bool = True,
50
64
  otel_logger_level: int = logging.ERROR,
51
65
  ) -> "TracerWrapper":
@@ -91,6 +105,9 @@ class TracerWrapper(object):
91
105
 
92
106
  obj._tracer_provider.add_span_processor(obj._span_processor)
93
107
 
108
+ # Setup threading context inheritance
109
+ obj._setup_threading_inheritance()
110
+
94
111
  # This is not a real instrumentation and does not generate telemetry
95
112
  # data, but it is required to ensure that OpenTelemetry context
96
113
  # propagation is enabled.
@@ -113,6 +130,43 @@ class TracerWrapper(object):
113
130
 
114
131
  return cls.instance
115
132
 
133
+ def _setup_threading_inheritance(self):
134
+ """Setup threading inheritance for isolated context."""
135
+ if TracerWrapper._original_thread_init is None:
136
+ # Monkey patch Thread.__init__ to capture context inheritance
137
+ TracerWrapper._original_thread_init = threading.Thread.__init__
138
+
139
+ def patched_thread_init(thread_self, *args, **kwargs):
140
+ # Capture current isolated context and token stack for inheritance
141
+ current_context = get_current_context()
142
+ current_token_stack = get_token_stack().copy()
143
+
144
+ # Get the original target function
145
+ original_target = kwargs.get("target")
146
+ if not original_target and args:
147
+ original_target = args[0]
148
+
149
+ # Only inherit if we have a target function
150
+ if original_target:
151
+ # Create a wrapper function that sets up context
152
+ def thread_wrapper(*target_args, **target_kwargs):
153
+ # Set inherited context and token stack in the new thread
154
+ attach_context(current_context)
155
+ set_token_stack(current_token_stack)
156
+ # Run original target
157
+ return original_target(*target_args, **target_kwargs)
158
+
159
+ # Replace the target with our wrapper
160
+ if "target" in kwargs:
161
+ kwargs["target"] = thread_wrapper
162
+ elif args:
163
+ args = (thread_wrapper,) + args[1:]
164
+
165
+ # Call original init
166
+ TracerWrapper._original_thread_init(thread_self, *args, **kwargs)
167
+
168
+ threading.Thread.__init__ = patched_thread_init
169
+
116
170
  def exit_handler(self):
117
171
  if isinstance(self._span_processor, LaminarSpanProcessor):
118
172
  self._span_processor.clear()
@@ -124,6 +178,31 @@ class TracerWrapper(object):
124
178
  console_log_handler.setFormatter(VerboseColorfulFormatter())
125
179
  self._logger.addHandler(console_log_handler)
126
180
 
181
+ def get_isolated_context(self) -> Context:
182
+ """Get the current isolated context."""
183
+ return get_current_context()
184
+
185
+ def push_span_context(self, span: trace.Span) -> Context:
186
+ """Push a new context with the given span onto the stack."""
187
+ current_ctx = get_current_context()
188
+ new_context = trace.set_span_in_context(span, current_ctx)
189
+ token = attach_context(new_context)
190
+
191
+ # Store the token for later detachment - tokens are much lighter than contexts
192
+ current_stack = get_token_stack().copy()
193
+ current_stack.append(token)
194
+ set_token_stack(current_stack)
195
+
196
+ return new_context
197
+
198
+ def pop_span_context(self) -> None:
199
+ """Pop the current span context from the stack."""
200
+ current_stack = get_token_stack().copy()
201
+ if current_stack:
202
+ token = current_stack.pop()
203
+ set_token_stack(current_stack)
204
+ detach_context(token)
205
+
127
206
  @staticmethod
128
207
  def set_static_params(
129
208
  resource_attributes: dict,
@@ -144,6 +223,15 @@ class TracerWrapper(object):
144
223
  # Any state cleanup. Now used in between tests
145
224
  if isinstance(cls.instance._span_processor, LaminarSpanProcessor):
146
225
  cls.instance._span_processor.clear()
226
+ # Clear the isolated context state for clean test state
227
+ try:
228
+ _isolated_token_stack.set([])
229
+ except LookupError:
230
+ pass
231
+ if hasattr(_isolated_token_stack_storage, "token_stack"):
232
+ _isolated_token_stack_storage.token_stack = []
233
+ # Reset the isolated context to a fresh state
234
+ attach_context(Context())
147
235
 
148
236
  def shutdown(self):
149
237
  if self._tracer_provider is None:
@@ -2,7 +2,10 @@ import abc
2
2
 
3
3
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
4
4
 
5
- from lmnr.opentelemetry_lib.utils.package_check import is_package_installed
5
+ from lmnr.opentelemetry_lib.utils.package_check import (
6
+ get_package_version,
7
+ is_package_installed,
8
+ )
6
9
 
7
10
 
8
11
  class InstrumentorInitializer(abc.ABC):
@@ -27,10 +30,8 @@ class AnthropicInstrumentorInitializer(InstrumentorInitializer):
27
30
  def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
28
31
  if not is_package_installed("anthropic"):
29
32
  return None
30
- if not is_package_installed("opentelemetry-instrumentation-anthropic"):
31
- return None
32
33
 
33
- from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
34
+ from ..opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
34
35
 
35
36
  return AnthropicInstrumentor(
36
37
  upload_base64_image=None,
@@ -54,6 +55,12 @@ class BrowserUseInstrumentorInitializer(InstrumentorInitializer):
54
55
  if not is_package_installed("browser-use"):
55
56
  return None
56
57
 
58
+ version = get_package_version("browser-use")
59
+ from packaging.version import parse
60
+
61
+ if version and parse(version) >= parse("0.5.0"):
62
+ return None
63
+
57
64
  from lmnr.sdk.browser.browser_use_otel import BrowserUseInstrumentor
58
65
 
59
66
  return BrowserUseInstrumentor()
@@ -127,10 +134,8 @@ class GroqInstrumentorInitializer(InstrumentorInitializer):
127
134
  def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
128
135
  if not is_package_installed("groq"):
129
136
  return None
130
- if not is_package_installed("opentelemetry-instrumentation-groq"):
131
- return None
132
137
 
133
- from opentelemetry.instrumentation.groq import GroqInstrumentor
138
+ from ..opentelemetry.instrumentation.groq import GroqInstrumentor
134
139
 
135
140
  return GroqInstrumentor()
136
141
 
@@ -0,0 +1,109 @@
1
+ import threading
2
+
3
+ from abc import ABC, abstractmethod
4
+ from contextvars import ContextVar
5
+ from opentelemetry.context import Context, Token
6
+
7
+
8
+ class _IsolatedRuntimeContext(ABC):
9
+ """The isolated RuntimeContext interface, identical to OpenTelemetry's _RuntimeContext
10
+ but isolated from the global context.
11
+ """
12
+
13
+ @abstractmethod
14
+ def attach(self, context: Context) -> Token[Context]:
15
+ """Sets the current `Context` object. Returns a
16
+ token that can be used to reset to the previous `Context`.
17
+
18
+ Args:
19
+ context: The Context to set.
20
+ """
21
+
22
+ @abstractmethod
23
+ def get_current(self) -> Context:
24
+ """Returns the current `Context` object."""
25
+
26
+ @abstractmethod
27
+ def detach(self, token: Token[Context]) -> None:
28
+ """Resets Context to a previous value
29
+
30
+ Args:
31
+ token: A reference to a previous Context.
32
+ """
33
+
34
+
35
+ class IsolatedContextVarsRuntimeContext(_IsolatedRuntimeContext):
36
+ """An isolated implementation of the RuntimeContext interface which wraps ContextVar
37
+ but uses its own ContextVar instead of the global one.
38
+ """
39
+
40
+ def __init__(self) -> None:
41
+ self._current_context = ContextVar(
42
+ "isolated_current_context", default=Context()
43
+ )
44
+
45
+ def attach(self, context: Context) -> Token[Context]:
46
+ """Sets the current `Context` object. Returns a
47
+ token that can be used to reset to the previous `Context`.
48
+
49
+ Args:
50
+ context: The Context to set.
51
+ """
52
+ return self._current_context.set(context)
53
+
54
+ def get_current(self) -> Context:
55
+ """Returns the current `Context` object."""
56
+ return self._current_context.get()
57
+
58
+ def detach(self, token: Token[Context]) -> None:
59
+ """Resets Context to a previous value
60
+
61
+ Args:
62
+ token: A reference to a previous Context.
63
+ """
64
+ self._current_context.reset(token)
65
+
66
+
67
+ # Create the isolated runtime context
68
+ _ISOLATED_RUNTIME_CONTEXT = IsolatedContextVarsRuntimeContext()
69
+
70
+ # Token stack for push/pop API compatibility - much lighter than copying contexts
71
+ _isolated_token_stack: ContextVar[list[Token[Context]]] = ContextVar(
72
+ "isolated_token_stack", default=[]
73
+ )
74
+
75
+ # Thread-local storage for threading support
76
+ _isolated_token_stack_storage = threading.local()
77
+
78
+
79
+ def get_token_stack() -> list[Token[Context]]:
80
+ """Get the token stack, supporting both asyncio and threading."""
81
+ try:
82
+ return _isolated_token_stack.get()
83
+ except LookupError:
84
+ if not hasattr(_isolated_token_stack_storage, "token_stack"):
85
+ _isolated_token_stack_storage.token_stack = []
86
+ return _isolated_token_stack_storage.token_stack
87
+
88
+
89
+ def set_token_stack(stack: list[Token[Context]]) -> None:
90
+ """Set the token stack, supporting both asyncio and threading."""
91
+ try:
92
+ _isolated_token_stack.set(stack)
93
+ except LookupError:
94
+ _isolated_token_stack_storage.token_stack = stack
95
+
96
+
97
+ def get_current_context() -> Context:
98
+ """Get the current isolated context."""
99
+ return _ISOLATED_RUNTIME_CONTEXT.get_current()
100
+
101
+
102
+ def attach_context(context: Context) -> Token[Context]:
103
+ """Attach a context to the isolated runtime context."""
104
+ return _ISOLATED_RUNTIME_CONTEXT.attach(context)
105
+
106
+
107
+ def detach_context(token: Token[Context]) -> None:
108
+ """Detach a context from the isolated runtime context."""
109
+ _ISOLATED_RUNTIME_CONTEXT.detach(token)
@@ -17,9 +17,6 @@ from lmnr.opentelemetry_lib.tracing.attributes import (
17
17
  SPAN_SDK_VERSION,
18
18
  )
19
19
  from lmnr.opentelemetry_lib.tracing.exporter import LaminarSpanExporter
20
- from lmnr.opentelemetry_lib.tracing.context_properties import (
21
- _set_association_properties_attributes,
22
- )
23
20
  from lmnr.version import PYTHON_VERSION, __version__
24
21
 
25
22
 
@@ -35,7 +32,7 @@ class LaminarSpanProcessor(SpanProcessor):
35
32
  api_key: str | None = None,
36
33
  timeout_seconds: int = 30,
37
34
  force_http: bool = False,
38
- max_export_batch_size: int = 512,
35
+ max_export_batch_size: int = 64,
39
36
  disable_batch: bool = False,
40
37
  exporter: SpanExporter | None = None,
41
38
  ):
@@ -76,9 +73,11 @@ class LaminarSpanProcessor(SpanProcessor):
76
73
  span.set_attribute(SPAN_SDK_VERSION, __version__)
77
74
  span.set_attribute(SPAN_LANGUAGE_VERSION, f"python@{PYTHON_VERSION}")
78
75
 
79
- association_properties = get_value("association_properties")
80
- if association_properties is not None:
81
- _set_association_properties_attributes(span, association_properties)
76
+ if span.name == "LangGraph.workflow":
77
+ graph_context = get_value("lmnr.langgraph.graph") or {}
78
+ for key, value in graph_context.items():
79
+ span.set_attribute(f"lmnr.association.properties.{key}", value)
80
+
82
81
  self.instance.on_start(span, parent_context)
83
82
 
84
83
  def on_end(self, span: Span):
@@ -1,6 +1,8 @@
1
1
  from contextlib import contextmanager
2
+ from typing import Generator, Tuple
2
3
 
3
4
  from opentelemetry import trace
5
+ from opentelemetry.context import Context
4
6
  from lmnr.opentelemetry_lib.tracing import TracerWrapper
5
7
 
6
8
 
@@ -16,3 +18,30 @@ def get_tracer(flush_on_exit: bool = False):
16
18
  finally:
17
19
  if flush_on_exit:
18
20
  wrapper.flush()
21
+
22
+
23
+ @contextmanager
24
+ def get_tracer_with_context(
25
+ flush_on_exit: bool = False,
26
+ ) -> Generator[Tuple[trace.Tracer, Context], None, None]:
27
+ """Get tracer with isolated context. Returns (tracer, context) tuple."""
28
+ wrapper = TracerWrapper()
29
+ try:
30
+ tracer = wrapper.get_tracer()
31
+ context = wrapper.get_isolated_context()
32
+ yield tracer, context
33
+ finally:
34
+ if flush_on_exit:
35
+ wrapper.flush()
36
+
37
+
38
+ def copy_current_context() -> Context:
39
+ """Copy the current isolated context for use in threads/tasks."""
40
+ wrapper = TracerWrapper()
41
+ return wrapper.get_isolated_context()
42
+
43
+
44
+ def set_context_for_thread(context: Context) -> None:
45
+ """Set the isolated context for the current thread."""
46
+ wrapper = TracerWrapper()
47
+ wrapper.set_isolated_context(context)
@@ -1,5 +1,7 @@
1
1
  from importlib.metadata import distributions
2
2
 
3
+ from typing import Optional
4
+
3
5
  installed_packages = {
4
6
  (dist.name or dist.metadata.get("Name", "")).lower() for dist in distributions()
5
7
  }
@@ -7,3 +9,10 @@ installed_packages = {
7
9
 
8
10
  def is_package_installed(package_name: str) -> bool:
9
11
  return package_name.lower() in installed_packages
12
+
13
+
14
+ def get_package_version(package_name: str) -> Optional[str]:
15
+ for dist in distributions():
16
+ if (dist.name or dist.metadata.get("Name", "")).lower() == package_name.lower():
17
+ return dist.version
18
+ return None
@@ -1,4 +1,5 @@
1
1
  from lmnr.opentelemetry_lib.decorators import json_dumps
2
+ from lmnr.sdk.laminar import Laminar
2
3
  from lmnr.sdk.browser.utils import with_tracer_wrapper
3
4
  from lmnr.sdk.utils import get_input_from_func_args
4
5
  from lmnr.version import __version__
@@ -19,7 +20,7 @@ except ImportError as e:
19
20
  "to install Browser Use or remove this import."
20
21
  ) from e
21
22
 
22
- _instruments = ("browser-use < 0.5.0",)
23
+ _instruments = ("browser-use >= 0.1.0",)
23
24
 
24
25
  WRAPPED_METHODS = [
25
26
  {
@@ -27,8 +28,8 @@ WRAPPED_METHODS = [
27
28
  "object": "Agent",
28
29
  "method": "run",
29
30
  "span_name": "agent.run",
30
- "ignore_input": False,
31
- "ignore_output": False,
31
+ "ignore_input": True,
32
+ "ignore_output": True,
32
33
  "span_type": "DEFAULT",
33
34
  },
34
35
  {
@@ -46,15 +47,15 @@ WRAPPED_METHODS = [
46
47
  "method": "act",
47
48
  "span_name": "controller.act",
48
49
  "ignore_input": True,
49
- "ignore_output": False,
50
+ "ignore_output": True,
50
51
  "span_type": "DEFAULT",
51
52
  },
52
53
  {
53
54
  "package": "browser_use.controller.registry.service",
54
55
  "object": "Registry",
55
56
  "method": "execute_action",
56
- "ignore_input": False,
57
- "ignore_output": False,
57
+ "ignore_input": True,
58
+ "ignore_output": True,
58
59
  "span_type": "TOOL",
59
60
  },
60
61
  ]
@@ -86,7 +87,8 @@ async def _wrap(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
86
87
  step_info = kwargs.get("step_info", args[0] if len(args) > 0 else None)
87
88
  if step_info and hasattr(step_info, "step_number"):
88
89
  span_name = f"agent.step.{step_info.step_number}"
89
- with tracer.start_as_current_span(span_name, attributes=attributes) as span:
90
+
91
+ with Laminar.start_as_current_span(span_name) as span:
90
92
  span.set_attributes(attributes)
91
93
  result = await wrapped(*args, **kwargs)
92
94
  if not to_wrap.get("ignore_output"):