lmnr 0.5.1a0__py3-none-any.whl → 0.5.2__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.
Files changed (33) hide show
  1. lmnr/__init__.py +0 -8
  2. lmnr/openllmetry_sdk/__init__.py +5 -33
  3. lmnr/openllmetry_sdk/decorators/base.py +24 -17
  4. lmnr/openllmetry_sdk/instruments.py +1 -0
  5. lmnr/openllmetry_sdk/opentelemetry/instrumentation/google_genai/__init__.py +454 -0
  6. lmnr/openllmetry_sdk/opentelemetry/instrumentation/google_genai/config.py +9 -0
  7. lmnr/openllmetry_sdk/opentelemetry/instrumentation/google_genai/utils.py +216 -0
  8. lmnr/openllmetry_sdk/tracing/__init__.py +1 -0
  9. lmnr/openllmetry_sdk/tracing/context_manager.py +13 -0
  10. lmnr/openllmetry_sdk/tracing/tracing.py +230 -252
  11. lmnr/sdk/browser/playwright_otel.py +42 -58
  12. lmnr/sdk/browser/pw_utils.py +8 -40
  13. lmnr/sdk/client/asynchronous/async_client.py +0 -34
  14. lmnr/sdk/client/asynchronous/resources/__init__.py +0 -4
  15. lmnr/sdk/client/asynchronous/resources/agent.py +96 -6
  16. lmnr/sdk/client/synchronous/resources/__init__.py +1 -3
  17. lmnr/sdk/client/synchronous/resources/agent.py +94 -8
  18. lmnr/sdk/client/synchronous/sync_client.py +0 -36
  19. lmnr/sdk/decorators.py +16 -2
  20. lmnr/sdk/laminar.py +3 -3
  21. lmnr/sdk/types.py +84 -170
  22. lmnr/sdk/utils.py +8 -1
  23. lmnr/version.py +1 -1
  24. {lmnr-0.5.1a0.dist-info → lmnr-0.5.2.dist-info}/METADATA +57 -57
  25. lmnr-0.5.2.dist-info/RECORD +54 -0
  26. lmnr/sdk/client/asynchronous/resources/pipeline.py +0 -89
  27. lmnr/sdk/client/asynchronous/resources/semantic_search.py +0 -60
  28. lmnr/sdk/client/synchronous/resources/pipeline.py +0 -89
  29. lmnr/sdk/client/synchronous/resources/semantic_search.py +0 -60
  30. lmnr-0.5.1a0.dist-info/RECORD +0 -54
  31. {lmnr-0.5.1a0.dist-info → lmnr-0.5.2.dist-info}/LICENSE +0 -0
  32. {lmnr-0.5.1a0.dist-info → lmnr-0.5.2.dist-info}/WHEEL +0 -0
  33. {lmnr-0.5.1a0.dist-info → lmnr-0.5.2.dist-info}/entry_points.txt +0 -0
@@ -2,7 +2,9 @@ import logging
2
2
  import uuid
3
3
 
4
4
  from lmnr.sdk.browser.pw_utils import handle_navigation_async, handle_navigation_sync
5
- from lmnr.sdk.browser.utils import with_tracer_wrapper
5
+ from lmnr.sdk.browser.utils import with_tracer_and_client_wrapper
6
+ from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
7
+ from lmnr.sdk.client.synchronous.sync_client import LaminarClient
6
8
  from lmnr.version import __version__
7
9
 
8
10
  from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
@@ -16,11 +18,11 @@ from opentelemetry.trace import (
16
18
  set_span_in_context,
17
19
  )
18
20
  from opentelemetry.context import get_current
19
- from typing import Collection, Optional
21
+ from typing import Collection
20
22
  from wrapt import wrap_function_wrapper
21
23
 
22
24
  try:
