lmnr 0.5.2__py3-none-any.whl → 0.6.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.
- lmnr/__init__.py +7 -2
- lmnr/cli.py +10 -8
- lmnr/opentelemetry_lib/__init__.py +55 -0
- lmnr/{openllmetry_sdk/decorators/base.py → opentelemetry_lib/decorators/__init__.py} +24 -15
- lmnr/{openllmetry_sdk → opentelemetry_lib}/opentelemetry/instrumentation/google_genai/utils.py +1 -1
- lmnr/opentelemetry_lib/tracing/__init__.py +139 -0
- lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +398 -0
- lmnr/{openllmetry_sdk → opentelemetry_lib}/tracing/attributes.py +14 -7
- lmnr/opentelemetry_lib/tracing/context_properties.py +53 -0
- lmnr/opentelemetry_lib/tracing/exporter.py +60 -0
- lmnr/opentelemetry_lib/tracing/instruments.py +121 -0
- lmnr/opentelemetry_lib/tracing/processor.py +96 -0
- lmnr/{openllmetry_sdk/tracing/context_manager.py → opentelemetry_lib/tracing/tracer.py} +6 -1
- lmnr/{openllmetry_sdk → opentelemetry_lib}/utils/package_check.py +3 -1
- lmnr/sdk/browser/browser_use_otel.py +20 -3
- lmnr/sdk/browser/patchright_otel.py +177 -0
- lmnr/sdk/browser/playwright_otel.py +16 -7
- lmnr/sdk/browser/pw_utils.py +116 -74
- lmnr/sdk/browser/rrweb/rrweb.umd.min.cjs +98 -0
- lmnr/sdk/client/asynchronous/resources/agent.py +22 -1
- lmnr/sdk/client/synchronous/resources/agent.py +23 -1
- lmnr/sdk/decorators.py +5 -3
- lmnr/sdk/eval_control.py +3 -2
- lmnr/sdk/evaluations.py +10 -16
- lmnr/sdk/laminar.py +16 -34
- lmnr/sdk/types.py +2 -0
- lmnr/sdk/utils.py +2 -3
- lmnr/version.py +1 -1
- {lmnr-0.5.2.dist-info → lmnr-0.6.0.dist-info}/METADATA +65 -63
- lmnr-0.6.0.dist-info/RECORD +54 -0
- {lmnr-0.5.2.dist-info → lmnr-0.6.0.dist-info}/WHEEL +1 -1
- lmnr/openllmetry_sdk/__init__.py +0 -75
- lmnr/openllmetry_sdk/config/__init__.py +0 -12
- lmnr/openllmetry_sdk/decorators/__init__.py +0 -0
- lmnr/openllmetry_sdk/instruments.py +0 -41
- lmnr/openllmetry_sdk/tracing/__init__.py +0 -1
- lmnr/openllmetry_sdk/tracing/content_allow_list.py +0 -24
- lmnr/openllmetry_sdk/tracing/tracing.py +0 -998
- lmnr/openllmetry_sdk/utils/in_memory_span_exporter.py +0 -61
- lmnr/sdk/browser/rrweb/rrweb.min.js +0 -18
- lmnr-0.5.2.dist-info/RECORD +0 -54
- /lmnr/{openllmetry_sdk → opentelemetry_lib}/.flake8 +0 -0
- /lmnr/{openllmetry_sdk → opentelemetry_lib}/opentelemetry/instrumentation/google_genai/__init__.py +0 -0
- /lmnr/{openllmetry_sdk → opentelemetry_lib}/opentelemetry/instrumentation/google_genai/config.py +0 -0
- /lmnr/{openllmetry_sdk → opentelemetry_lib}/utils/__init__.py +0 -0
- /lmnr/{openllmetry_sdk → opentelemetry_lib}/utils/json_encoder.py +0 -0
- {lmnr-0.5.2.dist-info → lmnr-0.6.0.dist-info}/LICENSE +0 -0
- {lmnr-0.5.2.dist-info → lmnr-0.6.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
import uuid
|
2
|
+
|
3
|
+
from typing import Optional, Union
|
4
|
+
from opentelemetry.sdk.trace.export import (
|
5
|
+
SpanProcessor,
|
6
|
+
SpanExporter,
|
7
|
+
BatchSpanProcessor,
|
8
|
+
SimpleSpanProcessor,
|
9
|
+
)
|
10
|
+
from opentelemetry.trace import Span
|
11
|
+
from opentelemetry.context import Context, get_value, get_current, set_value
|
12
|
+
|
13
|
+
from lmnr.opentelemetry_lib.tracing.attributes import (
|
14
|
+
SPAN_IDS_PATH,
|
15
|
+
SPAN_INSTRUMENTATION_SOURCE,
|
16
|
+
SPAN_LANGUAGE_VERSION,
|
17
|
+
SPAN_PATH,
|
18
|
+
SPAN_SDK_VERSION,
|
19
|
+
)
|
20
|
+
from lmnr.opentelemetry_lib.tracing.exporter import LaminarSpanExporter
|
21
|
+
from lmnr.opentelemetry_lib.tracing.context_properties import (
|
22
|
+
_set_association_properties_attributes,
|
23
|
+
)
|
24
|
+
from lmnr.version import PYTHON_VERSION, __version__
|
25
|
+
|
26
|
+
|
27
|
+
class LaminarSpanProcessor(SpanProcessor):
|
28
|
+
instance: Union[BatchSpanProcessor, SimpleSpanProcessor]
|
29
|
+
__span_id_to_path: dict[int, list[str]] = {}
|
30
|
+
__span_id_lists: dict[int, list[str]] = {}
|
31
|
+
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
base_url: Optional[str] = None,
|
35
|
+
port: Optional[int] = None,
|
36
|
+
api_key: Optional[str] = None,
|
37
|
+
timeout_seconds: int = 30,
|
38
|
+
force_http: bool = False,
|
39
|
+
max_export_batch_size: int = 512,
|
40
|
+
disable_batch: bool = False,
|
41
|
+
exporter: Optional[SpanExporter] = None,
|
42
|
+
):
|
43
|
+
self.exporter = exporter or LaminarSpanExporter(
|
44
|
+
base_url=base_url,
|
45
|
+
port=port,
|
46
|
+
api_key=api_key,
|
47
|
+
timeout_seconds=timeout_seconds,
|
48
|
+
force_http=force_http,
|
49
|
+
)
|
50
|
+
self.instance = (
|
51
|
+
SimpleSpanProcessor(self.exporter)
|
52
|
+
if disable_batch
|
53
|
+
else BatchSpanProcessor(
|
54
|
+
self.exporter, max_export_batch_size=max_export_batch_size
|
55
|
+
)
|
56
|
+
)
|
57
|
+
|
58
|
+
def on_start(self, span: Span, parent_context: Optional[Context] = None):
|
59
|
+
span_path_in_context = get_value("span_path", parent_context or get_current())
|
60
|
+
parent_span_path = span_path_in_context or (
|
61
|
+
self.__span_id_to_path.get(span.parent.span_id) if span.parent else None
|
62
|
+
)
|
63
|
+
parent_span_ids_path = (
|
64
|
+
self.__span_id_lists.get(span.parent.span_id, []) if span.parent else []
|
65
|
+
)
|
66
|
+
span_path = parent_span_path + [span.name] if parent_span_path else [span.name]
|
67
|
+
span_ids_path = parent_span_ids_path + [
|
68
|
+
str(uuid.UUID(int=span.get_span_context().span_id))
|
69
|
+
]
|
70
|
+
span.set_attribute(SPAN_PATH, span_path)
|
71
|
+
span.set_attribute(SPAN_IDS_PATH, span_ids_path)
|
72
|
+
set_value("span_path", span_path, get_current())
|
73
|
+
self.__span_id_to_path[span.get_span_context().span_id] = span_path
|
74
|
+
self.__span_id_lists[span.get_span_context().span_id] = span_ids_path
|
75
|
+
|
76
|
+
span.set_attribute(SPAN_INSTRUMENTATION_SOURCE, "python")
|
77
|
+
span.set_attribute(SPAN_SDK_VERSION, __version__)
|
78
|
+
span.set_attribute(SPAN_LANGUAGE_VERSION, f"python@{PYTHON_VERSION}")
|
79
|
+
|
80
|
+
association_properties = get_value("association_properties")
|
81
|
+
if association_properties is not None:
|
82
|
+
_set_association_properties_attributes(span, association_properties)
|
83
|
+
self.instance.on_start(span, parent_context)
|
84
|
+
|
85
|
+
def on_end(self, span: Span):
|
86
|
+
self.instance.on_end(span)
|
87
|
+
|
88
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
89
|
+
return self.instance.force_flush(timeout_millis)
|
90
|
+
|
91
|
+
def shutdown(self):
|
92
|
+
self.instance.shutdown()
|
93
|
+
|
94
|
+
def clear(self):
|
95
|
+
self.__span_id_to_path = {}
|
96
|
+
self.__span_id_lists = {}
|
@@ -1,6 +1,11 @@
|
|
1
1
|
from contextlib import contextmanager
|
2
2
|
|
3
|
-
from
|
3
|
+
from opentelemetry import trace
|
4
|
+
from lmnr.opentelemetry_lib.tracing import TracerWrapper
|
5
|
+
|
6
|
+
|
7
|
+
def get_laminar_tracer_provider() -> trace.TracerProvider:
|
8
|
+
return TracerWrapper.instance.__tracer_provider or trace.get_tracer_provider()
|
4
9
|
|
5
10
|
|
6
11
|
@contextmanager
|
@@ -1,6 +1,8 @@
|
|
1
1
|
from importlib.metadata import distributions
|
2
2
|
|
3
|
-
installed_packages = {
|
3
|
+
installed_packages = {
|
4
|
+
(dist.name or dist.metadata.get("Name", "")).lower() for dist in distributions()
|
5
|
+
}
|
4
6
|
|
5
7
|
|
6
8
|
def is_package_installed(package_name: str) -> bool:
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from lmnr.
|
1
|
+
from lmnr.opentelemetry_lib.decorators import json_dumps
|
2
2
|
from lmnr.sdk.browser.utils import with_tracer_wrapper
|
3
3
|
from lmnr.sdk.utils import get_input_from_func_args
|
4
4
|
from lmnr.version import __version__
|
@@ -8,6 +8,16 @@ from opentelemetry.instrumentation.utils import unwrap
|
|
8
8
|
from opentelemetry.trace import get_tracer, Tracer
|
9
9
|
from typing import Collection
|
10
10
|
from wrapt import wrap_function_wrapper
|
11
|
+
import pydantic
|
12
|
+
|
13
|
+
try:
|
14
|
+
from browser_use import AgentHistoryList
|
15
|
+
except ImportError as e:
|
16
|
+
raise ImportError(
|
17
|
+
f"Attempted to import {__file__}, but it is designed "
|
18
|
+
"to patch Browser Use, which is not installed. Use `pip install browser-use` "
|
19
|
+
"to install Browser Use or remove this import."
|
20
|
+
) from e
|
11
21
|
|
12
22
|
_instruments = ("browser-use >= 0.1.0",)
|
13
23
|
|
@@ -18,7 +28,7 @@ WRAPPED_METHODS = [
|
|
18
28
|
"method": "run",
|
19
29
|
"span_name": "agent.run",
|
20
30
|
"ignore_input": False,
|
21
|
-
"ignore_output":
|
31
|
+
"ignore_output": False,
|
22
32
|
"span_type": "DEFAULT",
|
23
33
|
},
|
24
34
|
{
|
@@ -73,7 +83,14 @@ async def _wrap(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
|
|
73
83
|
span.set_attributes(attributes)
|
74
84
|
result = await wrapped(*args, **kwargs)
|
75
85
|
if not to_wrap.get("ignore_output"):
|
76
|
-
|
86
|
+
if isinstance(result, AgentHistoryList):
|
87
|
+
result = result.final_result()
|
88
|
+
serialized = (
|
89
|
+
result.model_dump_json()
|
90
|
+
if isinstance(result, pydantic.BaseModel)
|
91
|
+
else json_dumps(result)
|
92
|
+
)
|
93
|
+
span.set_attribute("lmnr.span.output", serialized)
|
77
94
|
return result
|
78
95
|
|
79
96
|
|
@@ -0,0 +1,177 @@
|
|
1
|
+
from lmnr.sdk.browser.playwright_otel import (
|
2
|
+
_wrap_new_page,
|
3
|
+
_wrap_new_page_async,
|
4
|
+
_wrap_new_browser_sync,
|
5
|
+
_wrap_new_browser_async,
|
6
|
+
_wrap_new_context_sync,
|
7
|
+
_wrap_new_context_async,
|
8
|
+
_wrap_close_browser_sync,
|
9
|
+
_wrap_close_browser_async,
|
10
|
+
)
|
11
|
+
from lmnr.sdk.client.synchronous.sync_client import LaminarClient
|
12
|
+
from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
|
13
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
14
|
+
from opentelemetry.instrumentation.utils import unwrap
|
15
|
+
from opentelemetry.trace import get_tracer
|
16
|
+
from lmnr.version import __version__
|
17
|
+
from typing import Collection
|
18
|
+
from wrapt import wrap_function_wrapper
|
19
|
+
|
20
|
+
_instruments = ("patchright >= 1.9.0",)
|
21
|
+
|
22
|
+
WRAPPED_METHODS = [
|
23
|
+
{
|
24
|
+
"package": "patchright.sync_api",
|
25
|
+
"object": "BrowserContext",
|
26
|
+
"method": "new_page",
|
27
|
+
"wrapper": _wrap_new_page,
|
28
|
+
},
|
29
|
+
{
|
30
|
+
"package": "patchright.sync_api",
|
31
|
+
"object": "Browser",
|
32
|
+
"method": "new_page",
|
33
|
+
"wrapper": _wrap_new_page,
|
34
|
+
},
|
35
|
+
{
|
36
|
+
"package": "patchright.sync_api",
|
37
|
+
"object": "BrowserType",
|
38
|
+
"method": "launch",
|
39
|
+
"wrapper": _wrap_new_browser_sync,
|
40
|
+
},
|
41
|
+
{
|
42
|
+
"package": "patchright.sync_api",
|
43
|
+
"object": "BrowserType",
|
44
|
+
"method": "connect",
|
45
|
+
"wrapper": _wrap_new_browser_sync,
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"package": "patchright.sync_api",
|
49
|
+
"object": "BrowserType",
|
50
|
+
"method": "connect_over_cdp",
|
51
|
+
"wrapper": _wrap_new_browser_sync,
|
52
|
+
},
|
53
|
+
{
|
54
|
+
"package": "patchright.sync_api",
|
55
|
+
"object": "Browser",
|
56
|
+
"method": "close",
|
57
|
+
"wrapper": _wrap_close_browser_sync,
|
58
|
+
},
|
59
|
+
{
|
60
|
+
"package": "patchright.sync_api",
|
61
|
+
"object": "Browser",
|
62
|
+
"method": "new_context",
|
63
|
+
"wrapper": _wrap_new_context_sync,
|
64
|
+
},
|
65
|
+
{
|
66
|
+
"package": "patchright.sync_api",
|
67
|
+
"object": "BrowserType",
|
68
|
+
"method": "launch_persistent_context",
|
69
|
+
"wrapper": _wrap_new_context_sync,
|
70
|
+
},
|
71
|
+
]
|
72
|
+
|
73
|
+
WRAPPED_METHODS_ASYNC = [
|
74
|
+
{
|
75
|
+
"package": "patchright.async_api",
|
76
|
+
"object": "BrowserContext",
|
77
|
+
"method": "new_page",
|
78
|
+
"wrapper": _wrap_new_page_async,
|
79
|
+
},
|
80
|
+
{
|
81
|
+
"package": "patchright.async_api",
|
82
|
+
"object": "Browser",
|
83
|
+
"method": "new_page",
|
84
|
+
"wrapper": _wrap_new_page_async,
|
85
|
+
},
|
86
|
+
{
|
87
|
+
"package": "patchright.async_api",
|
88
|
+
"object": "BrowserType",
|
89
|
+
"method": "launch",
|
90
|
+
"wrapper": _wrap_new_browser_async,
|
91
|
+
},
|
92
|
+
{
|
93
|
+
"package": "patchright.async_api",
|
94
|
+
"object": "BrowserType",
|
95
|
+
"method": "connect",
|
96
|
+
"wrapper": _wrap_new_browser_async,
|
97
|
+
},
|
98
|
+
{
|
99
|
+
"package": "patchright.async_api",
|
100
|
+
"object": "BrowserType",
|
101
|
+
"method": "connect_over_cdp",
|
102
|
+
"wrapper": _wrap_new_browser_async,
|
103
|
+
},
|
104
|
+
{
|
105
|
+
"package": "patchright.async_api",
|
106
|
+
"object": "Browser",
|
107
|
+
"method": "close",
|
108
|
+
"wrapper": _wrap_close_browser_async,
|
109
|
+
},
|
110
|
+
{
|
111
|
+
"package": "patchright.async_api",
|
112
|
+
"object": "Browser",
|
113
|
+
"method": "new_context",
|
114
|
+
"wrapper": _wrap_new_context_async,
|
115
|
+
},
|
116
|
+
{
|
117
|
+
"package": "patchright.async_api",
|
118
|
+
"object": "BrowserType",
|
119
|
+
"method": "launch_persistent_context",
|
120
|
+
"wrapper": _wrap_new_context_async,
|
121
|
+
},
|
122
|
+
]
|
123
|
+
|
124
|
+
|
125
|
+
class PatchrightInstrumentor(BaseInstrumentor):
|
126
|
+
def __init__(self, client: LaminarClient, async_client: AsyncLaminarClient):
|
127
|
+
super().__init__()
|
128
|
+
self.client = client
|
129
|
+
self.async_client = async_client
|
130
|
+
|
131
|
+
def instrumentation_dependencies(self) -> Collection[str]:
|
132
|
+
return _instruments
|
133
|
+
|
134
|
+
def _instrument(self, **kwargs):
|
135
|
+
tracer_provider = kwargs.get("tracer_provider")
|
136
|
+
tracer = get_tracer(__name__, __version__, tracer_provider)
|
137
|
+
|
138
|
+
for wrapped_method in WRAPPED_METHODS:
|
139
|
+
wrap_package = wrapped_method.get("package")
|
140
|
+
wrap_object = wrapped_method.get("object")
|
141
|
+
wrap_method = wrapped_method.get("method")
|
142
|
+
try:
|
143
|
+
wrap_function_wrapper(
|
144
|
+
wrap_package,
|
145
|
+
f"{wrap_object}.{wrap_method}",
|
146
|
+
wrapped_method.get("wrapper")(
|
147
|
+
tracer,
|
148
|
+
self.client,
|
149
|
+
wrapped_method,
|
150
|
+
),
|
151
|
+
)
|
152
|
+
except ModuleNotFoundError:
|
153
|
+
pass
|
154
|
+
|
155
|
+
for wrapped_method in WRAPPED_METHODS_ASYNC:
|
156
|
+
wrap_package = wrapped_method.get("package")
|
157
|
+
wrap_object = wrapped_method.get("object")
|
158
|
+
wrap_method = wrapped_method.get("method")
|
159
|
+
try:
|
160
|
+
wrap_function_wrapper(
|
161
|
+
wrap_package,
|
162
|
+
f"{wrap_object}.{wrap_method}",
|
163
|
+
wrapped_method.get("wrapper")(
|
164
|
+
tracer,
|
165
|
+
self.async_client,
|
166
|
+
wrapped_method,
|
167
|
+
),
|
168
|
+
)
|
169
|
+
except ModuleNotFoundError:
|
170
|
+
pass
|
171
|
+
|
172
|
+
def _uninstrument(self, **kwargs):
|
173
|
+
for wrapped_method in WRAPPED_METHODS + WRAPPED_METHODS_ASYNC:
|
174
|
+
wrap_package = wrapped_method.get("package")
|
175
|
+
wrap_object = wrapped_method.get("object")
|
176
|
+
wrap_method = wrapped_method.get("method")
|
177
|
+
unwrap(wrap_package, f"{wrap_object}.{wrap_method}")
|
@@ -22,10 +22,11 @@ from typing import Collection
|
|
22
22
|
from wrapt import wrap_function_wrapper
|
23
23
|
|
24
24
|
try:
|
25
|
-
from playwright.async_api import Browser, BrowserContext
|
25
|
+
from playwright.async_api import Browser, BrowserContext, Page
|
26
26
|
from playwright.sync_api import (
|
27
27
|
Browser as SyncBrowser,
|
28
28
|
BrowserContext as SyncBrowserContext,
|
29
|
+
Page as SyncPage,
|
29
30
|
)
|
30
31
|
except ImportError as e:
|
31
32
|
raise ImportError(
|
@@ -88,10 +89,15 @@ def _wrap_new_browser_sync(
|
|
88
89
|
_context_spans[id(context)] = span
|
89
90
|
span.set_attribute("lmnr.internal.has_browser_session", True)
|
90
91
|
trace_id = format(span.get_span_context().trace_id, "032x")
|
92
|
+
|
93
|
+
def handle_page_navigation(page: SyncPage):
|
94
|
+
return handle_navigation_sync(page, session_id, trace_id, client)
|
95
|
+
|
91
96
|
context.on(
|
92
97
|
"page",
|
93
|
-
|
98
|
+
handle_page_navigation,
|
94
99
|
)
|
100
|
+
|
95
101
|
for page in context.pages:
|
96
102
|
handle_navigation_sync(page, session_id, trace_id, client)
|
97
103
|
return browser
|
@@ -115,7 +121,7 @@ async def _wrap_new_browser_async(
|
|
115
121
|
span.set_attribute("lmnr.internal.has_browser_session", True)
|
116
122
|
trace_id = format(span.get_span_context().trace_id, "032x")
|
117
123
|
|
118
|
-
async def handle_page_navigation(page):
|
124
|
+
async def handle_page_navigation(page: Page):
|
119
125
|
return await handle_navigation_async(page, session_id, trace_id, client)
|
120
126
|
|
121
127
|
context.on("page", handle_page_navigation)
|
@@ -140,9 +146,12 @@ def _wrap_new_context_sync(
|
|
140
146
|
span.set_attribute("lmnr.internal.has_browser_session", True)
|
141
147
|
trace_id = format(span.get_span_context().trace_id, "032x")
|
142
148
|
|
149
|
+
def handle_page_navigation(page: SyncPage):
|
150
|
+
return handle_navigation_sync(page, session_id, trace_id, client)
|
151
|
+
|
143
152
|
context.on(
|
144
153
|
"page",
|
145
|
-
|
154
|
+
handle_page_navigation,
|
146
155
|
)
|
147
156
|
for page in context.pages:
|
148
157
|
handle_navigation_sync(page, session_id, trace_id, client)
|
@@ -153,7 +162,7 @@ def _wrap_new_context_sync(
|
|
153
162
|
async def _wrap_new_context_async(
|
154
163
|
tracer: Tracer, client: AsyncLaminarClient, to_wrap, wrapped, instance, args, kwargs
|
155
164
|
):
|
156
|
-
context:
|
165
|
+
context: BrowserContext = await wrapped(*args, **kwargs)
|
157
166
|
session_id = str(uuid.uuid4().hex)
|
158
167
|
span = get_current_span()
|
159
168
|
if span == INVALID_SPAN:
|
@@ -311,10 +320,10 @@ WRAPPED_METHODS_ASYNC = [
|
|
311
320
|
"wrapper": _wrap_new_context_async,
|
312
321
|
},
|
313
322
|
{
|
314
|
-
"package": "playwright.
|
323
|
+
"package": "playwright.async_api",
|
315
324
|
"object": "BrowserType",
|
316
325
|
"method": "launch_persistent_context",
|
317
|
-
"wrapper":
|
326
|
+
"wrapper": _wrap_new_context_async,
|
318
327
|
},
|
319
328
|
]
|
320
329
|
|