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.

@@ -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": {k: self._safe_dump(v) for k, v in cp["channel_values"].items()},
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": pending,
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._safe_load(value_payload)))
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._safe_load(value_payload)))
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={k: self._safe_load(v) for k, v in raw["channelValues"].items()},
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._safe_dump(v) # type: ignore[assignment]
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._safe_load(md)
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._safe_load(third)))
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._safe_load(third)))
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._safe_dump(v)} for ch, v in writes]
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
  Metadata-Version: 2.4
2
2
  Name: agent-lab-sdk
3
- Version: 0.1.41
3
+ Version: 0.1.42
4
4
  Summary: SDK для работы с Agent Lab
5
5
  Author-email: Andrew Ohurtsov <andermirik@yandex.com>
6
6
  License: Proprietary and Confidential — All Rights Reserved
@@ -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=NFoRDTo-keZ0IGOJCWs2nuhPAPEfPOBFdhhNtv4Z0gQ,25874
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.41.dist-info/licenses/LICENSE,sha256=_TRXHkF3S9ilWBPdZcHLI_S-PRjK0L_SeOb2pcPAdV4,417
19
- agent_lab_sdk-0.1.41.dist-info/METADATA,sha256=ksFSStSOlPIsT5Xg-ATauthhOfeXWFsVCkfsPfoSOoc,19099
20
- agent_lab_sdk-0.1.41.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
- agent_lab_sdk-0.1.41.dist-info/top_level.txt,sha256=E1efqkJ89KNmPBWdLzdMHeVtH0dYyCo4fhnSb81_15I,14
22
- agent_lab_sdk-0.1.41.dist-info/RECORD,,
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,,