lmnr 0.6.16__py3-none-any.whl → 0.7.26__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 +6 -15
- lmnr/cli/__init__.py +270 -0
- lmnr/cli/datasets.py +371 -0
- lmnr/{cli.py → cli/evals.py} +20 -102
- lmnr/cli/rules.py +42 -0
- lmnr/opentelemetry_lib/__init__.py +9 -2
- lmnr/opentelemetry_lib/decorators/__init__.py +274 -168
- lmnr/opentelemetry_lib/litellm/__init__.py +352 -38
- lmnr/opentelemetry_lib/litellm/utils.py +82 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py +849 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/config.py +13 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_emitter.py +211 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_models.py +41 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/span_utils.py +401 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/streaming.py +425 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/utils.py +332 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/version.py +1 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/__init__.py +451 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/proxy.py +144 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_agent/__init__.py +100 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/__init__.py +476 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/utils.py +12 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +191 -129
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/schema_utils.py +26 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py +126 -41
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/__init__.py +488 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/config.py +8 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_emitter.py +143 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_models.py +41 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/span_utils.py +229 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/utils.py +92 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/version.py +1 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/__init__.py +381 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/utils.py +36 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +16 -16
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/__init__.py +61 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/__init__.py +472 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +1185 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +305 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/config.py +16 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +312 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_emitter.py +100 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_models.py +41 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +68 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py +197 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v0/__init__.py +176 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/__init__.py +368 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +325 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +135 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +786 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/version.py +1 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/openhands_ai/__init__.py +388 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/opentelemetry/__init__.py +69 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/skyvern/__init__.py +59 -61
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/threading/__init__.py +197 -0
- lmnr/opentelemetry_lib/tracing/__init__.py +119 -18
- lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +124 -25
- lmnr/opentelemetry_lib/tracing/attributes.py +4 -0
- lmnr/opentelemetry_lib/tracing/context.py +200 -0
- lmnr/opentelemetry_lib/tracing/exporter.py +109 -15
- lmnr/opentelemetry_lib/tracing/instruments.py +22 -5
- lmnr/opentelemetry_lib/tracing/processor.py +128 -30
- lmnr/opentelemetry_lib/tracing/span.py +398 -0
- lmnr/opentelemetry_lib/tracing/tracer.py +40 -1
- lmnr/opentelemetry_lib/tracing/utils.py +62 -0
- lmnr/opentelemetry_lib/utils/package_check.py +9 -0
- lmnr/opentelemetry_lib/utils/wrappers.py +11 -0
- lmnr/sdk/browser/background_send_events.py +158 -0
- lmnr/sdk/browser/browser_use_cdp_otel.py +100 -0
- lmnr/sdk/browser/browser_use_otel.py +12 -12
- lmnr/sdk/browser/bubus_otel.py +71 -0
- lmnr/sdk/browser/cdp_utils.py +518 -0
- lmnr/sdk/browser/inject_script.js +514 -0
- lmnr/sdk/browser/patchright_otel.py +18 -44
- lmnr/sdk/browser/playwright_otel.py +104 -187
- lmnr/sdk/browser/pw_utils.py +249 -210
- lmnr/sdk/browser/recorder/record.umd.min.cjs +84 -0
- lmnr/sdk/browser/utils.py +1 -1
- lmnr/sdk/client/asynchronous/async_client.py +47 -15
- lmnr/sdk/client/asynchronous/resources/__init__.py +2 -7
- lmnr/sdk/client/asynchronous/resources/browser_events.py +1 -0
- lmnr/sdk/client/asynchronous/resources/datasets.py +131 -0
- lmnr/sdk/client/asynchronous/resources/evals.py +122 -18
- lmnr/sdk/client/asynchronous/resources/evaluators.py +85 -0
- lmnr/sdk/client/asynchronous/resources/tags.py +4 -10
- lmnr/sdk/client/synchronous/resources/__init__.py +2 -2
- lmnr/sdk/client/synchronous/resources/datasets.py +131 -0
- lmnr/sdk/client/synchronous/resources/evals.py +83 -17
- lmnr/sdk/client/synchronous/resources/evaluators.py +85 -0
- lmnr/sdk/client/synchronous/resources/tags.py +4 -10
- lmnr/sdk/client/synchronous/sync_client.py +47 -15
- lmnr/sdk/datasets/__init__.py +94 -0
- lmnr/sdk/datasets/file_utils.py +91 -0
- lmnr/sdk/decorators.py +103 -23
- lmnr/sdk/evaluations.py +122 -33
- lmnr/sdk/laminar.py +816 -333
- lmnr/sdk/log.py +7 -2
- lmnr/sdk/types.py +124 -143
- lmnr/sdk/utils.py +115 -2
- lmnr/version.py +1 -1
- {lmnr-0.6.16.dist-info → lmnr-0.7.26.dist-info}/METADATA +71 -78
- lmnr-0.7.26.dist-info/RECORD +116 -0
- lmnr-0.7.26.dist-info/WHEEL +4 -0
- lmnr-0.7.26.dist-info/entry_points.txt +3 -0
- lmnr/opentelemetry_lib/tracing/context_properties.py +0 -65
- lmnr/sdk/browser/rrweb/rrweb.umd.min.cjs +0 -98
- lmnr/sdk/client/asynchronous/resources/agent.py +0 -329
- lmnr/sdk/client/synchronous/resources/agent.py +0 -323
- lmnr/sdk/datasets.py +0 -60
- lmnr-0.6.16.dist-info/LICENSE +0 -75
- lmnr-0.6.16.dist-info/RECORD +0 -61
- lmnr-0.6.16.dist-info/WHEEL +0 -4
- lmnr-0.6.16.dist-info/entry_points.txt +0 -3
|
@@ -0,0 +1,197 @@
|
|
|
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._lmnr_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
|
+
# Genearally, this must be set in __wrap_threading_start, but it is
|
|
163
|
+
# possible to Thread().run() without Thread().start(), so in that case,
|
|
164
|
+
# we need to capture the context here.
|
|
165
|
+
# We still want to capture the context in __wrap_threading_start,
|
|
166
|
+
# in order to stay close to the original implementation.
|
|
167
|
+
if not hasattr(instance, "_lmnr_otel_context"):
|
|
168
|
+
instance._lmnr_otel_context = get_current_context()
|
|
169
|
+
token = attach_context(instance._lmnr_otel_context)
|
|
170
|
+
return call_wrapped(*args, **kwargs)
|
|
171
|
+
finally:
|
|
172
|
+
if token is not None:
|
|
173
|
+
detach_context(token)
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def __wrap_thread_pool_submit(
|
|
177
|
+
call_wrapped: Callable[..., R],
|
|
178
|
+
instance: futures.ThreadPoolExecutor,
|
|
179
|
+
args: tuple[Callable[..., Any], ...],
|
|
180
|
+
kwargs: dict[str, Any],
|
|
181
|
+
) -> R:
|
|
182
|
+
# obtain the original function and wrapped kwargs
|
|
183
|
+
original_func = args[0]
|
|
184
|
+
otel_context = get_current_context()
|
|
185
|
+
|
|
186
|
+
def wrapped_func(*func_args: Any, **func_kwargs: Any) -> R:
|
|
187
|
+
token = None
|
|
188
|
+
try:
|
|
189
|
+
token = attach_context(otel_context)
|
|
190
|
+
return original_func(*func_args, **func_kwargs)
|
|
191
|
+
finally:
|
|
192
|
+
if token is not None:
|
|
193
|
+
detach_context(token)
|
|
194
|
+
|
|
195
|
+
# replace the original function with the wrapped function
|
|
196
|
+
new_args: tuple[Callable[..., Any], ...] = (wrapped_func,) + args[1:]
|
|
197
|
+
return call_wrapped(*new_args, **kwargs)
|
|
@@ -4,15 +4,28 @@ import threading
|
|
|
4
4
|
|
|
5
5
|
from lmnr.opentelemetry_lib.tracing.processor import LaminarSpanProcessor
|
|
6
6
|
from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
|
|
7
|
-
from lmnr.sdk.
|
|
7
|
+
from lmnr.sdk.types import SessionRecordingOptions
|
|
8
8
|
from lmnr.sdk.log import VerboseColorfulFormatter
|
|
9
9
|
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
|
+
clear_context,
|
|
16
|
+
pop_span_context as ctx_pop_span_context,
|
|
17
|
+
get_current_context,
|
|
18
|
+
get_token_stack,
|
|
19
|
+
push_span_context as ctx_push_span_context,
|
|
20
|
+
set_token_stack,
|
|
21
|
+
)
|
|
13
22
|
|
|
14
23
|
from opentelemetry import trace
|
|
15
|
-
from opentelemetry.
|
|
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
|
|
@@ -25,13 +38,14 @@ MAX_EVENTS_OR_ATTRIBUTES_PER_SPAN = 5000
|
|
|
25
38
|
class TracerWrapper(object):
|
|
26
39
|
resource_attributes: dict = {}
|
|
27
40
|
enable_content_tracing: bool = True
|
|
41
|
+
session_recording_options: SessionRecordingOptions = {}
|
|
28
42
|
_lock = threading.Lock()
|
|
29
43
|
_tracer_provider: TracerProvider | None = None
|
|
30
44
|
_logger: logging.Logger
|
|
31
|
-
_client: LaminarClient
|
|
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,
|
|
@@ -39,33 +53,36 @@ class TracerWrapper(object):
|
|
|
39
53
|
exporter: SpanExporter | None = None,
|
|
40
54
|
instruments: set[Instruments] | None = None,
|
|
41
55
|
block_instruments: set[Instruments] | None = None,
|
|
42
|
-
base_url: str =
|
|
56
|
+
base_url: str | None = None,
|
|
43
57
|
port: int = 8443,
|
|
44
58
|
http_port: int = 443,
|
|
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 =
|
|
62
|
+
timeout_seconds: int = 30,
|
|
49
63
|
set_global_tracer_provider: bool = True,
|
|
50
64
|
otel_logger_level: int = logging.ERROR,
|
|
65
|
+
session_recording_options: SessionRecordingOptions | None = None,
|
|
51
66
|
) -> "TracerWrapper":
|
|
52
67
|
# Silence some opentelemetry warnings
|
|
53
68
|
logging.getLogger("opentelemetry.trace").setLevel(otel_logger_level)
|
|
54
69
|
|
|
55
|
-
base_http_url = f"{base_url}:{http_port}"
|
|
70
|
+
base_http_url = f"{base_url}:{http_port}" if base_url else None
|
|
56
71
|
with cls._lock:
|
|
57
72
|
if not hasattr(cls, "instance"):
|
|
58
73
|
cls._initialize_logger(cls)
|
|
59
74
|
obj = super(TracerWrapper, cls).__new__(cls)
|
|
60
75
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
# Store session recording options
|
|
77
|
+
cls.session_recording_options = session_recording_options or {}
|
|
78
|
+
|
|
79
|
+
if project_api_key:
|
|
80
|
+
obj._async_client = AsyncLaminarClient(
|
|
81
|
+
base_url=base_http_url or "https://api.lmnr.ai",
|
|
82
|
+
project_api_key=project_api_key,
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
obj._async_client = None
|
|
69
86
|
|
|
70
87
|
obj._resource = Resource(attributes=TracerWrapper.resource_attributes)
|
|
71
88
|
|
|
@@ -91,6 +108,9 @@ class TracerWrapper(object):
|
|
|
91
108
|
|
|
92
109
|
obj._tracer_provider.add_span_processor(obj._span_processor)
|
|
93
110
|
|
|
111
|
+
# Setup threading context inheritance
|
|
112
|
+
obj._setup_threading_inheritance()
|
|
113
|
+
|
|
94
114
|
# This is not a real instrumentation and does not generate telemetry
|
|
95
115
|
# data, but it is required to ensure that OpenTelemetry context
|
|
96
116
|
# propagation is enabled.
|
|
@@ -102,7 +122,6 @@ class TracerWrapper(object):
|
|
|
102
122
|
tracer_provider=obj._tracer_provider,
|
|
103
123
|
instruments=instruments,
|
|
104
124
|
block_instruments=block_instruments,
|
|
105
|
-
client=obj._client,
|
|
106
125
|
async_client=obj._async_client,
|
|
107
126
|
)
|
|
108
127
|
|
|
@@ -113,6 +132,43 @@ class TracerWrapper(object):
|
|
|
113
132
|
|
|
114
133
|
return cls.instance
|
|
115
134
|
|
|
135
|
+
def _setup_threading_inheritance(self):
|
|
136
|
+
"""Setup threading inheritance for isolated context."""
|
|
137
|
+
if TracerWrapper._original_thread_init is None:
|
|
138
|
+
# Monkey patch Thread.__init__ to capture context inheritance
|
|
139
|
+
TracerWrapper._original_thread_init = threading.Thread.__init__
|
|
140
|
+
|
|
141
|
+
def patched_thread_init(thread_self, *args, **kwargs):
|
|
142
|
+
# Capture current isolated context and token stack for inheritance
|
|
143
|
+
current_context = get_current_context()
|
|
144
|
+
current_token_stack = get_token_stack().copy()
|
|
145
|
+
|
|
146
|
+
# Get the original target function
|
|
147
|
+
original_target = kwargs.get("target")
|
|
148
|
+
if not original_target and args:
|
|
149
|
+
original_target = args[0]
|
|
150
|
+
|
|
151
|
+
# Only inherit if we have a target function
|
|
152
|
+
if original_target:
|
|
153
|
+
# Create a wrapper function that sets up context
|
|
154
|
+
def thread_wrapper(*target_args, **target_kwargs):
|
|
155
|
+
# Set inherited context and token stack in the new thread
|
|
156
|
+
attach_context(current_context)
|
|
157
|
+
set_token_stack(current_token_stack)
|
|
158
|
+
# Run original target
|
|
159
|
+
return original_target(*target_args, **target_kwargs)
|
|
160
|
+
|
|
161
|
+
# Replace the target with our wrapper
|
|
162
|
+
if "target" in kwargs:
|
|
163
|
+
kwargs["target"] = thread_wrapper
|
|
164
|
+
elif args:
|
|
165
|
+
args = (thread_wrapper,) + args[1:]
|
|
166
|
+
|
|
167
|
+
# Call original init
|
|
168
|
+
TracerWrapper._original_thread_init(thread_self, *args, **kwargs)
|
|
169
|
+
|
|
170
|
+
threading.Thread.__init__ = patched_thread_init
|
|
171
|
+
|
|
116
172
|
def exit_handler(self):
|
|
117
173
|
if isinstance(self._span_processor, LaminarSpanProcessor):
|
|
118
174
|
self._span_processor.clear()
|
|
@@ -124,6 +180,21 @@ class TracerWrapper(object):
|
|
|
124
180
|
console_log_handler.setFormatter(VerboseColorfulFormatter())
|
|
125
181
|
self._logger.addHandler(console_log_handler)
|
|
126
182
|
|
|
183
|
+
def get_isolated_context(self) -> Context:
|
|
184
|
+
"""Get the current isolated context."""
|
|
185
|
+
return get_current_context()
|
|
186
|
+
|
|
187
|
+
def push_span_context(self, span: trace.Span) -> Context:
|
|
188
|
+
"""Push a new context with the given span onto the stack."""
|
|
189
|
+
current_ctx = get_current_context()
|
|
190
|
+
new_context = trace.set_span_in_context(span, current_ctx)
|
|
191
|
+
ctx_push_span_context(new_context)
|
|
192
|
+
return new_context
|
|
193
|
+
|
|
194
|
+
def pop_span_context(self) -> None:
|
|
195
|
+
"""Pop the current span context from the stack."""
|
|
196
|
+
ctx_pop_span_context()
|
|
197
|
+
|
|
127
198
|
@staticmethod
|
|
128
199
|
def set_static_params(
|
|
129
200
|
resource_attributes: dict,
|
|
@@ -134,8 +205,19 @@ class TracerWrapper(object):
|
|
|
134
205
|
|
|
135
206
|
@classmethod
|
|
136
207
|
def verify_initialized(cls) -> bool:
|
|
137
|
-
|
|
138
|
-
|
|
208
|
+
# This is not using lock, but it is fine to return False from here
|
|
209
|
+
# even if initialization is going on.
|
|
210
|
+
|
|
211
|
+
# If we try to acquire the lock here, it may deadlock if an automatic
|
|
212
|
+
# instrumentation is importing a file that (at the top level) has a
|
|
213
|
+
# function annotated with Laminar's `observe` decorator.
|
|
214
|
+
# The decorator is evaluated at import time, inside `init_instrumentations`,
|
|
215
|
+
# which is called by the `TracerWrapper` constructor while holding the lock.
|
|
216
|
+
# Without the lock here, we will simply return False, which will cause
|
|
217
|
+
# the decorator to return the original function. This is fine, at runtime,
|
|
218
|
+
# the next import statement will re-evaluate the decorator, and Laminar will
|
|
219
|
+
# have been initialized by that time.
|
|
220
|
+
return hasattr(cls, "instance") and hasattr(cls.instance, "_span_processor")
|
|
139
221
|
|
|
140
222
|
@classmethod
|
|
141
223
|
def clear(cls):
|
|
@@ -144,6 +226,8 @@ class TracerWrapper(object):
|
|
|
144
226
|
# Any state cleanup. Now used in between tests
|
|
145
227
|
if isinstance(cls.instance._span_processor, LaminarSpanProcessor):
|
|
146
228
|
cls.instance._span_processor.clear()
|
|
229
|
+
# Clear the isolated context state for clean test state
|
|
230
|
+
clear_context()
|
|
147
231
|
|
|
148
232
|
def shutdown(self):
|
|
149
233
|
if self._tracer_provider is None:
|
|
@@ -156,7 +240,24 @@ class TracerWrapper(object):
|
|
|
156
240
|
return False
|
|
157
241
|
return self._span_processor.force_flush()
|
|
158
242
|
|
|
159
|
-
def
|
|
243
|
+
def force_reinit_processor(self):
|
|
244
|
+
if isinstance(self._span_processor, LaminarSpanProcessor):
|
|
245
|
+
self._span_processor.force_flush()
|
|
246
|
+
self._span_processor.force_reinit()
|
|
247
|
+
# Clear the isolated context to prevent subsequent invocations
|
|
248
|
+
# (e.g., in Lambda) from continuing traces from previous invocations
|
|
249
|
+
clear_context()
|
|
250
|
+
else:
|
|
251
|
+
self._logger.warning(
|
|
252
|
+
"Not using LaminarSpanProcessor, cannot force reinit processor"
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
@classmethod
|
|
256
|
+
def get_session_recording_options(cls) -> SessionRecordingOptions:
|
|
257
|
+
"""Get the session recording options set during initialization."""
|
|
258
|
+
return cls.session_recording_options
|
|
259
|
+
|
|
260
|
+
def get_tracer(self) -> trace.Tracer:
|
|
160
261
|
if self._tracer_provider is None:
|
|
161
262
|
return trace.get_tracer_provider().get_tracer(TRACER_NAME)
|
|
162
263
|
return self._tracer_provider.get_tracer(TRACER_NAME)
|
|
@@ -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
|
|
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,
|
|
@@ -50,13 +51,62 @@ class BedrockInstrumentorInitializer(InstrumentorInitializer):
|
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
class BrowserUseInstrumentorInitializer(InstrumentorInitializer):
|
|
54
|
+
"""Instruments for different versions of browser-use:
|
|
55
|
+
|
|
56
|
+
- browser-use < 0.5: BrowserUseLegacyInstrumentor to track agent_step and
|
|
57
|
+
other structure spans. Session instrumentation is controlled by
|
|
58
|
+
Instruments.PLAYWRIGHT (or Instruments.PATCHRIGHT for several versions
|
|
59
|
+
in 0.4.* that used patchright)
|
|
60
|
+
- browser-use ~= 0.5: Structure spans live in browser_use package itself.
|
|
61
|
+
Session instrumentation is controlled by Instruments.PLAYWRIGHT
|
|
62
|
+
- browser-use >= 0.6.0rc1: BubusInstrumentor to keep spans structure.
|
|
63
|
+
Session instrumentation is controlled by Instruments.BROWSER_USE_SESSION
|
|
64
|
+
"""
|
|
65
|
+
|
|
53
66
|
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
54
67
|
if not is_package_installed("browser-use"):
|
|
55
68
|
return None
|
|
56
69
|
|
|
57
|
-
|
|
70
|
+
version = get_package_version("browser-use")
|
|
71
|
+
from packaging.version import parse
|
|
72
|
+
|
|
73
|
+
if version and parse(version) < parse("0.5.0"):
|
|
74
|
+
from lmnr.sdk.browser.browser_use_otel import BrowserUseLegacyInstrumentor
|
|
75
|
+
|
|
76
|
+
return BrowserUseLegacyInstrumentor()
|
|
77
|
+
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class BrowserUseSessionInstrumentorInitializer(InstrumentorInitializer):
|
|
82
|
+
def init_instrumentor(
|
|
83
|
+
self, async_client, *args, **kwargs
|
|
84
|
+
) -> BaseInstrumentor | None:
|
|
85
|
+
if not is_package_installed("browser-use"):
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
version = get_package_version("browser-use")
|
|
89
|
+
from packaging.version import parse
|
|
90
|
+
|
|
91
|
+
if version and parse(version) >= parse("0.6.0rc1"):
|
|
92
|
+
from lmnr.sdk.browser.browser_use_cdp_otel import BrowserUseInstrumentor
|
|
93
|
+
|
|
94
|
+
if async_client is None:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
return BrowserUseInstrumentor(async_client)
|
|
58
98
|
|
|
59
|
-
return
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class BubusInstrumentorInitializer(InstrumentorInitializer):
|
|
103
|
+
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
104
|
+
if not is_package_installed("bubus"):
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
from lmnr.sdk.browser.bubus_otel import BubusInstrumentor
|
|
108
|
+
|
|
109
|
+
return BubusInstrumentor()
|
|
60
110
|
|
|
61
111
|
|
|
62
112
|
class ChromaInstrumentorInitializer(InstrumentorInitializer):
|
|
@@ -71,6 +121,19 @@ class ChromaInstrumentorInitializer(InstrumentorInitializer):
|
|
|
71
121
|
return ChromaInstrumentor()
|
|
72
122
|
|
|
73
123
|
|
|
124
|
+
class ClaudeAgentInstrumentorInitializer(InstrumentorInitializer):
|
|
125
|
+
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
126
|
+
if not is_package_installed("claude-agent-sdk"):
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
if not is_package_installed("lmnr-claude-code-proxy"):
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
from ..opentelemetry.instrumentation.claude_agent import ClaudeAgentInstrumentor
|
|
133
|
+
|
|
134
|
+
return ClaudeAgentInstrumentor()
|
|
135
|
+
|
|
136
|
+
|
|
74
137
|
class CohereInstrumentorInitializer(InstrumentorInitializer):
|
|
75
138
|
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
76
139
|
if not is_package_installed("cohere"):
|
|
@@ -95,20 +158,24 @@ class CrewAIInstrumentorInitializer(InstrumentorInitializer):
|
|
|
95
158
|
return CrewAiInstrumentor()
|
|
96
159
|
|
|
97
160
|
|
|
98
|
-
class
|
|
161
|
+
class CuaAgentInstrumentorInitializer(InstrumentorInitializer):
|
|
99
162
|
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
100
|
-
if not is_package_installed("
|
|
163
|
+
if not is_package_installed("cua-agent"):
|
|
101
164
|
return None
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
165
|
+
|
|
166
|
+
from ..opentelemetry.instrumentation.cua_agent import CuaAgentInstrumentor
|
|
167
|
+
|
|
168
|
+
return CuaAgentInstrumentor()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class CuaComputerInstrumentorInitializer(InstrumentorInitializer):
|
|
172
|
+
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
173
|
+
if not is_package_installed("cua-computer"):
|
|
105
174
|
return None
|
|
106
175
|
|
|
107
|
-
from opentelemetry.instrumentation.
|
|
108
|
-
GoogleGenerativeAiInstrumentor,
|
|
109
|
-
)
|
|
176
|
+
from ..opentelemetry.instrumentation.cua_computer import CuaComputerInstrumentor
|
|
110
177
|
|
|
111
|
-
return
|
|
178
|
+
return CuaComputerInstrumentor()
|
|
112
179
|
|
|
113
180
|
|
|
114
181
|
class GoogleGenAIInstrumentorInitializer(InstrumentorInitializer):
|
|
@@ -127,10 +194,8 @@ class GroqInstrumentorInitializer(InstrumentorInitializer):
|
|
|
127
194
|
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
128
195
|
if not is_package_installed("groq"):
|
|
129
196
|
return None
|
|
130
|
-
if not is_package_installed("opentelemetry-instrumentation-groq"):
|
|
131
|
-
return None
|
|
132
197
|
|
|
133
|
-
from opentelemetry.instrumentation.groq import GroqInstrumentor
|
|
198
|
+
from ..opentelemetry.instrumentation.groq import GroqInstrumentor
|
|
134
199
|
|
|
135
200
|
return GroqInstrumentor()
|
|
136
201
|
|
|
@@ -147,6 +212,16 @@ class HaystackInstrumentorInitializer(InstrumentorInitializer):
|
|
|
147
212
|
return HaystackInstrumentor()
|
|
148
213
|
|
|
149
214
|
|
|
215
|
+
class KernelInstrumentorInitializer(InstrumentorInitializer):
|
|
216
|
+
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
217
|
+
if not is_package_installed("kernel"):
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
from ..opentelemetry.instrumentation.kernel import KernelInstrumentor
|
|
221
|
+
|
|
222
|
+
return KernelInstrumentor()
|
|
223
|
+
|
|
224
|
+
|
|
150
225
|
class LanceDBInstrumentorInitializer(InstrumentorInitializer):
|
|
151
226
|
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
152
227
|
if not is_package_installed("lancedb"):
|
|
@@ -261,10 +336,8 @@ class OpenAIInstrumentorInitializer(InstrumentorInitializer):
|
|
|
261
336
|
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
262
337
|
if not is_package_installed("openai"):
|
|
263
338
|
return None
|
|
264
|
-
if not is_package_installed("opentelemetry-instrumentation-openai"):
|
|
265
|
-
return None
|
|
266
339
|
|
|
267
|
-
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
|
|
340
|
+
from ..opentelemetry.instrumentation.openai import OpenAIInstrumentor
|
|
268
341
|
|
|
269
342
|
return OpenAIInstrumentor(
|
|
270
343
|
# Default in the package provided is an empty function, which
|
|
@@ -274,16 +347,37 @@ class OpenAIInstrumentorInitializer(InstrumentorInitializer):
|
|
|
274
347
|
)
|
|
275
348
|
|
|
276
349
|
|
|
350
|
+
class OpenHandsAIInstrumentorInitializer(InstrumentorInitializer):
|
|
351
|
+
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
352
|
+
if not is_package_installed("openhands-ai"):
|
|
353
|
+
return None
|
|
354
|
+
from ..opentelemetry.instrumentation.openhands_ai import OpenHandsInstrumentor
|
|
355
|
+
|
|
356
|
+
return OpenHandsInstrumentor()
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class OpenTelemetryInstrumentorInitializer(InstrumentorInitializer):
|
|
360
|
+
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
361
|
+
from ..opentelemetry.instrumentation.opentelemetry import (
|
|
362
|
+
OpentelemetryInstrumentor,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
return OpentelemetryInstrumentor()
|
|
366
|
+
|
|
367
|
+
|
|
277
368
|
class PatchrightInstrumentorInitializer(InstrumentorInitializer):
|
|
278
369
|
def init_instrumentor(
|
|
279
|
-
self,
|
|
370
|
+
self, async_client, *args, **kwargs
|
|
280
371
|
) -> BaseInstrumentor | None:
|
|
281
372
|
if not is_package_installed("patchright"):
|
|
282
373
|
return None
|
|
283
374
|
|
|
284
375
|
from lmnr.sdk.browser.patchright_otel import PatchrightInstrumentor
|
|
285
376
|
|
|
286
|
-
|
|
377
|
+
if async_client is None:
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
return PatchrightInstrumentor(async_client)
|
|
287
381
|
|
|
288
382
|
|
|
289
383
|
class PineconeInstrumentorInitializer(InstrumentorInitializer):
|
|
@@ -300,14 +394,17 @@ class PineconeInstrumentorInitializer(InstrumentorInitializer):
|
|
|
300
394
|
|
|
301
395
|
class PlaywrightInstrumentorInitializer(InstrumentorInitializer):
|
|
302
396
|
def init_instrumentor(
|
|
303
|
-
self,
|
|
397
|
+
self, async_client, *args, **kwargs
|
|
304
398
|
) -> BaseInstrumentor | None:
|
|
305
399
|
if not is_package_installed("playwright"):
|
|
306
400
|
return None
|
|
307
401
|
|
|
308
402
|
from lmnr.sdk.browser.playwright_otel import PlaywrightInstrumentor
|
|
309
403
|
|
|
310
|
-
|
|
404
|
+
if async_client is None:
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
return PlaywrightInstrumentor(async_client)
|
|
311
408
|
|
|
312
409
|
|
|
313
410
|
class QdrantInstrumentorInitializer(InstrumentorInitializer):
|
|
@@ -345,6 +442,7 @@ class SageMakerInstrumentorInitializer(InstrumentorInitializer):
|
|
|
345
442
|
|
|
346
443
|
return SageMakerInstrumentor()
|
|
347
444
|
|
|
445
|
+
|
|
348
446
|
class SkyvernInstrumentorInitializer(InstrumentorInitializer):
|
|
349
447
|
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
350
448
|
if not is_package_installed("skyvern"):
|
|
@@ -354,6 +452,7 @@ class SkyvernInstrumentorInitializer(InstrumentorInitializer):
|
|
|
354
452
|
|
|
355
453
|
return SkyvernInstrumentor()
|
|
356
454
|
|
|
455
|
+
|
|
357
456
|
class TogetherInstrumentorInitializer(InstrumentorInitializer):
|
|
358
457
|
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
|
|
359
458
|
if not is_package_installed("together"):
|