lingxigraph 2.0.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.
Files changed (64) hide show
  1. lingxigraph/__init__.py +187 -0
  2. lingxigraph/__main__.py +6 -0
  3. lingxigraph/cache.py +84 -0
  4. lingxigraph/cache_redis.py +66 -0
  5. lingxigraph/channels.py +190 -0
  6. lingxigraph/checkpoint/__init__.py +115 -0
  7. lingxigraph/checkpoint/memory.py +130 -0
  8. lingxigraph/checkpoint/postgres.py +283 -0
  9. lingxigraph/checkpoint/sqlite.py +329 -0
  10. lingxigraph/cli.py +390 -0
  11. lingxigraph/constants.py +6 -0
  12. lingxigraph/errors.py +69 -0
  13. lingxigraph/events.py +57 -0
  14. lingxigraph/examples/__init__.py +1 -0
  15. lingxigraph/examples/multi_agent_graph.py +118 -0
  16. lingxigraph/examples/production_graph.py +36 -0
  17. lingxigraph/func.py +122 -0
  18. lingxigraph/graph/__init__.py +7 -0
  19. lingxigraph/graph/builder.py +317 -0
  20. lingxigraph/graph/executor.py +2403 -0
  21. lingxigraph/graph/structure.py +67 -0
  22. lingxigraph/integrations/__init__.py +37 -0
  23. lingxigraph/integrations/_http.py +52 -0
  24. lingxigraph/integrations/coze.py +850 -0
  25. lingxigraph/integrations/openai_compat.py +225 -0
  26. lingxigraph/messages.py +227 -0
  27. lingxigraph/models.py +33 -0
  28. lingxigraph/observability.py +127 -0
  29. lingxigraph/patterns/__init__.py +25 -0
  30. lingxigraph/patterns/multi_agent.py +410 -0
  31. lingxigraph/prebuilt.py +214 -0
  32. lingxigraph/protocols/__init__.py +12 -0
  33. lingxigraph/protocols/a2a.py +165 -0
  34. lingxigraph/protocols/mcp.py +169 -0
  35. lingxigraph/py.typed +1 -0
  36. lingxigraph/runtime.py +275 -0
  37. lingxigraph/scaffold.py +264 -0
  38. lingxigraph/schema.py +230 -0
  39. lingxigraph/sdk.py +522 -0
  40. lingxigraph/serialization.py +164 -0
  41. lingxigraph/server/__init__.py +14 -0
  42. lingxigraph/server/app.py +912 -0
  43. lingxigraph/server/eventbus.py +80 -0
  44. lingxigraph/server/migrations/0001_v1.sql +193 -0
  45. lingxigraph/server/models.py +209 -0
  46. lingxigraph/server/registry.py +110 -0
  47. lingxigraph/server/repository.py +1219 -0
  48. lingxigraph/server/security.py +125 -0
  49. lingxigraph/server/worker.py +303 -0
  50. lingxigraph/store/__init__.py +124 -0
  51. lingxigraph/store/memory.py +196 -0
  52. lingxigraph/store/postgres.py +242 -0
  53. lingxigraph/studio/index.html +113 -0
  54. lingxigraph/studio/studio.css +234 -0
  55. lingxigraph/studio/studio.js +821 -0
  56. lingxigraph/tools.py +331 -0
  57. lingxigraph/types.py +243 -0
  58. lingxigraph/version.py +5 -0
  59. lingxigraph-2.0.1.dist-info/METADATA +256 -0
  60. lingxigraph-2.0.1.dist-info/RECORD +64 -0
  61. lingxigraph-2.0.1.dist-info/WHEEL +5 -0
  62. lingxigraph-2.0.1.dist-info/entry_points.txt +2 -0
  63. lingxigraph-2.0.1.dist-info/licenses/LICENSE +21 -0
  64. lingxigraph-2.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,187 @@
