lmnr 0.4.64__py3-none-any.whl → 0.4.65__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.
@@ -5,32 +5,35 @@ lmnr/openllmetry_sdk/__init__.py,sha256=G_sNpfg0rnA6n4AaKM88g_gwaCNFcg1Ew8dB8el4
5
5
  lmnr/openllmetry_sdk/config/__init__.py,sha256=5aGdIdo1LffBkNwIBUbqzN6OUCMCrURU4b0rf5LBSI0,300
6
6
  lmnr/openllmetry_sdk/decorators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  lmnr/openllmetry_sdk/decorators/base.py,sha256=RNyvSreGBwwh8EWaa3O9samrnmKoZHK7eBzGOPT3lHc,7132
8
- lmnr/openllmetry_sdk/instruments.py,sha256=X1S3XbFF_RDlbxxbNxqKKJ9JNUStdTI6gLYCPWnoOTs,1066
8
+ lmnr/openllmetry_sdk/instruments.py,sha256=9KoJ19Qar1dBrmO1wikNEkKxRfus8znQTj-g_maRTTM,1098
9
9
  lmnr/openllmetry_sdk/tracing/__init__.py,sha256=xT73L1t2si2CM6QmMiTZ7zn-dKKYBLNrpBBWq6WfVBw,68
10
10
  lmnr/openllmetry_sdk/tracing/attributes.py,sha256=cLBmSp4AMv9E91Ck3yD5Z1Qx1L5ZRV-80VJxFA-sO0Q,1426
11
11
  lmnr/openllmetry_sdk/tracing/content_allow_list.py,sha256=3feztm6PBWNelc8pAZUcQyEGyeSpNiVKjOaDk65l2ps,846
12
12
  lmnr/openllmetry_sdk/tracing/context_manager.py,sha256=rdSus-p-TaevQ8hIAhfbnZr5dTqRvACDkzXGDpflncY,306
13
- lmnr/openllmetry_sdk/tracing/tracing.py,sha256=W6Aps6CfkecscSqvcmMPL-C9Ee3J0jMKIMfK27ce5NI,34081
13
+ lmnr/openllmetry_sdk/tracing/tracing.py,sha256=gn-3jIFHwPFzmzhcfxnqs0Y2YkBbIDtoHCTHFpzhCEQ,35194
14
14
  lmnr/openllmetry_sdk/utils/__init__.py,sha256=pNhf0G3vTd5ccoc03i1MXDbricSaiqCbi1DLWhSekK8,604
15
15
  lmnr/openllmetry_sdk/utils/in_memory_span_exporter.py,sha256=H_4TRaThMO1H6vUQ0OpQvzJk_fZH0OOsRAM1iZQXsR8,2112
16
16
  lmnr/openllmetry_sdk/utils/json_encoder.py,sha256=dK6b_axr70IYL7Vv-bu4wntvDDuyntoqsHaddqX7P58,463
17
17
  lmnr/openllmetry_sdk/utils/package_check.py,sha256=_-Fu9Zbp9tOyy27_-Rul7tDc8JaXYR2FmqF8SWOXSCc,244
18
18
  lmnr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  lmnr/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- lmnr/sdk/browser/__init__.py,sha256=NSP5sB-dm-f0FP70_GMvVrNFwc5rHf7SW0_Oisyo3cE,343
21
- lmnr/sdk/browser/playwright_patch.py,sha256=wfbkFXYMl6VmsxzLfaArmuCKtASnd3hUtlL7Shj6lss,13071
20
+ lmnr/sdk/browser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ lmnr/sdk/browser/browser_use_otel.py,sha256=OhUS_uIPRcA25EXQpcagY_hGsAVtJxkh69og9vRvrhc,3966
22
+ lmnr/sdk/browser/playwright_otel.py,sha256=gqFOQCiNvQbuGuSmpUQNPfE1a68QZ6yUsfvA92ra_uc,10027
22
23
  lmnr/sdk/browser/rrweb/rrweb.min.js,sha256=X5pgaoX1j_OjKTqGQgKB-83xUSuydNLQa-Kkh1AAZYM,140485
