lmnr 0.4.66__py3-none-any.whl → 0.5.1__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 +30 -0
- lmnr/openllmetry_sdk/__init__.py +4 -16
- lmnr/openllmetry_sdk/tracing/attributes.py +0 -1
- lmnr/openllmetry_sdk/tracing/tracing.py +30 -10
- lmnr/sdk/browser/browser_use_otel.py +4 -4
- lmnr/sdk/browser/playwright_otel.py +299 -228
- lmnr/sdk/browser/pw_utils.py +289 -0
- lmnr/sdk/browser/utils.py +18 -53
- lmnr/sdk/client/asynchronous/async_client.py +157 -0
- lmnr/sdk/client/asynchronous/resources/__init__.py +13 -0
- lmnr/sdk/client/asynchronous/resources/agent.py +220 -0
- lmnr/sdk/client/asynchronous/resources/base.py +32 -0
- lmnr/sdk/client/asynchronous/resources/browser_events.py +40 -0
- lmnr/sdk/client/asynchronous/resources/evals.py +64 -0
- lmnr/sdk/client/asynchronous/resources/pipeline.py +89 -0
- lmnr/sdk/client/asynchronous/resources/semantic_search.py +60 -0
- lmnr/sdk/client/synchronous/resources/__init__.py +7 -0
- lmnr/sdk/client/synchronous/resources/agent.py +215 -0
- lmnr/sdk/client/synchronous/resources/base.py +32 -0
- lmnr/sdk/client/synchronous/resources/browser_events.py +40 -0
- lmnr/sdk/client/synchronous/resources/evals.py +102 -0
- lmnr/sdk/client/synchronous/resources/pipeline.py +89 -0
- lmnr/sdk/client/synchronous/resources/semantic_search.py +60 -0
- lmnr/sdk/client/synchronous/sync_client.py +170 -0
- lmnr/sdk/datasets.py +7 -2
- lmnr/sdk/evaluations.py +59 -35
- lmnr/sdk/laminar.py +34 -174
- lmnr/sdk/types.py +124 -23
- lmnr/sdk/utils.py +10 -0
- lmnr/version.py +6 -6
- {lmnr-0.4.66.dist-info → lmnr-0.5.1.dist-info}/METADATA +88 -38
- lmnr-0.5.1.dist-info/RECORD +55 -0
- {lmnr-0.4.66.dist-info → lmnr-0.5.1.dist-info}/WHEEL +1 -1
- lmnr/sdk/client.py +0 -313
- lmnr-0.4.66.dist-info/RECORD +0 -39
- {lmnr-0.4.66.dist-info → lmnr-0.5.1.dist-info}/LICENSE +0 -0
- {lmnr-0.4.66.dist-info → lmnr-0.5.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,289 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
import os
|
4
|
+
import time
|
5
|
+
import threading
|
6
|
+
|
7
|
+
from opentelemetry import trace
|
8
|
+
|
9
|
+
from lmnr.sdk.decorators import observe
|
10
|
+
from lmnr.sdk.browser.utils import retry_sync, retry_async
|
11
|
+
from lmnr.sdk.client.synchronous.sync_client import LaminarClient
|
12
|
+
from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
|
13
|
+
|
14
|
+
try:
|
15
|
+
from playwright.async_api import Page
|
16
|
+
from playwright.sync_api import Page as SyncPage
|
17
|
+
except ImportError as e:
|
18
|
+
raise ImportError(
|
19
|
+
f"Attempted to import {__file__}, but it is designed "
|
20
|
+
"to patch Playwright, which is not installed. Use `pip install playwright` "
|
21
|
+
"to install Playwright or remove this import."
|
22
|
+
) from e
|
23
|
+
|
24
|
+
logger = logging.getLogger(__name__)
|
25
|
+
|
26
|
+
# Track pages we've already instrumented to avoid double-instrumentation
|
27
|
+
instrumented_pages = set()
|
28
|
+
async_instrumented_pages = set()
|
29
|
+
|
30
|
+
|
31
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
32
|
+
with open(os.path.join(current_dir, "rrweb", "rrweb.min.js"), "r") as f:
|
33
|
+
RRWEB_CONTENT = f"() => {{ {f.read()} }}"
|
34
|
+
|
35
|
+
INJECT_PLACEHOLDER = """
|
36
|
+
() => {
|
37
|
+
const BATCH_SIZE = 1000; // Maximum events to store in memory
|
38
|
+
|
39
|
+
window.lmnrRrwebEventsBatch = new Set();
|
40
|
+
|
41
|
+
// Utility function to compress individual event data
|
42
|
+
async function compressEventData(data) {
|
43
|
+
const jsonString = JSON.stringify(data);
|
44
|
+
const blob = new Blob([jsonString], { type: 'application/json' });
|
45
|
+
const compressedStream = blob.stream().pipeThrough(new CompressionStream('gzip'));
|
46
|
+
const compressedResponse = new Response(compressedStream);
|
47
|
+
const compressedData = await compressedResponse.arrayBuffer();
|
48
|
+
return Array.from(new Uint8Array(compressedData));
|
49
|
+
}
|
50
|
+
|
51
|
+
window.lmnrGetAndClearEvents = () => {
|
52
|
+
const events = window.lmnrRrwebEventsBatch;
|
53
|
+
window.lmnrRrwebEventsBatch = new Set();
|
54
|
+
return Array.from(events);
|
55
|
+
};
|
56
|
+
|
57
|
+
// Add heartbeat events
|
58
|
+
setInterval(async () => {
|
59
|
+
const heartbeat = {
|
60
|
+
type: 6,
|
61
|
+
data: await compressEventData({ source: 'heartbeat' }),
|
62
|
+
timestamp: Date.now()
|
63
|
+
};
|
64
|
+
|
65
|
+
window.lmnrRrwebEventsBatch.add(heartbeat);
|
66
|
+
|
67
|
+
// Prevent memory issues by limiting batch size
|
68
|
+
if (window.lmnrRrwebEventsBatch.size > BATCH_SIZE) {
|
69
|
+
window.lmnrRrwebEventsBatch = new Set(Array.from(window.lmnrRrwebEventsBatch).slice(-BATCH_SIZE));
|
70
|
+
}
|
71
|
+
}, 1000);
|
72
|
+
|
73
|
+
window.lmnrRrweb.record({
|
74
|
+
async emit(event) {
|
75
|
+
// Ignore events from all tabs except the current one
|
76
|
+
if (document.visibilityState === 'hidden' || document.hidden) {
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
// Compress the data field
|
80
|
+
const compressedEvent = {
|
81
|
+
...event,
|
82
|
+
data: await compressEventData(event.data)
|
83
|
+
};
|
84
|
+
window.lmnrRrwebEventsBatch.add(compressedEvent);
|
85
|
+
}
|
86
|
+
});
|
87
|
+
}
|
88
|
+
"""
|
89
|
+
|
90
|
+
|
91
|
+
async def send_events_async(
|
92
|
+
page: Page, session_id: str, trace_id: str, client: AsyncLaminarClient
|
93
|
+
):
|
94
|
+
"""Fetch events from the page and send them to the server"""
|
95
|
+
try:
|
96
|
+
# Check if function exists first
|
97
|
+
has_function = await page.evaluate(
|
98
|
+
"""
|
99
|
+
() => typeof window.lmnrGetAndClearEvents === 'function'
|
100
|
+
"""
|
101
|
+
)
|
102
|
+
if not has_function:
|
103
|
+
return
|
104
|
+
|
105
|
+
events = await page.evaluate("window.lmnrGetAndClearEvents()")
|
106
|
+
if not events or len(events) == 0:
|
107
|
+
return
|
108
|
+
|
109
|
+
await client._browser_events.send(session_id, trace_id, events)
|
110
|
+
|
111
|
+
except Exception as e:
|
112
|
+
logger.error(f"Error sending events: {e}")
|
113
|
+
|
114
|
+
|
115
|
+
def send_events_sync(
|
116
|
+
page: SyncPage, session_id: str, trace_id: str, client: LaminarClient
|
117
|
+
):
|
118
|
+
"""Synchronous version of send_events"""
|
119
|
+
try:
|
120
|
+
# Check if function exists first
|
121
|
+
has_function = page.evaluate(
|
122
|
+
"""
|
123
|
+
() => typeof window.lmnrGetAndClearEvents === 'function'
|
124
|
+
"""
|
125
|
+
)
|
126
|
+
if not has_function:
|
127
|
+
return
|
128
|
+
|
129
|
+
events = page.evaluate("window.lmnrGetAndClearEvents()")
|
130
|
+
if not events or len(events) == 0:
|
131
|
+
return
|
132
|
+
|
133
|
+
client._browser_events.send(session_id, trace_id, events)
|
134
|
+
|
135
|
+
except Exception as e:
|
136
|
+
logger.error(f"Error sending events: {e}")
|
137
|
+
|
138
|
+
|
139
|
+
def inject_rrweb_sync(page: SyncPage):
|
140
|
+
try:
|
141
|
+
page.wait_for_load_state("domcontentloaded")
|
142
|
+
|
143
|
+
# Wrap the evaluate call in a try-catch
|
144
|
+
try:
|
145
|
+
is_loaded = page.evaluate(
|
146
|
+
"""() => typeof window.lmnrRrweb !== 'undefined'"""
|
147
|
+
)
|
148
|
+
except Exception as e:
|
149
|
+
logger.debug(f"Failed to check if rrweb is loaded: {e}")
|
150
|
+
is_loaded = False
|
151
|
+
|
152
|
+
if not is_loaded:
|
153
|
+
|
154
|
+
def load_rrweb():
|
155
|
+
try:
|
156
|
+
page.evaluate(RRWEB_CONTENT)
|
157
|
+
page.wait_for_function(
|
158
|
+
"""(() => typeof window.lmnrRrweb !== 'undefined')""",
|
159
|
+
timeout=5000,
|
160
|
+
)
|
161
|
+
return True
|
162
|
+
except Exception as e:
|
163
|
+
logger.debug(f"Failed to load rrweb: {e}")
|
164
|
+
return False
|
165
|
+
|
166
|
+
if not retry_sync(
|
167
|
+
load_rrweb, delay=1, error_message="Failed to load rrweb"
|
168
|
+
):
|
169
|
+
return
|
170
|
+
|
171
|
+
try:
|
172
|
+
page.evaluate(INJECT_PLACEHOLDER)
|
173
|
+
except Exception as e:
|
174
|
+
logger.debug(f"Failed to inject rrweb placeholder: {e}")
|
175
|
+
|
176
|
+
except Exception as e:
|
177
|
+
logger.error(f"Error during rrweb injection: {e}")
|
178
|
+
|
179
|
+
|
180
|
+
async def inject_rrweb_async(page: Page):
|
181
|
+
try:
|
182
|
+
await page.wait_for_load_state("domcontentloaded")
|
183
|
+
|
184
|
+
# Wrap the evaluate call in a try-catch
|
185
|
+
try:
|
186
|
+
is_loaded = await page.evaluate(
|
187
|
+
"""() => typeof window.lmnrRrweb !== 'undefined'"""
|
188
|
+
)
|
189
|
+
except Exception as e:
|
190
|
+
logger.debug(f"Failed to check if rrweb is loaded: {e}")
|
191
|
+
is_loaded = False
|
192
|
+
|
193
|
+
if not is_loaded:
|
194
|
+
|
195
|
+
async def load_rrweb():
|
196
|
+
try:
|
197
|
+
await page.evaluate(RRWEB_CONTENT)
|
198
|
+
await page.wait_for_function(
|
199
|
+
"""(() => typeof window.lmnrRrweb !== 'undefined')""",
|
200
|
+
timeout=5000,
|
201
|
+
)
|
202
|
+
return True
|
203
|
+
except Exception as e:
|
204
|
+
logger.debug(f"Failed to load rrweb: {e}")
|
205
|
+
return False
|
206
|
+
|
207
|
+
if not await retry_async(
|
208
|
+
load_rrweb, delay=1, error_message="Failed to load rrweb"
|
209
|
+
):
|
210
|
+
return
|
211
|
+
|
212
|
+
try:
|
213
|
+
await page.evaluate(INJECT_PLACEHOLDER)
|
214
|
+
except Exception as e:
|
215
|
+
logger.debug(f"Failed to inject rrweb placeholder: {e}")
|
216
|
+
|
217
|
+
except Exception as e:
|
218
|
+
logger.error(f"Error during rrweb injection: {e}")
|
219
|
+
|
220
|
+
|
221
|
+
@observe(name="playwright.page", ignore_input=True, ignore_output=True)
|
222
|
+
def handle_navigation_sync(
|
223
|
+
page: SyncPage, session_id: str, trace_id: str, client: LaminarClient
|
224
|
+
):
|
225
|
+
trace.get_current_span().set_attribute("lmnr.internal.has_browser_session", True)
|
226
|
+
# Check if we've already instrumented this page
|
227
|
+
page_id = id(page)
|
228
|
+
if page_id in instrumented_pages:
|
229
|
+
return
|
230
|
+
instrumented_pages.add(page_id)
|
231
|
+
|
232
|
+
def on_load():
|
233
|
+
try:
|
234
|
+
inject_rrweb_sync(page)
|
235
|
+
except Exception as e:
|
236
|
+
logger.error(f"Error in on_load handler: {e}")
|
237
|
+
|
238
|
+
page.on("load", on_load)
|
239
|
+
inject_rrweb_sync(page)
|
240
|
+
|
241
|
+
def collection_loop():
|
242
|
+
while not page.is_closed(): # Stop when page closes
|
243
|
+
send_events_sync(page, session_id, trace_id, client)
|
244
|
+
time.sleep(2)
|
245
|
+
|
246
|
+
# Clean up when page closes
|
247
|
+
if page_id in instrumented_pages:
|
248
|
+
instrumented_pages.remove(page_id)
|
249
|
+
|
250
|
+
thread = threading.Thread(target=collection_loop, daemon=True)
|
251
|
+
thread.start()
|
252
|
+
|
253
|
+
|
254
|
+
@observe(name="playwright.page", ignore_input=True, ignore_output=True)
|
255
|
+
async def handle_navigation_async(
|
256
|
+
page: Page, session_id: str, trace_id: str, client: AsyncLaminarClient
|
257
|
+
):
|
258
|
+
trace.get_current_span().set_attribute("lmnr.internal.has_browser_session", True)
|
259
|
+
# Check if we've already instrumented this page
|
260
|
+
page_id = id(page)
|
261
|
+
if page_id in async_instrumented_pages:
|
262
|
+
return
|
263
|
+
async_instrumented_pages.add(page_id)
|
264
|
+
|
265
|
+
async def on_load():
|
266
|
+
try:
|
267
|
+
await inject_rrweb_async(page)
|
268
|
+
except Exception as e:
|
269
|
+
logger.error(f"Error in on_load handler: {e}")
|
270
|
+
|
271
|
+
page.on("load", lambda: asyncio.create_task(on_load()))
|
272
|
+
await inject_rrweb_async(page)
|
273
|
+
|
274
|
+
async def collection_loop():
|
275
|
+
try:
|
276
|
+
while not page.is_closed(): # Stop when page closes
|
277
|
+
await send_events_async(page, session_id, trace_id, client)
|
278
|
+
await asyncio.sleep(2)
|
279
|
+
# Clean up when page closes
|
280
|
+
async_instrumented_pages.remove(page_id)
|
281
|
+
logger.info("Event collection stopped")
|
282
|
+
except Exception as e:
|
283
|
+
logger.error(f"Event collection stopped: {e}")
|
284
|
+
|
285
|
+
# Create and store task
|
286
|
+
task = asyncio.create_task(collection_loop())
|
287
|
+
|
288
|
+
# Clean up task when page closes
|
289
|
+
page.on("close", lambda: task.cancel())
|
lmnr/sdk/browser/utils.py
CHANGED
@@ -1,62 +1,15 @@
|
|
1
1
|
import asyncio
|
2
2
|
import logging
|
3
3
|
import time
|
4
|
+
from typing import Union
|
5
|
+
|
6
|
+
from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
|
7
|
+
from lmnr.sdk.client.synchronous.sync_client import LaminarClient
|
4
8
|
|
5
9
|
logger = logging.getLogger(__name__)
|
6
10
|
|
7
|
-
|
8
|
-
()
|
9
|
-
const BATCH_SIZE = 1000; // Maximum events to store in memory
|
10
|
-
|
11
|
-
window.lmnrRrwebEventsBatch = [];
|
12
|
-
|
13
|
-
// Utility function to compress individual event data
|
14
|
-
async function compressEventData(data) {
|
15
|
-
const jsonString = JSON.stringify(data);
|
16
|
-
const blob = new Blob([jsonString], { type: 'application/json' });
|
17
|
-
const compressedStream = blob.stream().pipeThrough(new CompressionStream('gzip'));
|
18
|
-
const compressedResponse = new Response(compressedStream);
|
19
|
-
const compressedData = await compressedResponse.arrayBuffer();
|
20
|
-
return Array.from(new Uint8Array(compressedData));
|
21
|
-
}
|
22
|
-
|
23
|
-
window.lmnrGetAndClearEvents = () => {
|
24
|
-
const events = window.lmnrRrwebEventsBatch;
|
25
|
-
window.lmnrRrwebEventsBatch = [];
|
26
|
-
return events;
|
27
|
-
};
|
28
|
-
|
29
|
-
// Add heartbeat events
|
30
|
-
setInterval(async () => {
|
31
|
-
const heartbeat = {
|
32
|
-
type: 6,
|
33
|
-
data: await compressEventData({ source: 'heartbeat' }),
|
34
|
-
timestamp: Date.now()
|
35
|
-
};
|
36
|
-
|
37
|
-
window.lmnrRrwebEventsBatch.push(heartbeat);
|
38
|
-
|
39
|
-
// Prevent memory issues by limiting batch size
|
40
|
-
if (window.lmnrRrwebEventsBatch.length > BATCH_SIZE) {
|
41
|
-
window.lmnrRrwebEventsBatch = window.lmnrRrwebEventsBatch.slice(-BATCH_SIZE);
|
42
|
-
}
|
43
|
-
}, 1000);
|
44
|
-
|
45
|
-
window.lmnrRrweb.record({
|
46
|
-
async emit(event) {
|
47
|
-
// Compress the data field
|
48
|
-
const compressedEvent = {
|
49
|
-
...event,
|
50
|
-
data: await compressEventData(event.data)
|
51
|
-
};
|
52
|
-
window.lmnrRrwebEventsBatch.push(compressedEvent);
|
53
|
-
}
|
54
|
-
});
|
55
|
-
}
|
56
|
-
"""
|
57
|
-
|
58
|
-
|
59
|
-
def _with_tracer_wrapper(func):
|
11
|
+
|
12
|
+
def with_tracer_wrapper(func):
|
60
13
|
"""Helper for providing tracer for wrapper functions."""
|
61
14
|
|
62
15
|
def _with_tracer(tracer, to_wrap):
|
@@ -68,6 +21,18 @@ def _with_tracer_wrapper(func):
|
|
68
21
|
return _with_tracer
|
69
22
|
|
70
23
|
|
24
|
+
def with_tracer_and_client_wrapper(func):
|
25
|
+
"""Helper for providing tracer and client for wrapper functions."""
|
26
|
+
|
27
|
+
def _with_tracer(tracer, client: Union[LaminarClient, AsyncLaminarClient], to_wrap):
|
28
|
+
def wrapper(wrapped, instance, args, kwargs):
|
29
|
+
return func(tracer, client, to_wrap, wrapped, instance, args, kwargs)
|
30
|
+
|
31
|
+
return wrapper
|
32
|
+
|
33
|
+
return _with_tracer
|
34
|
+
|
35
|
+
|
71
36
|
def retry_sync(func, retries=5, delay=0.5, error_message="Operation failed"):
|
72
37
|
"""Utility function for retry logic in synchronous operations"""
|
73
38
|
for attempt in range(retries):
|
@@ -0,0 +1,157 @@
|
|
1
|
+
"""
|
2
|
+
Laminar HTTP client. Used to send data to/from the Laminar API.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import httpx
|
6
|
+
import re
|
7
|
+
from typing import Optional, TypeVar
|
8
|
+
from types import TracebackType
|
9
|
+
|
10
|
+
from lmnr.sdk.client.asynchronous.resources import (
|
11
|
+
AsyncAgent,
|
12
|
+
AsyncBrowserEvents,
|
13
|
+
AsyncEvals,
|
14
|
+
AsyncPipeline,
|
15
|
+
AsyncSemanticSearch,
|
16
|
+
)
|
17
|
+
from lmnr.sdk.utils import from_env
|
18
|
+
|
19
|
+
_T = TypeVar("_T", bound="AsyncLaminarClient")
|
20
|
+
|
21
|
+
|
22
|
+
class AsyncLaminarClient:
|
23
|
+
__base_url: str
|
24
|
+
__project_api_key: str
|
25
|
+
__client: httpx.AsyncClient = None
|
26
|
+
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
base_url: Optional[str] = None,
|
30
|
+
project_api_key: Optional[str] = None,
|
31
|
+
port: Optional[int] = None,
|
32
|
+
timeout: int = 3600,
|
33
|
+
):
|
34
|
+
"""Initializer for the Laminar HTTP client.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
base_url (str): base URL of the Laminar API.
|
38
|
+
project_api_key (str): Laminar project API key
|
39
|
+
port (Optional[int], optional): port of the Laminar API HTTP server.\
|
40
|
+
Overrides any port in the base URL.
|
41
|
+
Defaults to None. If none is provided, the default port (443) will
|
42
|
+
be used.
|
43
|
+
timeout (int, optional): global timeout seconds for the HTTP client.\
|
44
|
+
Applied to all httpx operations, i.e. connect, read, get_from_pool, etc.
|
45
|
+
Defaults to 3600.
|
46
|
+
"""
|
47
|
+
# If port is already in the base URL, use it as is
|
48
|
+
base_url = base_url or from_env("LMNR_BASE_URL") or "https://api.lmnr.ai"
|
49
|
+
if match := re.search(r":(\d{1,5})$", base_url):
|
50
|
+
base_url = base_url[: -len(match.group(0))]
|
51
|
+
if port is None:
|
52
|
+
port = int(match.group(1))
|
53
|
+
|
54
|
+
base_url = base_url.rstrip("/")
|
55
|
+
self.__base_url = f"{base_url}:{port or 443}"
|
56
|
+
self.__project_api_key = project_api_key or from_env("LMNR_PROJECT_API_KEY")
|
57
|
+
if not self.__project_api_key:
|
58
|
+
raise ValueError(
|
59
|
+
"Project API key is not set. Please set the LMNR_PROJECT_API_KEY environment "
|
60
|
+
"variable or pass project_api_key to the initializer."
|
61
|
+
)
|
62
|
+
|
63
|
+
self.__client = httpx.AsyncClient(
|
64
|
+
headers=self._headers(),
|
65
|
+
timeout=timeout,
|
66
|
+
)
|
67
|
+
|
68
|
+
# Initialize resource objects
|
69
|
+
self.__pipeline = AsyncPipeline(
|
70
|
+
self.__client, self.__base_url, self.__project_api_key
|
71
|
+
)
|
72
|
+
self.__semantic_search = AsyncSemanticSearch(
|
73
|
+
self.__client, self.__base_url, self.__project_api_key
|
74
|
+
)
|
75
|
+
self.__agent = AsyncAgent(
|
76
|
+
self.__client, self.__base_url, self.__project_api_key
|
77
|
+
)
|
78
|
+
self.__evals = AsyncEvals(
|
79
|
+
self.__client, self.__base_url, self.__project_api_key
|
80
|
+
)
|
81
|
+
self.__browser_events = AsyncBrowserEvents(
|
82
|
+
self.__client, self.__base_url, self.__project_api_key
|
83
|
+
)
|
84
|
+
|
85
|
+
@property
|
86
|
+
def pipeline(self) -> AsyncPipeline:
|
87
|
+
"""Get the Pipeline resource.
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
Pipeline: The Pipeline resource instance.
|
91
|
+
"""
|
92
|
+
return self.__pipeline
|
93
|
+
|
94
|
+
@property
|
95
|
+
def semantic_search(self) -> AsyncSemanticSearch:
|
96
|
+
"""Get the SemanticSearch resource.
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
SemanticSearch: The SemanticSearch resource instance.
|
100
|
+
"""
|
101
|
+
return self.__semantic_search
|
102
|
+
|
103
|
+
@property
|
104
|
+
def agent(self) -> AsyncAgent:
|
105
|
+
"""Get the Agent resource.
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
Agent: The Agent resource instance.
|
109
|
+
"""
|
110
|
+
return self.__agent
|
111
|
+
|
112
|
+
@property
|
113
|
+
def _evals(self) -> AsyncEvals:
|
114
|
+
"""Get the Evals resource.
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
Evals: The Evals resource instance.
|
118
|
+
"""
|
119
|
+
return self.__evals
|
120
|
+
|
121
|
+
@property
|
122
|
+
def _browser_events(self) -> AsyncBrowserEvents:
|
123
|
+
"""Get the BrowserEvents resource.
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
BrowserEvents: The BrowserEvents resource instance.
|
127
|
+
"""
|
128
|
+
return self.__browser_events
|
129
|
+
|
130
|
+
def is_closed(self) -> bool:
|
131
|
+
return self.__client.is_closed
|
132
|
+
|
133
|
+
async def close(self) -> None:
|
134
|
+
"""Close the underlying HTTPX client.
|
135
|
+
|
136
|
+
The client will *not* be usable after this.
|
137
|
+
"""
|
138
|
+
await self.__client.aclose()
|
139
|
+
|
140
|
+
async def __aenter__(self: _T) -> _T:
|
141
|
+
return self
|
142
|
+
|
143
|
+
async def __aexit__(
|
144
|
+
self,
|
145
|
+
exc_type: Optional[type[BaseException]],
|
146
|
+
exc: Optional[BaseException],
|
147
|
+
exc_tb: Optional[TracebackType],
|
148
|
+
) -> None:
|
149
|
+
await self.close()
|
150
|
+
|
151
|
+
def _headers(self) -> dict[str, str]:
|
152
|
+
assert self.__project_api_key is not None, "Project API key is not set"
|
153
|
+
return {
|
154
|
+
"Authorization": "Bearer " + self.__project_api_key,
|
155
|
+
"Content-Type": "application/json",
|
156
|
+
"Accept": "application/json",
|
157
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from lmnr.sdk.client.asynchronous.resources.agent import AsyncAgent
|
2
|
+
from lmnr.sdk.client.asynchronous.resources.browser_events import AsyncBrowserEvents
|
3
|
+
from lmnr.sdk.client.asynchronous.resources.evals import AsyncEvals
|
4
|
+
from lmnr.sdk.client.asynchronous.resources.pipeline import AsyncPipeline
|
5
|
+
from lmnr.sdk.client.asynchronous.resources.semantic_search import AsyncSemanticSearch
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
"AsyncPipeline",
|
9
|
+
"AsyncSemanticSearch",
|
10
|
+
"AsyncAgent",
|
11
|
+
"AsyncEvals",
|
12
|
+
"AsyncBrowserEvents",
|
13
|
+
]
|