1
+ """Public API for the LingxiGraph runtime."""
2
+
3
+ from .cache import AsyncCache, BaseCache, InMemoryCache
4
+ from .channels import EphemeralValue, Topic
5
+ from .checkpoint import (
6
+ AsyncCheckpointer,
7
+ AsyncPostgresSaver,
8
+ Checkpoint,
9
+ Checkpointer,
10
+ CheckpointTuple,
11
+ InMemorySaver,
12
+ PendingWrite,
13
+ PostgresSaver,
14
+ SqliteSaver,
15
+ )
16
+ from .constants import END, START
17
+ from .errors import (
18
+ BudgetExceededError,
19
+ ConcurrentRunError,
20
+ EmptyInputError,
21
+ GraphCancelledError,
22
+ GraphInterrupt,
23
+ GraphRecursionError,
24
+ GraphTimeoutError,
25
+ GraphValidationError,
26
+ IdempotencyConflictError,
27
+ InvalidUpdateError,
28
+ LingxiGraphError,
29
+ PersistenceError,
30
+ )
31
+ from .events import Event, EventKind
32
+ from .func import entrypoint, task
33
+ from .graph import CompiledGraph, CompiledStateGraph, EdgeInfo, GraphInfo, NodeInfo, StateGraph
34
+ from .messages import (
35
+ REMOVE_ALL_MESSAGES,
36
+ AIMessage,
37
+ AIMessageChunk,
38
+ AnyMessage,
39
+ HumanMessage,
40
+ MessagesState,
41
+ RemoveMessage,
42
+ SystemMessage,
43
+ ToolCall,
44
+ ToolCallChunk,
45
+ ToolMessage,
46
+ add_messages,
47
+ merge_chunks,
48
+ )
49
+ from .models import ChatModel, StreamingChatModel
50
+ from .prebuilt import AgentState, create_agent, create_react_agent
51
+ from .runtime import (
52
+ CancellationToken,
53
+ ExecutionBudget,
54
+ Runtime,
55
+ get_config,
56
+ get_runtime,
57
+ get_store,
58
+ get_stream_writer,
59
+ )
60
+ from .serialization import JsonSerializer, SerializationError, Serializer
61
+ from .store import (
62
+ AsyncPostgresStore,
63
+ AsyncStore,
64
+ BaseStore,
65
+ Embedder,
66
+ InMemoryStore,
67
+ Item,
68
+ PostgresStore,
69
+ StoreOperation,
70
+ )
71
+ from .tools import ToolNode, ToolSpec, tool, tools_condition, validate_json_schema
72
+ from .types import (
73
+ CachePolicy,
74
+ Command,
75
+ CommandScope,
76
+ Durability,
77
+ Interrupt,
78
+ MultitaskStrategy,
79
+ RetryPolicy,
80
+ RunConfig,
81
+ RunStatus,
82
+ Send,
83
+ StateSnapshot,
84
+ StreamWriter,
85
+ SubgraphPersistence,
86
+ TaskSnapshot,
87
+ interrupt,
88
+ )
89
+ from .version import __version__
90
+
91
+ __all__ = [
92
+ "__version__",
93
+ "AsyncCache",
94
+ "AsyncCheckpointer",
95
+ "AsyncPostgresSaver",
96
+ "AsyncPostgresStore",
97
+ "AsyncStore",
98
+ "AIMessage",
99
+ "AIMessageChunk",
100
+ "AgentState",
101
+ "AnyMessage",
102
+ "BaseCache",
103
+ "BaseStore",
104
+ "BudgetExceededError",
105
+ "CachePolicy",
106
+ "CancellationToken",
107
+ "Checkpoint",
108
+ "Checkpointer",
109
+ "CheckpointTuple",
110
+ "ChatModel",
111
+ "Command",
112
+ "CommandScope",
113
+ "CompiledGraph",
114
+ "CompiledStateGraph",
115
+ "ConcurrentRunError",
116
+ "Durability",
117
+ "EdgeInfo",
118
+ "Embedder",
119
+ "ExecutionBudget",
120
+ "END",
121
+ "EmptyInputError",
122
+ "EphemeralValue",
123
+ "Event",
124
+ "EventKind",
125
+ "GraphCancelledError",
126
+ "GraphInterrupt",
127
+ "GraphRecursionError",
128
+ "GraphTimeoutError",
129
+ "GraphValidationError",
130
+ "GraphInfo",
131
+ "HumanMessage",
132
+ "IdempotencyConflictError",
133
+ "InMemoryCache",
134
+ "InMemorySaver",
135
+ "InMemoryStore",
136
+ "Interrupt",
137
+ "InvalidUpdateError",
138
+ "Item",
139
+ "JsonSerializer",
140
+ "LingxiGraphError",
141
+ "MultitaskStrategy",
142
+ "MessagesState",
143
+ "PendingWrite",
144
+ "NodeInfo",
145
+ "PersistenceError",
146
+ "PostgresSaver",
147
+ "PostgresStore",
148
+ "RetryPolicy",
149
+ "RunConfig",
150
+ "RunStatus",
151
+ "REMOVE_ALL_MESSAGES",
152
+ "RemoveMessage",
153
+ "Runtime",
154
+ "START",
155
+ "Send",
156
+ "SerializationError",
157
+ "Serializer",
158
+ "SqliteSaver",
159
+ "StateGraph",
160
+ "StateSnapshot",
161
+ "StreamWriter",
162
+ "StreamingChatModel",
163
+ "StoreOperation",
164
+ "SubgraphPersistence",
165
+ "SystemMessage",
166
+ "TaskSnapshot",
167
+ "ToolCall",
168
+ "ToolCallChunk",
169
+ "ToolMessage",
170
+ "ToolNode",
171
+ "ToolSpec",
172
+ "Topic",
173
+ "add_messages",
174
+ "create_agent",
175
+ "create_react_agent",
176
+ "entrypoint",
177
+ "get_config",
178
+ "get_runtime",
179
+ "get_store",
180
+ "get_stream_writer",
181
+ "interrupt",
182
+ "merge_chunks",
183
+ "tool",
184
+ "tools_condition",
185
+ "validate_json_schema",
186
+ "task",
187
+ ]
@@ -0,0 +1,6 @@
1
+ """Enable ``python -m lingxigraph`` as an alias for the console entry point."""
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
lingxigraph/cache.py ADDED
@@ -0,0 +1,84 @@
1
+ """Provider-neutral cache protocol and in-memory implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import copy
7
+ import time
8
+ from dataclasses import dataclass
9
+ from threading import RLock
10
+ from typing import Any, Protocol, runtime_checkable
11
+
12
+
13
+ @dataclass(frozen=True, slots=True)
14
+ class CacheEntry:
15
+ value: Any
16
+ expires_at: float | None = None
17
+
18
+
19
+ @runtime_checkable
20
+ class BaseCache(Protocol):
21
+ def get(self, key: str) -> Any | None: ...
22
+
23
+ def set(self, key: str, value: Any, *, ttl: float | None = None) -> None: ...
24
+
25
+ def delete(self, key: str) -> None: ...
26
+
27
+ def clear(self, *, namespace: str | None = None) -> None: ...
28
+
29
+
30
+ @runtime_checkable
31
+ class AsyncCache(Protocol):
32
+ async def aget(self, key: str) -> Any | None: ...
33
+
34
+ async def aset(self, key: str, value: Any, *, ttl: float | None = None) -> None: ...
35
+
36
+ async def adelete(self, key: str) -> None: ...
37
+
38
+
39
+ class InMemoryCache:
40
+ """Thread-safe TTL cache intended for tests and embedded deployments."""
41
+
42
+ def __init__(self) -> None:
43
+ self._items: dict[str, CacheEntry] = {}
44
+ self._lock = RLock()
45
+
46
+ def get(self, key: str) -> Any | None:
47
+ with self._lock:
48
+ entry = self._items.get(key)
49
+ if entry is None:
50
+ return None
51
+ if entry.expires_at is not None and entry.expires_at <= time.monotonic():
52
+ self._items.pop(key, None)
53
+ return None
54
+ return copy.deepcopy(entry.value)
55
+
56
+ def set(self, key: str, value: Any, *, ttl: float | None = None) -> None:
57
+ expires_at = time.monotonic() + ttl if ttl is not None else None
58
+ with self._lock:
59
+ self._items[key] = CacheEntry(copy.deepcopy(value), expires_at)
60
+
61
+ def delete(self, key: str) -> None:
62
+ with self._lock:
63
+ self._items.pop(key, None)
64
+
65
+ def clear(self, *, namespace: str | None = None) -> None:
66
+ with self._lock:
67
+ if namespace is None:
68
+ self._items.clear()
69
+ return
70
+ prefix = f"{namespace}:"
71
+ for key in [item for item in self._items if item.startswith(prefix)]:
72
+ self._items.pop(key, None)
73
+
74
+ async def aget(self, key: str) -> Any | None:
75
+ return await asyncio.to_thread(self.get, key)
76
+
77
+ async def aset(self, key: str, value: Any, *, ttl: float | None = None) -> None:
78
+ await asyncio.to_thread(self.set, key, value, ttl=ttl)
79
+
80
+ async def adelete(self, key: str) -> None:
81
+ await asyncio.to_thread(self.delete, key)
82
+
83
+
84
+ __all__ = ["AsyncCache", "BaseCache", "CacheEntry", "InMemoryCache"]
@@ -0,0 +1,66 @@
1
+ """Optional Redis-backed node cache."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import Any
7
+
8
+ from .serialization import JsonSerializer
9
+
10
+
11
+ class RedisCache:
12
+ """Async-first Redis cache with strict JSON payloads and namespace clearing."""
13
+
14
+ def __init__(
15
+ self,
16
+ url: str = "redis://localhost:6379/0",
17
+ *,
18
+ prefix: str = "lingxigraph:cache",
19
+ serializer: JsonSerializer | None = None,
20
+ ) -> None:
21
+ try:
22
+ from redis.asyncio import Redis
23
+ except ImportError as exc: # pragma: no cover - optional dependency
24
+ raise RuntimeError("install lingxigraph[redis] to use RedisCache") from exc
25
+ self._redis = Redis.from_url(url)
26
+ self._prefix = prefix.rstrip(":")
27
+ self._serializer = serializer or JsonSerializer()
28
+
29
+ def _key(self, key: str) -> str:
30
+ return f"{self._prefix}:{key}"
31
+
32
+ async def aget(self, key: str) -> Any | None:
33
+ payload = await self._redis.get(self._key(key))
34
+ return None if payload is None else self._serializer.loads(payload)
35
+
36
+ async def aset(self, key: str, value: Any, *, ttl: float | None = None) -> None:
37
+ milliseconds = max(1, int(ttl * 1000)) if ttl is not None else None
38
+ await self._redis.set(
39
+ self._key(key), self._serializer.dumps(value), px=milliseconds
40
+ )
41
+
42
+ async def adelete(self, key: str) -> None:
43
+ await self._redis.delete(self._key(key))
44
+
45
+ async def aclear(self, *, namespace: str | None = None) -> None:
46
+ pattern = self._key(f"{namespace}:*" if namespace else "*")
47
+ async for key in self._redis.scan_iter(match=pattern, count=500):
48
+ await self._redis.delete(key)
49
+
50
+ def get(self, key: str) -> Any | None:
51
+ return asyncio.run(self.aget(key))
52
+
53
+ def set(self, key: str, value: Any, *, ttl: float | None = None) -> None:
54
+ asyncio.run(self.aset(key, value, ttl=ttl))
55
+
56
+ def delete(self, key: str) -> None:
57
+ asyncio.run(self.adelete(key))
58
+
59
+ def clear(self, *, namespace: str | None = None) -> None:
60
+ asyncio.run(self.aclear(namespace=namespace))
61
+
62
+ async def close(self) -> None:
63
+ await self._redis.aclose()
64
+
65
+
66
+ __all__ = ["RedisCache"]
@@ -0,0 +1,190 @@
1
+ """State channels and reducer discovery for ``TypedDict`` schemas."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import copy
6
+ from collections.abc import Callable, Mapping
7
+ from dataclasses import dataclass
8
+ from typing import Annotated, Any, get_args, get_origin, get_type_hints
9
+
10
+ from .errors import InvalidUpdateError
11
+
12
+ Reducer = Callable[[Any, Any], Any]
13
+
14
+
15
+ @dataclass(frozen=True, slots=True)
16
+ class ReplaceValue:
17
+ """Internal write wrapper that overwrites a channel instead of reducing.
18
+
19
+ Subgraph nodes return their final channel values wrapped in this marker:
20
+ the child already merged the parent's seed value, so folding the result
21
+ through the reducer again would double-count it.
22
+ """
23
+
24
+ value: Any
25
+
26
+
27
+ @dataclass(frozen=True, slots=True)
28
+ class LastValue:
29
+ """A channel that accepts at most one write per superstep."""
30
+
31
+ value_type: Any = Any
32
+
33
+ def merge(self, current: Any, writes: list[Any], *, key: str) -> Any:
34
+ if len(writes) > 1:
35
+ raise InvalidUpdateError(
36
+ f"state key {key!r} received {len(writes)} writes in one superstep; "
37
+ "declare an Annotated reducer to merge concurrent writes"
38
+ )
39
+ if not writes:
40
+ return current
41
+ write = writes[0]
42
+ if isinstance(write, ReplaceValue):
43
+ write = write.value
44
+ return copy.deepcopy(write)
45
+
46
+
47
+ @dataclass(frozen=True, slots=True)
48
+ class BinaryOperatorAggregate:
49
+ """A channel that folds writes through a binary reducer."""
50
+
51
+ value_type: Any
52
+ operator: Reducer
53
+
54
+ def merge(self, current: Any, writes: list[Any], *, key: str) -> Any:
55
+ del key
56
+ if not writes:
57
+ return current
58
+ result = _MISSING if current is _MISSING else copy.deepcopy(current)
59
+ for value in writes:
60
+ if isinstance(value, ReplaceValue):
61
+ result = copy.deepcopy(value.value)
62
+ elif result is _MISSING:
63
+ result = copy.deepcopy(value)
64
+ else:
65
+ result = self.operator(result, copy.deepcopy(value))
66
+ return result
67
+
68
+
69
+ @dataclass(frozen=True, slots=True)
70
+ class Topic:
71
+ """A multi-value channel that optionally accumulates across supersteps."""
72
+
73
+ value_type: Any = Any
74
+ accumulate: bool = False
75
+
76
+ def merge(self, current: Any, writes: list[Any], *, key: str) -> Any:
77
+ del key
78
+ values: list[Any] = []
79
+ if self.accumulate and current is not _MISSING:
80
+ values.extend(copy.deepcopy(list(current)))
81
+ for write in writes:
82
+ if isinstance(write, ReplaceValue):
83
+ values = copy.deepcopy(list(write.value))
84
+ elif isinstance(write, (list, tuple)):
85
+ values.extend(copy.deepcopy(list(write)))
86
+ else:
87
+ values.append(copy.deepcopy(write))
88
+ return values
89
+
90
+
91
+ @dataclass(frozen=True, slots=True)
92
+ class EphemeralValue:
93
+ """A last-value channel cleared when a superstep does not write it."""
94
+
95
+ value_type: Any = Any
96
+
97
+ def merge(self, current: Any, writes: list[Any], *, key: str) -> Any:
98
+ if len(writes) > 1:
99
+ raise InvalidUpdateError(
100
+ f"ephemeral state key {key!r} received {len(writes)} writes in one superstep"
101
+ )
102
+ if not writes:
103
+ return _CLEAR
104
+ write = writes[0]
105
+ return copy.deepcopy(write.value if isinstance(write, ReplaceValue) else write)
106
+
107
+
108
+ Channel = LastValue | BinaryOperatorAggregate | Topic | EphemeralValue
109
+ _MISSING = object()
110
+ _CLEAR = object()
111
+
112
+
113
+ def extract_channels(schema: type) -> dict[str, Channel]:
114
+ """Build channels from a ``TypedDict``-style annotated schema."""
115
+
116
+ model_fields = getattr(schema, "model_fields", None)
117
+ if isinstance(model_fields, Mapping):
118
+ hints = {
119
+ name: getattr(field, "annotation", Any)
120
+ for name, field in model_fields.items()
121
+ }
122
+ else:
123
+ try:
124
+ hints = get_type_hints(schema, include_extras=True)
125
+ except (NameError, TypeError) as exc:
126
+ raise InvalidUpdateError(f"cannot inspect state schema {schema!r}: {exc}") from exc
127
+ if not hints:
128
+ raise InvalidUpdateError("state schema must declare at least one annotated key")
129
+
130
+ channels: dict[str, Channel] = {}
131
+ for key, hint in hints.items():
132
+ if get_origin(hint) is Annotated:
133
+ args = get_args(hint)
134
+ value_type, metadata = args[0], args[1:]
135
+ channel = next((item for item in metadata if isinstance(item, (LastValue, BinaryOperatorAggregate, Topic, EphemeralValue))), None)
136
+ if channel is not None:
137
+ channels[key] = channel
138
+ continue
139
+ reducer = next((item for item in metadata if callable(item)), None)
140
+ channels[key] = (
141
+ BinaryOperatorAggregate(value_type, reducer)
142
+ if reducer is not None
143
+ else LastValue(value_type)
144
+ )
145
+ else:
146
+ channels[key] = LastValue(hint)
147
+ return channels
148
+
149
+
150
+ def merge_updates(
151
+ state: Mapping[str, Any],
152
+ updates: list[tuple[str, Mapping[str, Any]]],
153
+ channels: Mapping[str, Channel],
154
+ ) -> dict[str, Any]:
155
+ """Merge node updates deterministically in the supplied task order."""
156
+
157
+ writes: dict[str, list[Any]] = {key: [] for key in channels}
158
+ for node, update in updates:
159
+ if not isinstance(update, Mapping):
160
+ raise InvalidUpdateError(
161
+ f"node {node!r} returned {type(update).__name__}; expected dict or Command"
162
+ )
163
+ unknown = set(update) - set(channels)
164
+ if unknown:
165
+ names = ", ".join(sorted(unknown))
166
+ raise InvalidUpdateError(f"node {node!r} wrote unknown state key(s): {names}")
167
+ for key, value in update.items():
168
+ writes[key].append(value)
169
+
170
+ merged = copy.deepcopy(dict(state))
171
+ for key, channel in channels.items():
172
+ current = merged.get(key, _MISSING)
173
+ value = channel.merge(current, writes[key], key=key)
174
+ if value is _CLEAR:
175
+ merged.pop(key, None)
176
+ elif value is not _MISSING:
177
+ merged[key] = value
178
+ return merged
179
+
180
+
181
+ __all__ = [
182
+ "BinaryOperatorAggregate",
183
+ "Channel",
184
+ "LastValue",
185
+ "EphemeralValue",
186
+ "ReplaceValue",
187
+ "Topic",
188
+ "extract_channels",
189
+ "merge_updates",
190
+ ]
@@ -0,0 +1,115 @@
1
+ """Checkpoint protocol and persisted value types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncIterator, Iterable, Mapping
6
+ from dataclasses import dataclass, field
7
+ from typing import Any, Protocol, runtime_checkable
8
+
9
+ from ..types import Interrupt, Send, TaskSnapshot
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class PendingWrite:
14
+ """One durable task result written before its superstep commits."""
15
+
16
+ checkpoint_id: str
17
+ task_id: str
18
+ index: int
19
+ values: Mapping[str, Any]
20
+ task_path: tuple[str, ...] = ()
21
+ goto: tuple[str | Send, ...] = ()
22
+ error: str | None = None
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class Checkpoint:
27
+ id: str
28
+ ts: str
29
+ step: int
30
+ channel_values: Mapping[str, Any]
31
+ next: tuple[str, ...]
32
+ pending_sends: tuple[Send, ...] = ()
33
+ pending_interrupts: tuple[Interrupt, ...] = ()
34
+ parent_id: str | None = None
35
+ namespace: tuple[str, ...] = ()
36
+ run_id: str | None = None
37
+ channel_versions: Mapping[str, int] = field(default_factory=dict)
38
+ tasks: tuple[TaskSnapshot, ...] = ()
39
+ schema_version: int = 2
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class CheckpointTuple:
44
+ config: Mapping[str, Any]
45
+ checkpoint: Checkpoint
46
+ metadata: Mapping[str, Any] = field(default_factory=dict)
47
+
48
+
49
+ @runtime_checkable
50
+ class Checkpointer(Protocol):
51
+ def put(
52
+ self,
53
+ config: Mapping[str, Any],
54
+ checkpoint: Checkpoint,
55
+ metadata: Mapping[str, Any],
56
+ ) -> Mapping[str, Any]: ...
57
+
58
+ def get_tuple(self, config: Mapping[str, Any]) -> CheckpointTuple | None: ...
59
+
60
+ def list(self, config: Mapping[str, Any]) -> Iterable[CheckpointTuple]: ...
61
+
62
+ def put_writes(
63
+ self,
64
+ config: Mapping[str, Any],
65
+ checkpoint_id: str,
66
+ writes: Iterable[PendingWrite],
67
+ ) -> None: ...
68
+
69
+ def get_writes(
70
+ self, config: Mapping[str, Any], checkpoint_id: str
71
+ ) -> Iterable[PendingWrite]: ...
72
+
73
+ def delete_thread(self, config: Mapping[str, Any]) -> None: ...
74
+
75
+
76
+ @runtime_checkable
77
+ class AsyncCheckpointer(Protocol):
78
+ async def aput(
79
+ self,
80
+ config: Mapping[str, Any],
81
+ checkpoint: Checkpoint,
82
+ metadata: Mapping[str, Any],
83
+ ) -> Mapping[str, Any]: ...
84
+
85
+ async def aget_tuple(self, config: Mapping[str, Any]) -> CheckpointTuple | None: ...
86
+
87
+ def alist(self, config: Mapping[str, Any]) -> AsyncIterator[CheckpointTuple]: ...
88
+
89
+ async def aput_writes(
90
+ self,
91
+ config: Mapping[str, Any],
92
+ checkpoint_id: str,
93
+ writes: Iterable[PendingWrite],
94
+ ) -> None: ...
95
+
96
+ async def aget_writes(
97
+ self, config: Mapping[str, Any], checkpoint_id: str
98
+ ) -> Iterable[PendingWrite]: ...
99
+
100
+
101
+ from .memory import InMemorySaver
102
+ from .postgres import AsyncPostgresSaver, PostgresSaver
103
+ from .sqlite import SqliteSaver
104
+
105
+ __all__ = [
106
+ "AsyncCheckpointer",
107
+ "Checkpoint",
108
+ "Checkpointer",
109
+ "CheckpointTuple",
110
+ "InMemorySaver",
111
+ "PendingWrite",
112
+ "PostgresSaver",
113
+ "AsyncPostgresSaver",
114
+ "SqliteSaver",
115
+ ]