23
- lmnr/sdk/datasets.py,sha256=hJcQcwTJbtA4COoVG3god4xll9TBSDMfvrhKmMfanjg,1567
24
+ lmnr/sdk/browser/utils.py,sha256=IGiZc9ydRARk_J6PdxtQb4ffkuUYprdZLdsHYtdRrbU,3500
25
+ lmnr/sdk/client.py,sha256=f5MsMadaBlEfw9DOSgb6r2Y-l8O8gISnZ_gzzPcW-xU,10567
26
+ lmnr/sdk/datasets.py,sha256=XMbZtcrm56XPOezKhbyJQWdRZX3_wcxU1Eneld_vKVQ,1579
24
27
  lmnr/sdk/decorators.py,sha256=g0VBqUEMCPRbgjgGHauVuKK1wHEd9rkiGzlYUYrcml4,2336
25
28
  lmnr/sdk/eval_control.py,sha256=G6Fg3Xx_KWv72iBaWlNMdyRTF2bZFQnwJ68sJNSpIcY,177
26
- lmnr/sdk/evaluations.py,sha256=WmPAgQVm2C83xsG-qMdPrMuvCMFMoVdO37LqZtLc8xw,18702
27
- lmnr/sdk/laminar.py,sha256=1KkEq8DDogobtrFwgTOo71hDptX1oMMkDcYn973S-eU,38717
29
+ lmnr/sdk/evaluations.py,sha256=2YccO4yiAxWUhXhFBW1D3OY44g1vdpbtKig5yVDlOrU,19712
30
+ lmnr/sdk/laminar.py,sha256=MKlcLdPBSS2KltSX0xhYIOx4D2rpOvuISCMUPfjYbrc,33884
28
31
  lmnr/sdk/log.py,sha256=nt_YMmPw1IRbGy0b7q4rTtP4Yo3pQfNxqJPXK3nDSNQ,2213
29
- lmnr/sdk/types.py,sha256=NUVAIZAjjyWB6Ze7naa3Vx9g14Em6OtIXsSNSCJWISE,9892
32
+ lmnr/sdk/types.py,sha256=83rXmIGP12kyF0f27Wrw4kzJPBC44uyiaUwK32eusS0,10822
30
33
  lmnr/sdk/utils.py,sha256=sD1YEqhdPaHweY2VGmjMF9MC-X7Ikdc49E01D-HF77E,3377
