agent-lab-sdk 0.1.41__py3-none-any.whl → 0.1.42__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.
Potentially problematic release.
This version of agent-lab-sdk might be problematic. Click here for more details.
- agent_lab_sdk/langgraph/checkpoint/agw_saver.py +128 -19
- {agent_lab_sdk-0.1.41.dist-info → agent_lab_sdk-0.1.42.dist-info}/METADATA +1 -1
- {agent_lab_sdk-0.1.41.dist-info → agent_lab_sdk-0.1.42.dist-info}/RECORD +6 -6
- {agent_lab_sdk-0.1.41.dist-info → agent_lab_sdk-0.1.42.dist-info}/WHEEL +0 -0
- {agent_lab_sdk-0.1.41.dist-info → agent_lab_sdk-0.1.42.dist-info}/licenses/LICENSE +0 -0
- {agent_lab_sdk-0.1.41.dist-info → agent_lab_sdk-0.1.42.dist-info}/top_level.txt +0 -0
|
@@ -317,6 +317,117 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
317
317
|
except Exception:
|
|
318
318
|
return obj
|
|
319
319
|
|
|
320
|
+
# ----------------------- deep dump/load (leaf-first) -------------
|
|
321
|
+
@staticmethod
|
|
322
|
+
def _is_json_scalar(x: Any) -> bool:
|
|
323
|
+
return x is None or isinstance(x, (str, int, float, bool))
|
|
324
|
+
|
|
325
|
+
@staticmethod
|
|
326
|
+
def _coerce_key(k: Any) -> str:
|
|
327
|
+
return k if isinstance(k, str) else str(k)
|
|
328
|
+
|
|
329
|
+
def _safe_dump_deep(self, obj: Any, _seen: set[int] | None = None) -> Any:
|
|
330
|
+
"""
|
|
331
|
+
Идём от листьев к корню:
|
|
332
|
+
- Для контейнеров рекурсируем внутрь и сохраняем форму контейнера.
|
|
333
|
+
- Для листьев вызываем _safe_dump (fallback на serde/typed + base64).
|
|
334
|
+
"""
|
|
335
|
+
if _seen is None:
|
|
336
|
+
_seen = set()
|
|
337
|
+
|
|
338
|
+
if self._is_json_scalar(obj):
|
|
339
|
+
return obj
|
|
340
|
+
|
|
341
|
+
if isinstance(obj, dict):
|
|
342
|
+
oid = id(obj)
|
|
343
|
+
if oid in _seen:
|
|
344
|
+
return {"type": "Cycle", "blob": None}
|
|
345
|
+
_seen.add(oid)
|
|
346
|
+
return {
|
|
347
|
+
self._coerce_key(k): self._safe_dump_deep(v, _seen) for k, v in obj.items()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if isinstance(obj, (list, tuple, set)):
|
|
351
|
+
oid = id(obj)
|
|
352
|
+
if oid in _seen:
|
|
353
|
+
return ["<cycle>"]
|
|
354
|
+
_seen.add(oid)
|
|
355
|
+
return [self._safe_dump_deep(v, _seen) for v in obj]
|
|
356
|
+
|
|
357
|
+
# лист: доверяем универсальному дамперу
|
|
358
|
+
return self._safe_dump(obj)
|
|
359
|
+
|
|
360
|
+
def _safe_load_deep(self, obj: Any, _seen: set[int] | None = None) -> Any:
|
|
361
|
+
"""
|
|
362
|
+
Обратная операция:
|
|
363
|
+
- Контейнеры сначала пробуем целиком скормить serde.loads(...).
|
|
364
|
+
Если вернулся НЕ JSON-контейнер (например, объект сообщения) — возвращаем его.
|
|
365
|
+
Иначе рекурсивно обходим внутрь и листья скармливаем _safe_load.
|
|
366
|
+
- typed {"type","blob"} обрабатываем как раньше.
|
|
367
|
+
"""
|
|
368
|
+
if _seen is None:
|
|
369
|
+
_seen = set()
|
|
370
|
+
|
|
371
|
+
# Примитивы: просто через _safe_load (декод base64/bytes и т.п.)
|
|
372
|
+
if self._is_json_scalar(obj):
|
|
373
|
+
return self._safe_load(obj)
|
|
374
|
+
|
|
375
|
+
# dict
|
|
376
|
+
if isinstance(obj, dict):
|
|
377
|
+
# типизированная обёртка — сразу разворачиваем
|
|
378
|
+
if all(k in obj for k in TYPED_KEYS):
|
|
379
|
+
return self._safe_load(obj)
|
|
380
|
+
|
|
381
|
+
# 1) parse-first: пробуем целиком восстановить объект через serde
|
|
382
|
+
try:
|
|
383
|
+
parsed = self.serde.loads(orjson.dumps(obj))
|
|
384
|
+
# если получили не-JSON-контейнер (объект), возвращаем
|
|
385
|
+
if not isinstance(parsed, (dict, list, tuple, str, int, float, bool, type(None))):
|
|
386
|
+
return parsed
|
|
387
|
+
except Exception:
|
|
388
|
+
pass
|
|
389
|
+
|
|
390
|
+
# 2) иначе — рекурсивно
|
|
391
|
+
oid = id(obj)
|
|
392
|
+
if oid in _seen:
|
|
393
|
+
return obj
|
|
394
|
+
_seen.add(oid)
|
|
395
|
+
return {k: self._safe_load_deep(v, _seen) for k, v in obj.items()}
|
|
396
|
+
|
|
397
|
+
# list
|
|
398
|
+
if isinstance(obj, list):
|
|
399
|
+
# parse-first: пытаемся восстановить весь список одной операцией
|
|
400
|
+
try:
|
|
401
|
+
parsed = self.serde.loads(orjson.dumps(obj))
|
|
402
|
+
if not isinstance(parsed, (dict, list, tuple, str, int, float, bool, type(None))):
|
|
403
|
+
return parsed
|
|
404
|
+
except Exception:
|
|
405
|
+
pass
|
|
406
|
+
|
|
407
|
+
oid = id(obj)
|
|
408
|
+
if oid in _seen:
|
|
409
|
+
return obj
|
|
410
|
+
_seen.add(oid)
|
|
411
|
+
return [self._safe_load_deep(v, _seen) for v in obj]
|
|
412
|
+
|
|
413
|
+
# tuple — аналогично list, но вернём list (JSON-совместимо)
|
|
414
|
+
if isinstance(obj, tuple):
|
|
415
|
+
try:
|
|
416
|
+
parsed = self.serde.loads(orjson.dumps(obj))
|
|
417
|
+
if not isinstance(parsed, (dict, list, tuple, str, int, float, bool, type(None))):
|
|
418
|
+
return parsed
|
|
419
|
+
except Exception:
|
|
420
|
+
pass
|
|
421
|
+
|
|
422
|
+
oid = id(obj)
|
|
423
|
+
if oid in _seen:
|
|
424
|
+
return obj
|
|
425
|
+
_seen.add(oid)
|
|
426
|
+
return [self._safe_load_deep(v, _seen) for v in obj]
|
|
427
|
+
|
|
428
|
+
# Всё остальное — лист, через _safe_load
|
|
429
|
+
return self._safe_load(obj)
|
|
430
|
+
|
|
320
431
|
# ----------------------- config <-> api --------------------------
|
|
321
432
|
def _to_api_config(self, cfg: RunnableConfig | None) -> Dict[str, Any]:
|
|
322
433
|
if not cfg:
|
|
@@ -334,25 +445,21 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
334
445
|
|
|
335
446
|
# --------------------- checkpoint (de)ser ------------------------
|
|
336
447
|
def _encode_cp(self, cp: Checkpoint) -> Dict[str, Any]:
|
|
337
|
-
pending: list[Any] = []
|
|
338
|
-
for item in cp.get("pending_sends", []) or []:
|
|
339
|
-
try:
|
|
340
|
-
channel, value = item
|
|
341
|
-
except Exception:
|
|
342
|
-
pending.append(item)
|
|
343
|
-
continue
|
|
344
|
-
pending.append([channel, self._safe_dump(value)])
|
|
345
448
|
return {
|
|
346
449
|
"v": cp["v"],
|
|
347
450
|
"id": cp["id"],
|
|
348
451
|
"ts": cp["ts"],
|
|
349
|
-
"channelValues": {
|
|
452
|
+
"channelValues": {
|
|
453
|
+
k: self._safe_dump_deep(v) for k, v in cp["channel_values"].items()
|
|
454
|
+
},
|
|
350
455
|
"channelVersions": cp["channel_versions"],
|
|
351
456
|
"versionsSeen": cp["versions_seen"],
|
|
352
|
-
"pendingSends":
|
|
457
|
+
"pendingSends": [] # как в BasePostgresSaver, они внутри checkpoint не нужны
|
|
353
458
|
}
|
|
354
459
|
|
|
355
460
|
def _decode_cp(self, raw: Dict[str, Any]) -> Checkpoint:
|
|
461
|
+
# Поддерживаем приём pendingSends (если сервер их отдаёт),
|
|
462
|
+
# но сами их не шлём при записи.
|
|
356
463
|
pending_sends: list[Tuple[str, Any]] = []
|
|
357
464
|
for obj in raw.get("pendingSends", []) or []:
|
|
358
465
|
if isinstance(obj, dict) and "channel" in obj:
|
|
@@ -360,18 +467,20 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
360
467
|
value_payload: Any = obj.get("value")
|
|
361
468
|
if value_payload is None and all(k in obj for k in TYPED_KEYS):
|
|
362
469
|
value_payload = {k: obj[k] for k in TYPED_KEYS}
|
|
363
|
-
pending_sends.append((channel, self.
|
|
470
|
+
pending_sends.append((channel, self._safe_load_deep(value_payload)))
|
|
364
471
|
elif isinstance(obj, (list, tuple)) and len(obj) >= 2:
|
|
365
472
|
channel = obj[0]
|
|
366
473
|
value_payload = obj[1]
|
|
367
|
-
pending_sends.append((channel, self.
|
|
474
|
+
pending_sends.append((channel, self._safe_load_deep(value_payload)))
|
|
368
475
|
else:
|
|
369
|
-
pending_sends.append(obj)
|
|
476
|
+
pending_sends.append(obj)
|
|
370
477
|
return Checkpoint(
|
|
371
478
|
v=raw["v"],
|
|
372
479
|
id=raw["id"],
|
|
373
480
|
ts=raw["ts"],
|
|
374
|
-
channel_values={
|
|
481
|
+
channel_values={
|
|
482
|
+
k: self._safe_load_deep(v) for k, v in raw["channelValues"].items()
|
|
483
|
+
},
|
|
375
484
|
channel_versions=raw["channelVersions"],
|
|
376
485
|
versions_seen=raw["versionsSeen"],
|
|
377
486
|
pending_sends=pending_sends,
|
|
@@ -404,13 +513,13 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
404
513
|
return {}
|
|
405
514
|
out: CheckpointMetadata = {}
|
|
406
515
|
for k, v in md.items():
|
|
407
|
-
out[k] = self._enc_meta(v) if isinstance(v, dict) else self.
|
|
516
|
+
out[k] = self._enc_meta(v) if isinstance(v, dict) else self._safe_dump_deep(v) # type: ignore[assignment]
|
|
408
517
|
return out
|
|
409
518
|
|
|
410
519
|
def _dec_meta(self, md: Any) -> Any:
|
|
411
520
|
if isinstance(md, dict):
|
|
412
521
|
return {k: self._dec_meta(v) for k, v in md.items()}
|
|
413
|
-
return self.
|
|
522
|
+
return self._safe_load_deep(md)
|
|
414
523
|
|
|
415
524
|
# ------------------------ HTTP wrapper ---------------------------
|
|
416
525
|
async def _http(
|
|
@@ -521,7 +630,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
521
630
|
k in second for k in TYPED_KEYS
|
|
522
631
|
):
|
|
523
632
|
third = second
|
|
524
|
-
pending.append((first, second, self.
|
|
633
|
+
pending.append((first, second, self._safe_load_deep(third)))
|
|
525
634
|
elif isinstance(w, (list, tuple)):
|
|
526
635
|
if len(w) == 3:
|
|
527
636
|
first, second, third = w
|
|
@@ -530,7 +639,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
530
639
|
third = None
|
|
531
640
|
else:
|
|
532
641
|
continue
|
|
533
|
-
pending.append((first, second, self.
|
|
642
|
+
pending.append((first, second, self._safe_load_deep(third)))
|
|
534
643
|
return CheckpointTuple(
|
|
535
644
|
config=self._decode_config(node.get("config")),
|
|
536
645
|
checkpoint=self._decode_cp(node["checkpoint"]),
|
|
@@ -609,7 +718,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
609
718
|
task_id: str,
|
|
610
719
|
task_path: str = "",
|
|
611
720
|
) -> None:
|
|
612
|
-
enc = [{"first": ch, "second": self.
|
|
721
|
+
enc = [{"first": ch, "second": self._safe_dump_deep(v)} for ch, v in writes]
|
|
613
722
|
payload = {
|
|
614
723
|
"config": self._to_api_config(cfg),
|
|
615
724
|
"writes": enc,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
agent_lab_sdk/__init__.py,sha256=1Dlmv-wuz1QuciymKtYtX7jXzr_fkeGTe7aENfEDl3E,108
|
|
2
2
|
agent_lab_sdk/langgraph/checkpoint/__init__.py,sha256=DnKwR1LwbaQ3qhb124lE-tnojrUIVcCdNzHEHwgpL5M,86
|
|
3
|
-
agent_lab_sdk/langgraph/checkpoint/agw_saver.py,sha256=
|
|
3
|
+
agent_lab_sdk/langgraph/checkpoint/agw_saver.py,sha256=8A0nfn2GOpCHlFijxC3VQSiTNotoOftSPjwpqX7kQiE,30392
|
|
4
4
|
agent_lab_sdk/langgraph/checkpoint/serde.py,sha256=UTSYbTbhBeL1CAr-XMbaH3SSIx9TeiC7ak22duXvqkw,5175
|
|
5
5
|
agent_lab_sdk/llm/__init__.py,sha256=Yo9MbYdHS1iX05A9XiJGwWN1Hm4IARGav9mNFPrtDeA,376
|
|
6
6
|
agent_lab_sdk/llm/agw_token_manager.py,sha256=_bPPI8muaEa6H01P8hHQOJHiiivaLd8N_d3OT9UT_80,4787
|
|
@@ -15,8 +15,8 @@ agent_lab_sdk/schema/log_message.py,sha256=nadi6lZGRuDSPmfbYs9QPpRJUT9Pfy8Y7pGCv
|
|
|
15
15
|
agent_lab_sdk/storage/__init__.py,sha256=HAtUoqg3k0irqPMewayadVA9aXJOmYSxRr6a5J1scT0,174
|
|
16
16
|
agent_lab_sdk/storage/storage.py,sha256=ELpt7GRwFD-aWa6ctinfA_QwcvzWLvKS0Wz8FlxVqAs,2075
|
|
17
17
|
agent_lab_sdk/storage/storage_v2.py,sha256=ONseynX59xzWK17dfzxZvnii2rpz3Oo2Zo9Ck-lcGnw,1997
|
|
18
|
-
agent_lab_sdk-0.1.
|
|
19
|
-
agent_lab_sdk-0.1.
|
|
20
|
-
agent_lab_sdk-0.1.
|
|
21
|
-
agent_lab_sdk-0.1.
|
|
22
|
-
agent_lab_sdk-0.1.
|
|
18
|
+
agent_lab_sdk-0.1.42.dist-info/licenses/LICENSE,sha256=_TRXHkF3S9ilWBPdZcHLI_S-PRjK0L_SeOb2pcPAdV4,417
|
|
19
|
+
agent_lab_sdk-0.1.42.dist-info/METADATA,sha256=-cgSfyjYUfwlxVTFWTpdYEAxd-pCmESXTubfOANl6_o,19099
|
|
20
|
+
agent_lab_sdk-0.1.42.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
agent_lab_sdk-0.1.42.dist-info/top_level.txt,sha256=E1efqkJ89KNmPBWdLzdMHeVtH0dYyCo4fhnSb81_15I,14
|
|
22
|
+
agent_lab_sdk-0.1.42.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|