agent-lab-sdk 0.1.42.1__tar.gz → 0.1.44__tar.gz
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.
Potentially problematic release.
This version of agent-lab-sdk might be problematic. Click here for more details.
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/PKG-INFO +1 -1
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/langgraph/checkpoint/agw_saver.py +87 -28
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk.egg-info/PKG-INFO +1 -1
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/pyproject.toml +1 -1
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/LICENSE +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/README.md +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/__init__.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/langgraph/checkpoint/__init__.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/langgraph/checkpoint/serde.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/llm/__init__.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/llm/agw_token_manager.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/llm/gigachat_token_manager.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/llm/llm.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/llm/throttled.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/metrics/__init__.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/metrics/metrics.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/schema/__init__.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/schema/input_types.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/schema/log_message.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/storage/__init__.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/storage/storage.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/storage/storage_v2.py +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk.egg-info/SOURCES.txt +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk.egg-info/dependency_links.txt +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk.egg-info/requires.txt +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk.egg-info/top_level.txt +0 -0
- {agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/setup.cfg +0 -0
{agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/langgraph/checkpoint/agw_saver.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import threading
|
|
4
5
|
import base64
|
|
5
6
|
import logging
|
|
6
7
|
import os
|
|
@@ -102,7 +103,10 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
102
103
|
super().__init__(serde=serde)
|
|
103
104
|
self.base_url = base_url.rstrip("/")
|
|
104
105
|
self.timeout = timeout
|
|
105
|
-
|
|
106
|
+
# Фоновый loop для sync-обёрток
|
|
107
|
+
self._bg_loop: asyncio.AbstractEventLoop | None = None
|
|
108
|
+
self._bg_thread: threading.Thread | None = None
|
|
109
|
+
self._loop_lock = threading.Lock()
|
|
106
110
|
|
|
107
111
|
raw_attempts = os.getenv("AGW_HTTP_MAX_RETRIES")
|
|
108
112
|
if raw_attempts is None:
|
|
@@ -175,6 +179,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
175
179
|
|
|
176
180
|
self._verify = verify
|
|
177
181
|
self._client: httpx.AsyncClient | None = None
|
|
182
|
+
self._client_loop: asyncio.AbstractEventLoop | None = None
|
|
178
183
|
|
|
179
184
|
def _create_client(self) -> httpx.AsyncClient:
|
|
180
185
|
return httpx.AsyncClient(
|
|
@@ -185,14 +190,23 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
185
190
|
trust_env=True,
|
|
186
191
|
)
|
|
187
192
|
|
|
188
|
-
def
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
def _ensure_bg_loop(self) -> asyncio.AbstractEventLoop:
|
|
194
|
+
with self._loop_lock:
|
|
195
|
+
if self._bg_loop and self._bg_loop.is_running():
|
|
196
|
+
return self._bg_loop
|
|
197
|
+
|
|
198
|
+
loop = asyncio.new_event_loop()
|
|
199
|
+
|
|
200
|
+
def runner():
|
|
201
|
+
asyncio.set_event_loop(loop)
|
|
202
|
+
loop.run_forever()
|
|
203
|
+
|
|
204
|
+
t = threading.Thread(target=runner, name="agw-checkpoint-loop", daemon=True)
|
|
205
|
+
t.start()
|
|
206
|
+
|
|
207
|
+
self._bg_loop = loop
|
|
208
|
+
self._bg_thread = t
|
|
209
|
+
return loop
|
|
196
210
|
|
|
197
211
|
def _compute_retry_delay(self, attempt: int) -> float:
|
|
198
212
|
if attempt <= 0:
|
|
@@ -218,6 +232,14 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
218
232
|
logger.debug("Failed to close AGW httpx.AsyncClient: %s", close_exc)
|
|
219
233
|
finally:
|
|
220
234
|
self._client = None
|
|
235
|
+
self._client_loop = None
|
|
236
|
+
# останавливаем фоновый loop, если поднимали
|
|
237
|
+
if self._bg_loop is not None:
|
|
238
|
+
try:
|
|
239
|
+
self._bg_loop.call_soon_threadsafe(self._bg_loop.stop)
|
|
240
|
+
finally:
|
|
241
|
+
self._bg_loop = None
|
|
242
|
+
self._bg_thread = None
|
|
221
243
|
|
|
222
244
|
# ----------------------- universal dump/load ---------------------
|
|
223
245
|
def _safe_dump(self, obj: Any) -> Any:
|
|
@@ -528,6 +550,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
528
550
|
path: str,
|
|
529
551
|
*,
|
|
530
552
|
ok_statuses: Iterable[int] | None = None,
|
|
553
|
+
label_path: str | None = None,
|
|
531
554
|
**kw,
|
|
532
555
|
) -> httpx.Response:
|
|
533
556
|
if "json" in kw:
|
|
@@ -536,14 +559,26 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
536
559
|
logger.debug("AGW HTTP payload: %s", kw["data"].decode())
|
|
537
560
|
|
|
538
561
|
ok_set = set(ok_statuses) if ok_statuses is not None else set()
|
|
562
|
+
metric_path = label_path or path
|
|
539
563
|
|
|
540
564
|
attempt = 1
|
|
541
565
|
while True:
|
|
542
|
-
|
|
566
|
+
# клиент должен принадлежать текущему loop
|
|
567
|
+
current_loop = asyncio.get_running_loop()
|
|
568
|
+
client = self._client
|
|
569
|
+
if client is None or client.is_closed or self._client_loop is not current_loop:
|
|
570
|
+
if client is not None:
|
|
571
|
+
try:
|
|
572
|
+
await client.aclose()
|
|
573
|
+
except Exception as e:
|
|
574
|
+
logger.exception("ошибка при закрытии клиента", e)
|
|
575
|
+
client = self._create_client()
|
|
576
|
+
self._client = client
|
|
577
|
+
self._client_loop = current_loop
|
|
543
578
|
try:
|
|
544
579
|
resp = await client.request(method, path, **kw)
|
|
545
580
|
except httpx.RequestError as exc:
|
|
546
|
-
AGW_HTTP_ERROR.labels(method,
|
|
581
|
+
AGW_HTTP_ERROR.labels(method, metric_path).inc()
|
|
547
582
|
logger.warning(
|
|
548
583
|
"AGW request %s %s failed on attempt %d/%d: %s",
|
|
549
584
|
method,
|
|
@@ -553,7 +588,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
553
588
|
exc,
|
|
554
589
|
)
|
|
555
590
|
if attempt >= self.retry_max_attempts:
|
|
556
|
-
AGW_HTTP_FINAL_ERROR.labels(method,
|
|
591
|
+
AGW_HTTP_FINAL_ERROR.labels(method, metric_path).inc()
|
|
557
592
|
if self._client is not None:
|
|
558
593
|
try:
|
|
559
594
|
await self._client.aclose()
|
|
@@ -564,6 +599,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
564
599
|
)
|
|
565
600
|
finally:
|
|
566
601
|
self._client = None
|
|
602
|
+
self._client_loop = None
|
|
567
603
|
raise
|
|
568
604
|
|
|
569
605
|
if self._client is not None:
|
|
@@ -576,6 +612,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
576
612
|
)
|
|
577
613
|
finally:
|
|
578
614
|
self._client = None
|
|
615
|
+
self._client_loop = None
|
|
579
616
|
delay = self._compute_retry_delay(attempt)
|
|
580
617
|
if delay > 0:
|
|
581
618
|
await asyncio.sleep(delay)
|
|
@@ -584,16 +621,16 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
584
621
|
|
|
585
622
|
status = resp.status_code
|
|
586
623
|
if status < 400 or status in ok_set:
|
|
587
|
-
AGW_HTTP_SUCCESS.labels(method,
|
|
624
|
+
AGW_HTTP_SUCCESS.labels(method, metric_path).inc()
|
|
588
625
|
return resp
|
|
589
626
|
|
|
590
|
-
AGW_HTTP_ERROR.labels(method,
|
|
627
|
+
AGW_HTTP_ERROR.labels(method, metric_path).inc()
|
|
591
628
|
if status in (404, 406):
|
|
592
|
-
AGW_HTTP_FINAL_ERROR.labels(method,
|
|
629
|
+
AGW_HTTP_FINAL_ERROR.labels(method, metric_path).inc()
|
|
593
630
|
return resp
|
|
594
631
|
|
|
595
632
|
if attempt >= self.retry_max_attempts:
|
|
596
|
-
AGW_HTTP_FINAL_ERROR.labels(method,
|
|
633
|
+
AGW_HTTP_FINAL_ERROR.labels(method, metric_path).inc()
|
|
597
634
|
return resp
|
|
598
635
|
|
|
599
636
|
try:
|
|
@@ -611,6 +648,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
611
648
|
)
|
|
612
649
|
finally:
|
|
613
650
|
self._client = None
|
|
651
|
+
self._client_loop = None
|
|
614
652
|
delay = self._compute_retry_delay(attempt)
|
|
615
653
|
if delay > 0:
|
|
616
654
|
await asyncio.sleep(delay)
|
|
@@ -657,13 +695,15 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
657
695
|
tid = api_cfg["threadId"]
|
|
658
696
|
|
|
659
697
|
if cid:
|
|
698
|
+
path_template = "/checkpoint/{threadId}/{checkpointId}"
|
|
660
699
|
path = f"/checkpoint/{tid}/{cid}"
|
|
661
700
|
params = {"checkpointNs": api_cfg.get("checkpointNs", "")}
|
|
662
701
|
else:
|
|
702
|
+
path_template = "/checkpoint/{threadId}"
|
|
663
703
|
path = f"/checkpoint/{tid}"
|
|
664
704
|
params = None
|
|
665
705
|
|
|
666
|
-
resp = await self._http("GET", path, params=params)
|
|
706
|
+
resp = await self._http("GET", path, params=params, label_path=path_template)
|
|
667
707
|
logger.debug("AGW aget_tuple response: %s", resp.text)
|
|
668
708
|
|
|
669
709
|
if not resp.text:
|
|
@@ -687,7 +727,12 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
687
727
|
"before": self._to_api_config(before) if before else None,
|
|
688
728
|
"limit": limit,
|
|
689
729
|
}
|
|
690
|
-
resp = await self._http(
|
|
730
|
+
resp = await self._http(
|
|
731
|
+
"POST",
|
|
732
|
+
"/checkpoint/list",
|
|
733
|
+
json=payload,
|
|
734
|
+
label_path="/checkpoint/list",
|
|
735
|
+
)
|
|
691
736
|
logger.debug("AGW alist response: %s", resp.text)
|
|
692
737
|
resp.raise_for_status()
|
|
693
738
|
for item in resp.json():
|
|
@@ -706,7 +751,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
706
751
|
"metadata": self._enc_meta(get_checkpoint_metadata(cfg, metadata)),
|
|
707
752
|
"newVersions": new_versions,
|
|
708
753
|
}
|
|
709
|
-
resp = await self._http("POST", "/checkpoint", json=payload)
|
|
754
|
+
resp = await self._http("POST", "/checkpoint", json=payload, label_path="/checkpoint")
|
|
710
755
|
logger.debug("AGW aput response: %s", resp.text)
|
|
711
756
|
resp.raise_for_status()
|
|
712
757
|
return resp.json()["config"]
|
|
@@ -725,19 +770,31 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
725
770
|
"taskId": task_id,
|
|
726
771
|
"taskPath": task_path,
|
|
727
772
|
}
|
|
728
|
-
resp = await self._http(
|
|
773
|
+
resp = await self._http(
|
|
774
|
+
"POST",
|
|
775
|
+
"/checkpoint/writes",
|
|
776
|
+
json=payload,
|
|
777
|
+
label_path="/checkpoint/writes",
|
|
778
|
+
)
|
|
729
779
|
logger.debug("AGW aput_writes response: %s", resp.text)
|
|
730
780
|
resp.raise_for_status()
|
|
731
781
|
|
|
732
782
|
async def adelete_thread(self, thread_id: str) -> None:
|
|
733
|
-
resp = await self._http(
|
|
783
|
+
resp = await self._http(
|
|
784
|
+
"DELETE",
|
|
785
|
+
f"/checkpoint/{thread_id}",
|
|
786
|
+
label_path="/checkpoint/{threadId}",
|
|
787
|
+
)
|
|
734
788
|
resp.raise_for_status()
|
|
735
789
|
|
|
736
790
|
# =================================================================
|
|
737
791
|
# sync-обёртки
|
|
738
792
|
# =================================================================
|
|
739
793
|
def _run(self, coro):
|
|
740
|
-
|
|
794
|
+
# sync-обёртки всегда выполняем в собственном loop в отдельном потоке
|
|
795
|
+
loop = self._ensure_bg_loop()
|
|
796
|
+
fut = asyncio.run_coroutine_threadsafe(coro, loop)
|
|
797
|
+
return fut.result()
|
|
741
798
|
|
|
742
799
|
def list(
|
|
743
800
|
self,
|
|
@@ -747,12 +804,14 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
747
804
|
before: RunnableConfig | None = None,
|
|
748
805
|
limit: int | None = None,
|
|
749
806
|
) -> Iterator[CheckpointTuple]:
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
807
|
+
async def _collect():
|
|
808
|
+
out = []
|
|
809
|
+
async for item in self.alist(cfg, filter=filter, before=before, limit=limit):
|
|
810
|
+
out.append(item)
|
|
811
|
+
return out
|
|
812
|
+
|
|
813
|
+
for item in self._run(_collect()):
|
|
814
|
+
yield item
|
|
756
815
|
|
|
757
816
|
def get_tuple(self, cfg: RunnableConfig) -> CheckpointTuple | None:
|
|
758
817
|
return self._run(self.aget_tuple(cfg))
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agent-lab-sdk"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.44"
|
|
8
8
|
description = "SDK для работы с Agent Lab"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Proprietary and Confidential — All Rights Reserved" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_lab_sdk-0.1.42.1 → agent_lab_sdk-0.1.44}/agent_lab_sdk/langgraph/checkpoint/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|