31
- lmnr/version.py,sha256=v7DBfMNhZpmxPRXlj_blnGYLZeSnUSzmF20-ubdgmzs,1328
32
- lmnr-0.4.64.dist-info/LICENSE,sha256=67b_wJHVV1CBaWkrKFWU1wyqTPSdzH77Ls-59631COg,10411
33
- lmnr-0.4.64.dist-info/METADATA,sha256=B-pNM9ZEKV7zQcuQMQWVv-HekuIxDyLTbYXbX6Jqorc,13827
34
- lmnr-0.4.64.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
35
- lmnr-0.4.64.dist-info/entry_points.txt,sha256=K1jE20ww4jzHNZLnsfWBvU3YKDGBgbOiYG5Y7ivQcq4,37
36
- lmnr-0.4.64.dist-info/RECORD,,
34
+ lmnr/version.py,sha256=Hjj2AvIZyO2U2-U6lqZbPOgOne3kXYH1lhbu3RtRcq8,1328
35
+ lmnr-0.4.65.dist-info/LICENSE,sha256=67b_wJHVV1CBaWkrKFWU1wyqTPSdzH77Ls-59631COg,10411
36
+ lmnr-0.4.65.dist-info/METADATA,sha256=SLHcFjOlTbQkm58HNU2rLmGava74_kmxyG_65k0Sfu8,13877
37
+ lmnr-0.4.65.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
38
+ lmnr-0.4.65.dist-info/entry_points.txt,sha256=K1jE20ww4jzHNZLnsfWBvU3YKDGBgbOiYG5Y7ivQcq4,37
39
+ lmnr-0.4.65.dist-info/RECORD,,
@@ -1,377 +0,0 @@
1
- import uuid
2
- import asyncio
3
- import logging
4
- import time
5
- import os
6
- import aiohttp
7
- import requests
8
- import threading
9
- import gzip
10
- import json
11
- from lmnr.version import SDK_VERSION, PYTHON_VERSION
12
- from lmnr import Laminar
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
- try:
17
- from playwright.async_api import BrowserContext, Page
18
- from playwright.sync_api import (
19
- BrowserContext as SyncBrowserContext,
20
- Page as SyncPage,
21
- )
22
- except ImportError as e:
23
- raise ImportError(
24
- f"Attempted to import {__file__}, but it is designed "
25
- "to patch Playwright, which is not installed. Use `pip install playwright` "
26
- "to install Playwright or remove this import."
27
- ) from e
28
-
29
- _original_new_page = None
30
- _original_new_page_async = None
31
-
32
- current_dir = os.path.dirname(os.path.abspath(__file__))
33
- with open(os.path.join(current_dir, "rrweb", "rrweb.min.js"), "r") as f:
34
- RRWEB_CONTENT = f"() => {{ {f.read()} }}"
35
-
36
- INJECT_PLACEHOLDER = """
37
- () => {
38
- const BATCH_SIZE = 1000; // Maximum events to store in memory
39
-
40
- window.lmnrRrwebEventsBatch = [];
41
-
42
- // Utility function to compress individual event data
43
- async function compressEventData(data) {
44
- const jsonString = JSON.stringify(data);
45
- const blob = new Blob([jsonString], { type: 'application/json' });
46
- const compressedStream = blob.stream().pipeThrough(new CompressionStream('gzip'));
47
- const compressedResponse = new Response(compressedStream);
48
- const compressedData = await compressedResponse.arrayBuffer();
49
- return Array.from(new Uint8Array(compressedData));
50
- }
51
-
52
- window.lmnrGetAndClearEvents = () => {
53
- const events = window.lmnrRrwebEventsBatch;
54
- window.lmnrRrwebEventsBatch = [];
55
- return events;
56
- };
57
-
58
- // Add heartbeat events
59
- setInterval(async () => {
60
- const heartbeat = {
61
- type: 6,
62
- data: await compressEventData({ source: 'heartbeat' }),
63
- timestamp: Date.now()
64
- };
65
-
66
- window.lmnrRrwebEventsBatch.push(heartbeat);
67
-
68
- // Prevent memory issues by limiting batch size
69
- if (window.lmnrRrwebEventsBatch.length > BATCH_SIZE) {
70
- window.lmnrRrwebEventsBatch = window.lmnrRrwebEventsBatch.slice(-BATCH_SIZE);
71
- }
72
- }, 1000);
73
-
74
- window.lmnrRrweb.record({
75
- async emit(event) {
76
- // Compress the data field
77
- const compressedEvent = {
78
- ...event,
79
- data: await compressEventData(event.data)
80
- };
81
- window.lmnrRrwebEventsBatch.push(compressedEvent);
82
- }
83
- });
84
- }
85
- """
86
-
87
-
88
- def retry_sync(func, retries=5, delay=0.5, error_message="Operation failed"):
89
- """Utility function for retry logic in synchronous operations"""
90
- for attempt in range(retries):
91
- try:
92
- result = func()
93
- if result: # If function returns truthy value, consider it successful
94
- return result
95
- if attempt == retries - 1: # Last attempt
96
- logger.error(f"{error_message} after all retries")
97
- return None
98
- except Exception as e:
99
- if attempt == retries - 1: # Last attempt
100
- logger.error(f"{error_message}: {e}")
101
- return None
102
- time.sleep(delay)
103
- return None
104
-
105
-
106
- async def retry_async(func, retries=5, delay=0.5, error_message="Operation failed"):
107
- """Utility function for retry logic in asynchronous operations"""
108
- for attempt in range(retries):
109
- try:
110
- result = await func()
111
- if result: # If function returns truthy value, consider it successful
112
- return result
113
- if attempt == retries - 1: # Last attempt
114
- logger.error(f"{error_message} after all retries")
115
- return None
116
- except Exception as e:
117
- if attempt == retries - 1: # Last attempt
118
- logger.error(f"{error_message}: {e}")
119
- return None
120
- await asyncio.sleep(delay)
121
- return None
122
-
123
-
124
- async def send_events_async(
125
- page: Page, http_url: str, project_api_key: str, session_id: str, trace_id: str
126
- ):
127
- """Fetch events from the page and send them to the server"""
128
- try:
129
- # Check if function exists first
130
- has_function = await page.evaluate(
131
- """
132
- () => typeof window.lmnrGetAndClearEvents === 'function'
133
- """
134
- )
135
- if not has_function:
136
- return
137
-
138
- events = await page.evaluate("window.lmnrGetAndClearEvents()")
139
- if not events or len(events) == 0:
140
- return
141
-
142
- payload = {
143
- "sessionId": session_id,
144
- "traceId": trace_id,
145
- "events": events,
146
- "source": f"python@{PYTHON_VERSION}",
147
- "sdkVersion": SDK_VERSION,
148
- }
149
-
150
- headers = {
151
- "Content-Type": "application/json",
152
- "Authorization": f"Bearer {project_api_key}",
153
- "Accept": "application/json",
154
- }
155
-
156
- async with aiohttp.ClientSession() as session:
157
- async with session.post(
158
- f"{http_url}/v1/browser-sessions/events",
159
- json=payload,
160
- headers=headers,
161
- ) as response:
162
- if not response.ok:
163
- logger.error(f"Failed to send events: {response.status}")
164
-
165
- except Exception as e:
166
- logger.error(f"Error sending events: {e}")
167
-
168
-
169
- def send_events_sync(
170
- page: SyncPage, http_url: str, project_api_key: str, session_id: str, trace_id: str
171
- ):
172
- """Synchronous version of send_events"""
173
- try:
174
- # Check if function exists first
175
- has_function = page.evaluate(
176
- """
177
- () => typeof window.lmnrGetAndClearEvents === 'function'
178
- """
179
- )
180
- if not has_function:
181
- return
182
-
183
- events = page.evaluate("window.lmnrGetAndClearEvents()")
184
- if not events or len(events) == 0:
185
- return
186
-
187
- payload = {
188
- "sessionId": session_id,
189
- "traceId": trace_id,
190
- "events": events,
191
- "source": f"python@{PYTHON_VERSION}",
192
- "sdkVersion": SDK_VERSION,
193
- }
194
-
195
- headers = {
196
- "Content-Type": "application/json",
197
- "Authorization": f"Bearer {project_api_key}",
198
- "Accept": "application/json",
199
- "Content-Encoding": "gzip", # Add Content-Encoding header
200
- }
201
-
202
- # Compress the payload
203
- compressed_payload = gzip.compress(json.dumps(payload).encode("utf-8"))
204
-
205
- response = requests.post(
206
- f"{http_url}/v1/browser-sessions/events",
207
- data=compressed_payload, # Use data instead of json for raw bytes
208
- headers=headers,
209
- )
210
- if not response.ok:
211
- logger.error(f"Failed to send events: {response.status_code}")
212
-
213
- except Exception as e:
214
- logger.error(f"Error sending events: {e}")
215
-
216
-
217
- def init_playwright_tracing(http_url: str, project_api_key: str):
218
-
219
- def inject_rrweb(page: SyncPage):
220
- try:
221
- page.wait_for_load_state("domcontentloaded")
222
-
223
- # Wrap the evaluate call in a try-catch
224
- try:
225
- is_loaded = page.evaluate(
226
- """() => typeof window.lmnrRrweb !== 'undefined'"""
227
- )
228
- except Exception as e:
229
- logger.debug(f"Failed to check if rrweb is loaded: {e}")
230
- is_loaded = False
231
-
232
- if not is_loaded:
233
- def load_rrweb():
234
- try:
235
- page.evaluate(RRWEB_CONTENT)
236
- page.wait_for_function(
237
- """(() => typeof window.lmnrRrweb !== 'undefined')""",
238
- timeout=5000,
239
- )
240
- return True
241
- except Exception as e:
242
- logger.debug(f"Failed to load rrweb: {e}")
243
- return False
244
-
245
- if not retry_sync(
246
- load_rrweb, delay=1, error_message="Failed to load rrweb"
247
- ):
248
- return
249
-
250
- try:
251
- page.evaluate(INJECT_PLACEHOLDER)
252
- except Exception as e:
253
- logger.debug(f"Failed to inject rrweb placeholder: {e}")
254
-
255
- except Exception as e:
256
- logger.error(f"Error during rrweb injection: {e}")
257
-
258
- async def inject_rrweb_async(page: Page):
259
- try:
260
- await page.wait_for_load_state("domcontentloaded")
261
-
262
- # Wrap the evaluate call in a try-catch
263
- try:
264
- is_loaded = await page.evaluate(
265
- """() => typeof window.lmnrRrweb !== 'undefined'"""
266
- )
267
- except Exception as e:
268
- logger.debug(f"Failed to check if rrweb is loaded: {e}")
269
- is_loaded = False
270
-
271
- if not is_loaded:
272
- async def load_rrweb():
273
- try:
274
- await page.evaluate(RRWEB_CONTENT)
275
- await page.wait_for_function(
276
- """(() => typeof window.lmnrRrweb !== 'undefined')""",
277
- timeout=5000,
278
- )
279
- return True
280
- except Exception as e:
281
- logger.debug(f"Failed to load rrweb: {e}")
282
- return False
283
-
284
- if not await retry_async(
285
- load_rrweb, delay=1, error_message="Failed to load rrweb"
286
- ):
287
- return
288
-
289
- try:
290
- await page.evaluate(INJECT_PLACEHOLDER)
291
- except Exception as e:
292
- logger.debug(f"Failed to inject rrweb placeholder: {e}")
293
-
294
- except Exception as e:
295
- logger.error(f"Error during rrweb injection: {e}")
296
-
297
- def handle_navigation(page: SyncPage, session_id: str, trace_id: str):
298
- def on_load():
299
- try:
300
- inject_rrweb(page)
301
- except Exception as e:
302
- logger.error(f"Error in on_load handler: {e}")
303
-
304
- page.on("load", on_load)
305
- inject_rrweb(page)
306
-
307
- def collection_loop():
308
- while not page.is_closed(): # Stop when page closes
309
- send_events_sync(page, http_url, project_api_key, session_id, trace_id)
310
- time.sleep(2)
311
-
312
- thread = threading.Thread(target=collection_loop, daemon=True)
313
- thread.start()
314
-
315
- async def handle_navigation_async(page: Page, session_id: str, trace_id: str):
316
- async def on_load():
317
- try:
318
- await inject_rrweb_async(page)
319
- except Exception as e:
320
- logger.error(f"Error in on_load handler: {e}")
321
-
322
- page.on("load", lambda: asyncio.create_task(on_load()))
323
- await inject_rrweb_async(page)
324
-
325
- async def collection_loop():
326
- try:
327
- while not page.is_closed(): # Stop when page closes
328
- await send_events_async(
329
- page, http_url, project_api_key, session_id, trace_id
330
- )
331
- await asyncio.sleep(2)
332
- logger.info("Event collection stopped")
333
- except Exception as e:
334
- logger.error(f"Event collection stopped: {e}")
335
-
336
- # Create and store task
337
- task = asyncio.create_task(collection_loop())
338
-
339
- # Clean up task when page closes
340
- page.on("close", lambda: task.cancel())
341
-
342
- def patched_new_page(self: SyncBrowserContext, *args, **kwargs):
343
- with Laminar.start_as_current_span(name="browser_context.new_page") as span:
344
- page = _original_new_page(self, *args, **kwargs)
345
-
346
- session_id = str(uuid.uuid4().hex)
347
- span.set_attribute("lmnr.internal.has_browser_session", True)
348
-
349
- trace_id = format(span.get_span_context().trace_id, "032x")
350
- session_id = str(uuid.uuid4().hex)
351
-
352
- handle_navigation(page, session_id, trace_id)
353
- return page
354
-
355
- async def patched_new_page_async(self: BrowserContext, *args, **kwargs):
356
- with Laminar.start_as_current_span(name="browser_context.new_page") as span:
357
- page = await _original_new_page_async(self, *args, **kwargs)
358
-
359
- session_id = str(uuid.uuid4().hex)
360
-
361
- span.set_attribute("lmnr.internal.has_browser_session", True)
362
- trace_id = format(span.get_span_context().trace_id, "032x")
363
- session_id = str(uuid.uuid4().hex)
364
- await handle_navigation_async(page, session_id, trace_id)
365
- return page
366
-
367
- def patch_browser():
368
- global _original_new_page, _original_new_page_async
369
- if _original_new_page_async is None:
370
- _original_new_page_async = BrowserContext.new_page
371
- BrowserContext.new_page = patched_new_page_async
372
-
373
- if _original_new_page is None:
374
- _original_new_page = SyncBrowserContext.new_page
375
- SyncBrowserContext.new_page = patched_new_page
376
-
377
- patch_browser()
File without changes
File without changes