23
- from playwright.async_api import Browser
25
+ from playwright.async_api import Browser, BrowserContext
24
26
  from playwright.sync_api import (
25
27
  Browser as SyncBrowser,
26
28
  BrowserContext as SyncBrowserContext,
@@ -37,12 +39,12 @@ _instruments = ("playwright >= 1.9.0",)
37
39
  logger = logging.getLogger(__name__)
38
40
 
39
41
  _context_spans: dict[str, Span] = {}
40
- _project_api_key: Optional[str] = None
41
- _base_http_url: Optional[str] = None
42
42
 
43
43
 
44
- @with_tracer_wrapper
45
- def _wrap_new_page(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
44
+ @with_tracer_and_client_wrapper
45
+ def _wrap_new_page(
46
+ tracer: Tracer, client: LaminarClient, to_wrap, wrapped, instance, args, kwargs
47
+ ):
46
48
  with tracer.start_as_current_span(
47
49
  f"{to_wrap.get('object')}.{to_wrap.get('method')}"
48
50
  ) as span:
@@ -50,15 +52,13 @@ def _wrap_new_page(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
50
52
  session_id = str(uuid.uuid4().hex)
51
53
  trace_id = format(get_current_span().get_span_context().trace_id, "032x")
52
54
  span.set_attribute("lmnr.internal.has_browser_session", True)
53
- handle_navigation_sync(
54
- page, session_id, trace_id, _project_api_key, _base_http_url
55
- )
55
+ handle_navigation_sync(page, session_id, trace_id, client)
56
56
  return page
57
57
 
58
58
 
59
- @with_tracer_wrapper
59
+ @with_tracer_and_client_wrapper
60
60
  async def _wrap_new_page_async(
61
- tracer: Tracer, to_wrap, wrapped, instance, args, kwargs
61
+ tracer: Tracer, client: AsyncLaminarClient, to_wrap, wrapped, instance, args, kwargs
62
62
  ):
63
63
  with tracer.start_as_current_span(
64
64
  f"{to_wrap.get('object')}.{to_wrap.get('method')}"
@@ -67,14 +67,14 @@ async def _wrap_new_page_async(
67
67
  session_id = str(uuid.uuid4().hex)
68
68
  trace_id = format(span.get_span_context().trace_id, "032x")
69
69
  span.set_attribute("lmnr.internal.has_browser_session", True)
70
- await handle_navigation_async(
71
- page, session_id, trace_id, _project_api_key, _base_http_url
72
- )
70
+ await handle_navigation_async(page, session_id, trace_id, client)
73
71
  return page
74
72
 
75
73
 
76
- @with_tracer_wrapper
77
- def _wrap_new_browser_sync(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
74
+ @with_tracer_and_client_wrapper
75
+ def _wrap_new_browser_sync(
76
+ tracer: Tracer, client: LaminarClient, to_wrap, wrapped, instance, args, kwargs
77
+ ):
78
78
  global _context_spans
79
79
  browser: SyncBrowser = wrapped(*args, **kwargs)
80
80
  session_id = str(uuid.uuid4().hex)
@@ -90,20 +90,16 @@ def _wrap_new_browser_sync(tracer: Tracer, to_wrap, wrapped, instance, args, kwa
90
90
  trace_id = format(span.get_span_context().trace_id, "032x")
91
91
  context.on(
92
92
  "page",
93
- lambda page: handle_navigation_sync(
94
- page, session_id, trace_id, _project_api_key, _base_http_url
95
- ),
93
+ lambda page: handle_navigation_sync(page, session_id, trace_id, client),
96
94
  )
97
95
  for page in context.pages:
98
- handle_navigation_sync(
99
- page, session_id, trace_id, _project_api_key, _base_http_url
100
- )
96
+ handle_navigation_sync(page, session_id, trace_id, client)
101
97
  return browser
102
98
 
103
99
 
104
- @with_tracer_wrapper
100
+ @with_tracer_and_client_wrapper
105
101
  async def _wrap_new_browser_async(
106
- tracer: Tracer, to_wrap, wrapped, instance, args, kwargs
102
+ tracer: Tracer, client: AsyncLaminarClient, to_wrap, wrapped, instance, args, kwargs
107
103
  ):
108
104
  global _context_spans
109
105
  browser: Browser = await wrapped(*args, **kwargs)
@@ -120,20 +116,18 @@ async def _wrap_new_browser_async(
120
116
  trace_id = format(span.get_span_context().trace_id, "032x")
121
117
 
122
118
  async def handle_page_navigation(page):
123
- return await handle_navigation_async(
124
- page, session_id, trace_id, _project_api_key, _base_http_url
125
- )
119
+ return await handle_navigation_async(page, session_id, trace_id, client)
126
120
 
127
121
  context.on("page", handle_page_navigation)
128
122
  for page in context.pages:
129
- await handle_navigation_async(
130
- page, session_id, trace_id, _project_api_key, _base_http_url
131
- )
123
+ await handle_navigation_async(page, session_id, trace_id, client)
132
124
  return browser
133
125
 
134
126
 
135
- @with_tracer_wrapper
136
- def _wrap_new_context_sync(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
127
+ @with_tracer_and_client_wrapper
128
+ def _wrap_new_context_sync(
129
+ tracer: Tracer, client: LaminarClient, to_wrap, wrapped, instance, args, kwargs
130
+ ):
137
131
  context: SyncBrowserContext = wrapped(*args, **kwargs)
138
132
  session_id = str(uuid.uuid4().hex)
139
133
  span = get_current_span()
@@ -148,20 +142,16 @@ def _wrap_new_context_sync(tracer: Tracer, to_wrap, wrapped, instance, args, kwa
148
142
 
149
143
  context.on(
150
144
  "page",
151
- lambda page: handle_navigation_sync(
152
- page, session_id, trace_id, _project_api_key, _base_http_url
153
- ),
145
+ lambda page: handle_navigation_sync(page, session_id, trace_id, client),
154
146
  )
155
147
  for page in context.pages:
156
- handle_navigation_sync(
157
- page, session_id, trace_id, _project_api_key, _base_http_url
158
- )
148
+ handle_navigation_sync(page, session_id, trace_id, client)
159
149
  return context
160
150
 
161
151
 
162
- @with_tracer_wrapper
152
+ @with_tracer_and_client_wrapper
163
153
  async def _wrap_new_context_async(
164
- tracer: Tracer, to_wrap, wrapped, instance, args, kwargs
154
+ tracer: Tracer, client: AsyncLaminarClient, to_wrap, wrapped, instance, args, kwargs
165
155
  ):
166
156
  context: SyncBrowserContext = await wrapped(*args, **kwargs)
167
157
  session_id = str(uuid.uuid4().hex)
@@ -176,21 +166,18 @@ async def _wrap_new_context_async(
176
166
  trace_id = format(span.get_span_context().trace_id, "032x")
177
167
 
178
168
  async def handle_page_navigation(page):
179
- return await handle_navigation_async(
180
- page, session_id, trace_id, _project_api_key, _base_http_url
181
- )
169
+ return await handle_navigation_async(page, session_id, trace_id, client)
182
170
 
183
171
  context.on("page", handle_page_navigation)
184
172
  for page in context.pages:
185
- await handle_navigation_async(
186
- page, session_id, trace_id, _project_api_key, _base_http_url
187
- )
173
+ await handle_navigation_async(page, session_id, trace_id, client)
188
174
  return context
189
175
 
190
176
 
191
- @with_tracer_wrapper
177
+ @with_tracer_and_client_wrapper
192
178
  def _wrap_close_browser_sync(
193
179
  tracer: Tracer,
180
+ client: LaminarClient,
194
181
  to_wrap,
195
182
  wrapped,
196
183
  instance: SyncBrowser,
@@ -208,9 +195,10 @@ def _wrap_close_browser_sync(
208
195
  return wrapped(*args, **kwargs)
209
196
 
210
197
 
211
- @with_tracer_wrapper
198
+ @with_tracer_and_client_wrapper
212
199
  async def _wrap_close_browser_async(
213
200
  tracer: Tracer,
201
+ client: AsyncLaminarClient,
214
202
  to_wrap,
215
203
  wrapped,
216
204
  instance: Browser,
@@ -332,24 +320,18 @@ WRAPPED_METHODS_ASYNC = [
332
320
 
333
321
 
334
322
  class PlaywrightInstrumentor(BaseInstrumentor):
335
- def __init__(self):
323
+ def __init__(self, client: LaminarClient, async_client: AsyncLaminarClient):
336
324
  super().__init__()
325
+ self.client = client
326
+ self.async_client = async_client
337
327
 
338
328
  def instrumentation_dependencies(self) -> Collection[str]:
339
329
  return _instruments
340
330
 
341
331
  def _instrument(self, **kwargs):
342
- global _project_api_key, _base_http_url
343
-
344
332
  tracer_provider = kwargs.get("tracer_provider")
345
333
  tracer = get_tracer(__name__, __version__, tracer_provider)
346
334
 
347
- if kwargs.get("project_api_key"):
348
- _project_api_key = kwargs.get("project_api_key")
349
-
350
- if kwargs.get("base_http_url"):
351
- _base_http_url = kwargs.get("base_http_url")
352
-
353
335
  for wrapped_method in WRAPPED_METHODS:
354
336
  wrap_package = wrapped_method.get("package")
355
337
  wrap_object = wrapped_method.get("object")
@@ -360,6 +342,7 @@ class PlaywrightInstrumentor(BaseInstrumentor):
360
342
  f"{wrap_object}.{wrap_method}",
361
343
  wrapped_method.get("wrapper")(
362
344
  tracer,
345
+ self.client,
363
346
  wrapped_method,
364
347
  ),
365
348
  )
@@ -377,6 +360,7 @@ class PlaywrightInstrumentor(BaseInstrumentor):
377
360
  f"{wrap_object}.{wrap_method}",
378
361
  wrapped_method.get("wrapper")(
379
362
  tracer,
363
+ self.async_client,
380
364
  wrapped_method,
381
365
  ),
382
366
  )
@@ -3,7 +3,6 @@ import logging
3
3
  import os
4
4
  import time
5
5
  import threading
6
- from typing import Optional
7
6
 
8
7
  from opentelemetry import trace
9
8
 
@@ -27,8 +26,6 @@ logger = logging.getLogger(__name__)
27
26
  # Track pages we've already instrumented to avoid double-instrumentation
28
27
  instrumented_pages = set()
29
28
  async_instrumented_pages = set()
30
- client: Optional[LaminarClient] = None
31
- async_client: Optional[AsyncLaminarClient] = None
32
29
 
33
30
 
34
31
  current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -223,21 +220,8 @@ async def inject_rrweb_async(page: Page):
223
220
 
224
221
  @observe(name="playwright.page", ignore_input=True, ignore_output=True)
225
222
  def handle_navigation_sync(
226
- page: SyncPage,
227
- session_id: str,
228
- trace_id: str,
229
- project_api_key: Optional[str] = None,
230
- base_http_url: Optional[str] = None,
223
+ page: SyncPage, session_id: str, trace_id: str, client: LaminarClient
231
224
  ):
232
- global client
233
- if client is None:
234
- client = LaminarClient(base_url=base_http_url, project_api_key=project_api_key)
235
- if (base_http_url is not None and base_http_url != client.base_url) or (
236
- project_api_key is not None and project_api_key != client.project_api_key
237
- ):
238
- if client is not None:
239
- client.close()
240
- client = LaminarClient(base_url=base_http_url, project_api_key=project_api_key)
241
225
  trace.get_current_span().set_attribute("lmnr.internal.has_browser_session", True)
242
226
  # Check if we've already instrumented this page
243
227
  page_id = id(page)
@@ -255,10 +239,11 @@ def handle_navigation_sync(
255
239
  inject_rrweb_sync(page)
256
240
 
257
241
  def collection_loop():
258
- while not page.is_closed():
242
+ while not page.is_closed(): # Stop when page closes
259
243
  send_events_sync(page, session_id, trace_id, client)
260
244
  time.sleep(2)
261
245
 
246
+ # Clean up when page closes
262
247
  if page_id in instrumented_pages:
263
248
  instrumented_pages.remove(page_id)
264
249
 
@@ -268,25 +253,8 @@ def handle_navigation_sync(
268
253
 
269
254
  @observe(name="playwright.page", ignore_input=True, ignore_output=True)
270
255
  async def handle_navigation_async(
271
- page: Page,
272
- session_id: str,
273
- trace_id: str,
274
- project_api_key: Optional[str] = None,
275
- base_http_url: Optional[str] = None,
256
+ page: Page, session_id: str, trace_id: str, client: AsyncLaminarClient
276
257
  ):
277
- global async_client
278
- if async_client is None:
279
- async_client = AsyncLaminarClient(
280
- base_url=base_http_url, project_api_key=project_api_key
281
- )
282
- if (base_http_url is not None and base_http_url != async_client.base_url) or (
283
- project_api_key is not None and project_api_key != async_client.project_api_key
284
- ):
285
- if async_client is not None:
286
- await async_client.close()
287
- async_client = AsyncLaminarClient(
288
- base_url=base_http_url, project_api_key=project_api_key
289
- )
290
258
  trace.get_current_span().set_attribute("lmnr.internal.has_browser_session", True)
291
259
  # Check if we've already instrumented this page
292
260
  page_id = id(page)
@@ -303,19 +271,19 @@ async def handle_navigation_async(
303
271
  page.on("load", lambda: asyncio.create_task(on_load()))
304
272
  await inject_rrweb_async(page)
305
273
 
306
- async def collection_loop(client: AsyncLaminarClient):
274
+ async def collection_loop():
307
275
  try:
308
- while not page.is_closed():
276
+ while not page.is_closed(): # Stop when page closes
309
277
  await send_events_async(page, session_id, trace_id, client)
310
278
  await asyncio.sleep(2)
311
-
279
+ # Clean up when page closes
312
280
  async_instrumented_pages.remove(page_id)
313
281
  logger.info("Event collection stopped")
314
282
  except Exception as e:
315
283
  logger.error(f"Event collection stopped: {e}")
316
284
 
317
285
  # Create and store task
318
- task = asyncio.create_task(collection_loop(async_client))
286
+ task = asyncio.create_task(collection_loop())
319
287
 
320
288
  # Clean up task when page closes
321
289
  page.on("close", lambda: task.cancel())
@@ -11,8 +11,6 @@ from lmnr.sdk.client.asynchronous.resources import (
11
11
  AsyncAgent,
12
12
  AsyncBrowserEvents,
13
13
  AsyncEvals,
14
- AsyncPipeline,
15
- AsyncSemanticSearch,
16
14
  )
17
15
  from lmnr.sdk.utils import from_env
18
16
 
@@ -66,12 +64,6 @@ class AsyncLaminarClient:
66
64
  )
67
65
 
68
66
  # 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
67
  self.__agent = AsyncAgent(
76
68
  self.__client, self.__base_url, self.__project_api_key
77
69
  )
@@ -82,24 +74,6 @@ class AsyncLaminarClient:
82
74
  self.__client, self.__base_url, self.__project_api_key
83
75
  )
84
76
 
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
77
  @property
104
78
  def agent(self) -> AsyncAgent:
105
79
  """Get the Agent resource.
@@ -137,14 +111,6 @@ class AsyncLaminarClient:
137
111
  """
138
112
  await self.__client.aclose()
139
113
 
140
- @property
141
- def base_url(self) -> str:
142
- return self.__base_url
143
-
144
- @property
145
- def project_api_key(self) -> str:
146
- return self.__project_api_key
147
-
148
114
  async def __aenter__(self: _T) -> _T:
149
115
  return self
150
116
 
@@ -1,12 +1,8 @@
1
1
  from lmnr.sdk.client.asynchronous.resources.agent import AsyncAgent
2
2
  from lmnr.sdk.client.asynchronous.resources.browser_events import AsyncBrowserEvents
3
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
4
 
7
5
  __all__ = [
8
- "AsyncPipeline",
9
- "AsyncSemanticSearch",
10
6
  "AsyncAgent",
11
7
  "AsyncEvals",
12
8
  "AsyncBrowserEvents",
@@ -35,7 +35,16 @@ class AsyncAgent(BaseAsyncResource):
35
35
  model_provider: Optional[ModelProvider] = None,
36
36
  model: Optional[str] = None,
37
37
  enable_thinking: bool = True,
38
+ agent_state: Optional[str] = None,
39
+ storage_state: Optional[str] = None,
38
40
  return_screenshots: bool = False,
41
+ return_agent_state: bool = False,
42
+ return_storage_state: bool = False,
43
+ timeout: Optional[int] = None,
44
+ cdp_url: Optional[str] = None,
45
+ max_steps: Optional[int] = None,
46
+ thinking_token_budget: Optional[int] = None,
47
+ start_url: Optional[str] = None,
39
48
  ) -> AsyncIterator[RunAgentResponseChunk]:
40
49
  """Run Laminar index agent in streaming mode.
41
50
 
@@ -46,7 +55,17 @@ class AsyncAgent(BaseAsyncResource):
46
55
  model_provider (Optional[ModelProvider], optional): LLM model provider
47
56
  model (Optional[str], optional): LLM model name
48
57
  enable_thinking (bool, optional): whether to enable thinking on the underlying LLM. Default to True.
58
+ agent_state (Optional[str], optional): the agent's state as returned by the previous agent run. Default to None.
59
+ storage_state (Optional[str], optional): the browser's storage state as returned by the previous agent run. Default to None.
49
60
  return_screenshots (bool, optional): whether to return screenshots of the agent's states at every step. Default to False.
61
+ return_agent_state (bool, optional): whether to return the agent's state in the final chunk. Default to False.
62
+ return_storage_state (bool, optional): whether to return the storage state in the final chunk. Default to False.
63
+ timeout (Optional[int], optional): timeout seconds for the agent's response. Default to None.
64
+ cdp_url (Optional[str], optional): Chrome DevTools Protocol URL of an existing browser session. Default to None.
65
+ max_steps (Optional[int], optional): maximum number of steps the agent can take. If not set, the backend will use a default value (currently 100). Default to None.
66
+ thinking_token_budget (Optional[int], optional): maximum number of tokens the underlying LLM can spend on thinking in each step, if supported by the model. Default to None.
67
+ start_url (Optional[str], optional): the URL to start the agent on. Must be a valid URL - refer to https://playwright.dev/docs/api/class-page#page-goto. If not specified, the agent infers this from the prompt. Default to None.
68
+
50
69
  Returns:
51
70
  AsyncIterator[RunAgentResponseChunk]: a generator of response chunks
52
71
  """
@@ -60,7 +79,16 @@ class AsyncAgent(BaseAsyncResource):
60
79
  model_provider: Optional[ModelProvider] = None,
61
80
  model: Optional[str] = None,
62
81
  enable_thinking: bool = True,
82
+ agent_state: Optional[str] = None,
83
+ storage_state: Optional[str] = None,
63
84
  return_screenshots: bool = False,
85
+ return_agent_state: bool = False,
86
+ return_storage_state: bool = False,
87
+ timeout: Optional[int] = None,
88
+ cdp_url: Optional[str] = None,
89
+ max_steps: Optional[int] = None,
90
+ thinking_token_budget: Optional[int] = None,
91
+ start_url: Optional[str] = None,
64
92
  ) -> AgentOutput:
65
93
  """Run Laminar index agent.
66
94
 
@@ -70,7 +98,17 @@ class AsyncAgent(BaseAsyncResource):
70
98
  model_provider (Optional[ModelProvider], optional): LLM model provider
71
99
  model (Optional[str], optional): LLM model name
72
100
  enable_thinking (bool, optional): whether to enable thinking on the underlying LLM. Default to True.
101
+ agent_state (Optional[str], optional): the agent's state as returned by the previous agent run. Default to None.
102
+ storage_state (Optional[str], optional): the browser's storage state as returned by the previous agent run. Default to None.
73
103
  return_screenshots (bool, optional): whether to return screenshots of the agent's states at every step. Default to False.
104
+ return_agent_state (bool, optional): whether to return the agent's state. Default to False.
105
+ return_storage_state (bool, optional): whether to return the storage state. Default to False.
106
+ timeout (Optional[int], optional): timeout seconds for the agent's response. Default to None.
107
+ cdp_url (Optional[str], optional): Chrome DevTools Protocol URL of an existing browser session. Default to None.
108
+ max_steps (Optional[int], optional): maximum number of steps the agent can take. If not set, the backend will use a default value (currently 100). Default to None.
109
+ thinking_token_budget (Optional[int], optional): maximum number of tokens the underlying LLM can spend on thinking in each step, if supported by the model. Default to None.
110
+ start_url (Optional[str], optional): the URL to start the agent on. Must be a valid URL - refer to https://playwright.dev/docs/api/class-page#page-goto. If not specified, the agent infers this from the prompt. Default to None.
111
+
74
112
  Returns:
75
113
  AgentOutput: agent output
76
114
  """
@@ -85,7 +123,15 @@ class AsyncAgent(BaseAsyncResource):
85
123
  model: Optional[str] = None,
86
124
  stream: Literal[False] = False,
87
125
  enable_thinking: bool = True,
126
+ agent_state: Optional[str] = None,
127
+ storage_state: Optional[str] = None,
88
128
  return_screenshots: bool = False,
129
+ return_agent_state: bool = False,
130
+ return_storage_state: bool = False,
131
+ timeout: Optional[int] = None,
132
+ max_steps: Optional[int] = None,
133
+ thinking_token_budget: Optional[int] = None,
134
+ start_url: Optional[str] = None,
89
135
  ) -> AgentOutput:
90
136
  """Run Laminar index agent.
91
137
 
@@ -96,7 +142,16 @@ class AsyncAgent(BaseAsyncResource):
96
142
  model (Optional[str], optional): LLM model name
97
143
  stream (Literal[False], optional): whether to stream the agent's response
98
144
  enable_thinking (bool, optional): whether to enable thinking on the underlying LLM. Default to True.
145
+ agent_state (Optional[str], optional): the agent's state as returned by the previous agent run. Default to None.
146
+ storage_state (Optional[str], optional): the browser's storage state as returned by the previous agent run. Default to None.
99
147
  return_screenshots (bool, optional): whether to return screenshots of the agent's states at every step. Default to False.
148
+ return_agent_state (bool, optional): whether to return the agent's state. Default to False.
149
+ return_storage_state (bool, optional): whether to return the storage state. Default to False.
150
+ timeout (Optional[int], optional): timeout seconds for the agent's response. Default to None.
151
+ cdp_url (Optional[str], optional): Chrome DevTools Protocol URL of an existing browser session. Default to None.
152
+ max_steps (Optional[int], optional): maximum number of steps the agent can take. If not set, the backend will use a default value (currently 100). Default to None.
153
+ thinking_token_budget (Optional[int], optional): maximum number of tokens the underlying LLM can spend on thinking in each step, if supported by the model. Default to None.
154
+ start_url (Optional[str], optional): the URL to start the agent on. Must be a valid URL - refer to https://playwright.dev/docs/api/class-page#page-goto. If not specified, the agent infers this from the prompt. Default to None.
100
155
  Returns:
101
156
  AgentOutput: agent output
102
157
  """
@@ -110,7 +165,16 @@ class AsyncAgent(BaseAsyncResource):
110
165
  model: Optional[str] = None,
111
166
  stream: bool = False,
112
167
  enable_thinking: bool = True,
168
+ agent_state: Optional[str] = None,
169
+ storage_state: Optional[str] = None,
113
170
  return_screenshots: bool = False,
171
+ return_agent_state: bool = False,
172
+ return_storage_state: bool = False,
173
+ timeout: Optional[int] = None,
174
+ cdp_url: Optional[str] = None,
175
+ max_steps: Optional[int] = None,
176
+ thinking_token_budget: Optional[int] = None,
177
+ start_url: Optional[str] = None,
114
178
  ) -> Union[AgentOutput, Awaitable[AsyncIterator[RunAgentResponseChunk]]]:
115
179
  """Run Laminar index agent.
116
180
 
@@ -121,7 +185,17 @@ class AsyncAgent(BaseAsyncResource):
121
185
  model (Optional[str], optional): LLM model name
122
186
  stream (bool, optional): whether to stream the agent's response
123
187
  enable_thinking (bool, optional): whether to enable thinking on the underlying LLM. Default to True.
188
+ agent_state (Optional[str], optional): the agent's state as returned by the previous agent run. Default to None.
189
+ storage_state (Optional[str], optional): the browser's storage state as returned by the previous agent run. Default to None.
124
190
  return_screenshots (bool, optional): whether to return screenshots of the agent's states at every step. Default to False.
191
+ return_agent_state (bool, optional): whether to return the agent's state. Default to False.
192
+ return_storage_state (bool, optional): whether to return the storage state. Default to False.
193
+ timeout (Optional[int], optional): timeout seconds for the agent's response. Default to None.
194
+ cdp_url (Optional[str], optional): Chrome DevTools Protocol URL of an existing browser session. Default to None.
195
+ max_steps (Optional[int], optional): maximum number of steps the agent can take. If not set, the backend will use a default value (currently 100). Default to None.
196
+ thinking_token_budget (Optional[int], optional): maximum number of tokens the underlying LLM can spend on thinking in each step, if supported by the model. Default to None.
197
+ start_url (Optional[str], optional): the URL to start the agent on. Must be a valid URL - refer to https://playwright.dev/docs/api/class-page#page-goto. If not specified, the agent infers this from the prompt. Default to None.
198
+
125
199
  Returns:
126
200
  Union[AgentOutput, AsyncIterator[RunAgentResponseChunk]]: agent output or a generator of response chunks
127
201
  """
@@ -142,15 +216,23 @@ class AsyncAgent(BaseAsyncResource):
142
216
  parent_span_context=parent_span_context,
143
217
  model_provider=model_provider,
144
218
  model=model,
145
- # We always connect to stream, because our TLS listeners on AWS
146
- # Network load balancers have a hard fixed idle timeout of 350 seconds.
219
+ agent_state=agent_state,
220
+ storage_state=storage_state,
221
+ # We always connect to stream, because our network configuration
222
+ # has a hard fixed idle timeout of 350 seconds.
147
223
  # This means that if we don't stream, the connection will be closed.
148
224
  # For now, we just return the content of the final chunk if `stream` is
149
225
  # `False`.
150
- # https://aws.amazon.com/blogs/networking-and-content-delivery/introducing-nlb-tcp-configurable-idle-timeout/
151
226
  stream=True,
152
227
  enable_thinking=enable_thinking,
153
228
  return_screenshots=return_screenshots,
229
+ return_agent_state=return_agent_state,
230
+ return_storage_state=return_storage_state,
231
+ timeout=timeout,
232
+ cdp_url=cdp_url,
233
+ max_steps=max_steps,
234
+ thinking_token_budget=thinking_token_budget,
235
+ start_url=start_url,
154
236
  )
155
237
 
156
238
  # For streaming case, use a generator function
@@ -174,7 +256,7 @@ class AsyncAgent(BaseAsyncResource):
174
256
  async with self._client.stream(
175
257
  "POST",
176
258
  self._base_url + "/v1/agent/run",
177
- json=request.to_dict(),
259
+ json=request.model_dump(by_alias=True),
178
260
  headers=self._headers(),
179
261
  ) as response:
180
262
  async for line in response.aiter_lines():
@@ -187,6 +269,8 @@ class AsyncAgent(BaseAsyncResource):
187
269
  if line:
188
270
  chunk = RunAgentResponseChunk.model_validate_json(line)
189
271
  yield chunk.root
272
+ if chunk.root.chunk_type in ["finalOutput", "error"]:
273
+ break
190
274
 
191
275
  async def __run_non_streaming(self, request: RunAgentRequest) -> AgentOutput:
192
276
  """Run agent in non-streaming mode.
@@ -202,9 +286,11 @@ class AsyncAgent(BaseAsyncResource):
202
286
  async with self._client.stream(
203
287
  "POST",
204
288
  self._base_url + "/v1/agent/run",
205
- json=request.to_dict(),
289
+ json=request.model_dump(by_alias=True),
206
290
  headers=self._headers(),
207
291
  ) as response:
292
+ if response.status_code != 200:
293
+ raise RuntimeError(await response.read())
208
294
  async for line in response.aiter_lines():
209
295
  line = str(line)
210
296
  if line.startswith("[DONE]"):
@@ -214,7 +300,11 @@ class AsyncAgent(BaseAsyncResource):
214
300
  line = line[6:]
215
301
  if line:
216
302
  chunk = RunAgentResponseChunk.model_validate_json(line)
217
- if chunk.root.chunkType == "finalOutput":
303
+ if chunk.root.chunk_type == "finalOutput":
218
304
  final_chunk = chunk.root
305
+ elif chunk.root.chunk_type == "error":
306
+ raise RuntimeError(chunk.root.error)
307
+ elif chunk.root.chunk_type == "timeout":
308
+ raise TimeoutError("Agent timed out")
219
309
 
220
310
  return final_chunk.content if final_chunk is not None else AgentOutput()
@@ -1,7 +1,5 @@
1
1
  from lmnr.sdk.client.synchronous.resources.agent import Agent
2
2
  from lmnr.sdk.client.synchronous.resources.browser_events import BrowserEvents
3
3
  from lmnr.sdk.client.synchronous.resources.evals import Evals
4
- from lmnr.sdk.client.synchronous.resources.pipeline import Pipeline
5
- from lmnr.sdk.client.synchronous.resources.semantic_search import SemanticSearch
6
4
 
7
- __all__ = ["Pipeline", "SemanticSearch", "Agent", "Evals", "BrowserEvents"]
5
+ __all__ = ["Agent", "Evals", "BrowserEvents"]