Skip to content

Commit f8a2d0b

Browse files
Cdp use fixes (#184)
* wip (attempt 0): don't enable rrweb if zone detected * wip (attempt 1): isolated world * wip: isolated context continued * fix full snapshots, by not storing URL in key * bubus instrumentation to keep spans hierarchy correct * add about:blank, expose disabled_instruments in evaluate * further safety and bump version * also commit bumped pyproject.toml * further context id checks, Laminar.use_span * Update src/lmnr/sdk/evaluations.py Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * return True from should skip in case of errors * Update src/lmnr/sdk/browser/cdp_utils.py Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
1 parent e7bce80 commit f8a2d0b

File tree

9 files changed

+468
-104
lines changed

9 files changed

+468
-104
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
[project]
88
name = "lmnr"
9-
version = "0.7.10"
9+
version = "0.7.11"
1010
description = "Python SDK for Laminar"
1111
authors = [
1212
{ name = "lmnr.ai", email = "[email protected]" }

src/lmnr/opentelemetry_lib/tracing/_instrument_initializers.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,19 @@ def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
5151

5252

5353
class BrowserUseInstrumentorInitializer(InstrumentorInitializer):
54-
def init_instrumentor(
55-
self, client, async_client, *args, **kwargs
56-
) -> BaseInstrumentor | None:
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+
66+
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
5767
if not is_package_installed("browser-use"):
5868
return None
5969

@@ -65,6 +75,19 @@ def init_instrumentor(
6575

6676
return BrowserUseLegacyInstrumentor()
6777

78+
return None
79+
80+
81+
class BrowserUseSessionInstrumentorInitializer(InstrumentorInitializer):
82+
def init_instrumentor(
83+
self, client, 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+
6891
if version and parse(version) >= parse("0.6.0rc1"):
6992
from lmnr.sdk.browser.browser_use_cdp_otel import BrowserUseInstrumentor
7093

@@ -73,6 +96,16 @@ def init_instrumentor(
7396
return None
7497

7598

99+
class BubusInstrumentorInitializer(InstrumentorInitializer):
100+
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
101+
if not is_package_installed("bubus"):
102+
return None
103+
104+
from lmnr.sdk.browser.bubus_otel import BubusInstrumentor
105+
106+
return BubusInstrumentor()
107+
108+
76109
class ChromaInstrumentorInitializer(InstrumentorInitializer):
77110
def init_instrumentor(self, *args, **kwargs) -> BaseInstrumentor | None:
78111
if not is_package_installed("chromadb"):

src/lmnr/opentelemetry_lib/tracing/instruments.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ class Instruments(Enum):
1717
ANTHROPIC = "anthropic"
1818
BEDROCK = "bedrock"
1919
BROWSER_USE = "browser_use"
20+
BROWSER_USE_SESSION = "browser_use_session"
21+
BUBUS = "bubus"
2022
CHROMA = "chroma"
2123
COHERE = "cohere"
2224
CREWAI = "crewai"
@@ -60,6 +62,8 @@ class Instruments(Enum):
6062
Instruments.ANTHROPIC: initializers.AnthropicInstrumentorInitializer(),
6163
Instruments.BEDROCK: initializers.BedrockInstrumentorInitializer(),
6264
Instruments.BROWSER_USE: initializers.BrowserUseInstrumentorInitializer(),
65+
Instruments.BROWSER_USE_SESSION: initializers.BrowserUseSessionInstrumentorInitializer(),
66+
Instruments.BUBUS: initializers.BubusInstrumentorInitializer(),
6367
Instruments.CHROMA: initializers.ChromaInstrumentorInitializer(),
6468
Instruments.COHERE: initializers.CohereInstrumentorInitializer(),
6569
Instruments.CREWAI: initializers.CrewAIInstrumentorInitializer(),

src/lmnr/sdk/browser/browser_use_cdp_otel.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import asyncio
2+
import uuid
3+
14
from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
25
from lmnr.sdk.browser.utils import with_tracer_and_client_wrapper
36
from lmnr.version import __version__
@@ -12,7 +15,6 @@
1215
from opentelemetry.trace import get_tracer, Tracer
1316
from typing import Collection
1417
from wrapt import wrap_function_wrapper
15-
import uuid
1618

1719
# Stable versions, e.g. 0.6.0, satisfy this condition too
1820
_instruments = ("browser-use >= 0.6.0rc1",)
@@ -33,12 +35,7 @@
3335
]
3436

3537

36-
@with_tracer_and_client_wrapper
37-
async def _wrap(
38-
tracer: Tracer, client: AsyncLaminarClient, to_wrap, wrapped, instance, args, kwargs
39-
):
40-
result = await wrapped(*args, **kwargs)
41-
38+
async def process_wrapped_result(result, instance, client, to_wrap):
4239
if to_wrap.get("action") == "inject_session_recorder":
4340
is_registered = await is_recorder_present(result)
4441
if not is_registered:
@@ -50,6 +47,14 @@ async def _wrap(
5047
cdp_session = await instance.get_or_create_cdp_session(target_id)
5148
await take_full_snapshot(cdp_session)
5249

50+
51+
@with_tracer_and_client_wrapper
52+
async def _wrap(
53+
tracer: Tracer, client: AsyncLaminarClient, to_wrap, wrapped, instance, args, kwargs
54+
):
55+
result = await wrapped(*args, **kwargs)
56+
asyncio.create_task(process_wrapped_result(result, instance, client, to_wrap))
57+
5358
return result
5459

5560

src/lmnr/sdk/browser/bubus_otel.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from typing import Collection
2+
3+
from lmnr import Laminar
4+
from lmnr.opentelemetry_lib.tracing.context import get_current_context
5+
from lmnr.sdk.log import get_default_logger
6+
7+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
8+
from opentelemetry.instrumentation.utils import unwrap
9+
from opentelemetry.trace import NonRecordingSpan, get_current_span
10+
from wrapt import wrap_function_wrapper
11+
12+
13+
_instruments = ("bubus >= 1.3.0",)
14+
event_id_to_span_context = {}
15+
logger = get_default_logger(__name__)
16+
17+
18+
def wrap_dispatch(wrapped, instance, args, kwargs):
19+
event = args[0] if args and len(args) > 0 else kwargs.get("event", None)
20+
if event and hasattr(event, "event_id"):
21+
event_id = event.event_id
22+
if event_id:
23+
span = get_current_span(get_current_context())
24+
event_id_to_span_context[event_id] = span.get_span_context()
25+
return wrapped(*args, **kwargs)
26+
27+
28+
async def wrap_process_event(wrapped, instance, args, kwargs):
29+
event = args[0] if args and len(args) > 0 else kwargs.get("event", None)
30+
span_context = None
31+
if event and hasattr(event, "event_id"):
32+
event_id = event.event_id
33+
if event_id:
34+
span_context = event_id_to_span_context.get(event_id)
35+
if not span_context:
36+
return await wrapped(*args, **kwargs)
37+
if not Laminar.is_initialized():
38+
return await wrapped(*args, **kwargs)
39+
with Laminar.use_span(NonRecordingSpan(span_context)):
40+
return await wrapped(*args, **kwargs)
41+
42+
43+
class BubusInstrumentor(BaseInstrumentor):
44+
def __init__(self):
45+
super().__init__()
46+
47+
def instrumentation_dependencies(self) -> Collection[str]:
48+
return _instruments
49+
50+
def _instrument(self, **kwargs):
51+
try:
52+
wrap_function_wrapper("bubus.service", "EventBus.dispatch", wrap_dispatch)
53+
except (ModuleNotFoundError, ImportError):
54+
pass
55+
try:
56+
wrap_function_wrapper(
57+
"bubus.service", "EventBus.process_event", wrap_process_event
58+
)
59+
except (ModuleNotFoundError, ImportError):
60+
pass
61+
62+
def _uninstrument(self, **kwargs):
63+
try:
64+
unwrap("bubus.service", "EventBus.dispatch")
65+
except (ModuleNotFoundError, ImportError):
66+
pass
67+
try:
68+
unwrap("bubus.service", "EventBus.process_event")
69+
except (ModuleNotFoundError, ImportError):
70+
pass
71+
event_id_to_span_context.clear()

0 commit comments

Comments
 (0)