langgraph-checkpoint 1.0.0__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.
@@ -0,0 +1,432 @@
1
+ from abc import ABC
2
+ from datetime import datetime, timezone
3
+ from typing import (
4
+ Any,
5
+ AsyncIterator,
6
+ Dict,
7
+ Iterator,
8
+ List,
9
+ Literal,
10
+ Mapping,
11
+ NamedTuple,
12
+ Optional,
13
+ Tuple,
14
+ TypedDict,
15
+ TypeVar,
16
+ Union,
17
+ )
18
+
19
+ from langchain_core.runnables import ConfigurableFieldSpec, RunnableConfig
20
+
21
+ from langgraph.checkpoint.base.id import uuid6
22
+ from langgraph.checkpoint.serde.base import SerializerProtocol, maybe_add_typed_methods
23
+ from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
24
+ from langgraph.checkpoint.serde.types import (
25
+ ChannelProtocol,
26
+ SendProtocol,
27
+ )
28
+
29
+ V = TypeVar("V", int, float, str)
30
+ PendingWrite = Tuple[str, str, Any]
31
+
32
+
33
+ # Marked as total=False to allow for future expansion.
34
+ class CheckpointMetadata(TypedDict, total=False):
35
+ """Metadata associated with a checkpoint."""
36
+
37
+ source: Literal["input", "loop", "update"]
38
+ """The source of the checkpoint.
39
+ - "input": The checkpoint was created from an input to invoke/stream/batch.
40
+ - "loop": The checkpoint was created from inside the pregel loop.
41
+ - "update": The checkpoint was created from a manual state update.
42
+ """
43
+ step: int
44
+ """The step number of the checkpoint.
45
+ -1 for the first "input" checkpoint.
46
+ 0 for the first "loop" checkpoint.
47
+ ... for the nth checkpoint afterwards.
48
+ """
49
+ writes: dict[str, Any]
50
+ """The writes that were made between the previous checkpoint and this one.
51
+
52
+ Mapping from node name to writes emitted by that node.
53
+ """
54
+ score: Optional[int]
55
+ """The score of the checkpoint.
56
+
57
+ The score can be used to mark a checkpoint as "good".
58
+ """
59
+
60
+
61
+ class TaskInfo(TypedDict):
62
+ status: Literal["scheduled", "success", "error"]
63
+
64
+
65
+ class Checkpoint(TypedDict):
66
+ """State snapshot at a given point in time."""
67
+
68
+ v: int
69
+ """The version of the checkpoint format. Currently 1."""
70
+ id: str
71
+ """The ID of the checkpoint. This is both unique and monotonically
72
+ increasing, so can be used for sorting checkpoints from first to last."""
73
+ ts: str
74
+ """The timestamp of the checkpoint in ISO 8601 format."""
75
+ channel_values: dict[str, Any]
76
+ """The values of the channels at the time of the checkpoint.
77
+
78
+ Mapping from channel name to channel snapshot value.
79
+ """
80
+ channel_versions: dict[str, Union[str, int, float]]
81
+ """The versions of the channels at the time of the checkpoint.
82
+
83
+ The keys are channel names and the values are the logical time step
84
+ at which the channel was last updated.
85
+ """
86
+ versions_seen: dict[str, dict[str, Union[str, int, float]]]
87
+ """Map from node ID to map from channel name to version seen.
88
+
89
+ This keeps track of the versions of the channels that each node has seen.
90
+
91
+ Used to determine which nodes to execute next.
92
+ """
93
+ pending_sends: List[SendProtocol]
94
+ """List of packets sent to nodes but not yet processed.
95
+ Cleared by the next checkpoint."""
96
+ current_tasks: Dict[str, TaskInfo]
97
+ """Map from task ID to task info."""
98
+
99
+
100
+ def empty_checkpoint() -> Checkpoint:
101
+ return Checkpoint(
102
+ v=1,
103
+ id=str(uuid6(clock_seq=-2)),
104
+ ts=datetime.now(timezone.utc).isoformat(),
105
+ channel_values={},
106
+ channel_versions={},
107
+ versions_seen={},
108
+ pending_sends=[],
109
+ current_tasks={},
110
+ )
111
+
112
+
113
+ def copy_checkpoint(checkpoint: Checkpoint) -> Checkpoint:
114
+ return Checkpoint(
115
+ v=checkpoint["v"],
116
+ ts=checkpoint["ts"],
117
+ id=checkpoint["id"],
118
+ channel_values=checkpoint["channel_values"].copy(),
119
+ channel_versions=checkpoint["channel_versions"].copy(),
120
+ versions_seen={k: v.copy() for k, v in checkpoint["versions_seen"].items()},
121
+ pending_sends=checkpoint.get("pending_sends", []).copy(),
122
+ current_tasks=checkpoint.get("current_tasks", {}).copy(),
123
+ )
124
+
125
+
126
+ def create_checkpoint(
127
+ checkpoint: Checkpoint,
128
+ channels: Optional[Mapping[str, ChannelProtocol]],
129
+ step: int,
130
+ *,
131
+ id: Optional[str] = None,
132
+ ) -> Checkpoint:
133
+ """Create a checkpoint for the given channels."""
134
+ ts = datetime.now(timezone.utc).isoformat()
135
+ if channels is None:
136
+ values = checkpoint["channel_values"]
137
+ else:
138
+ values: dict[str, Any] = {}
139
+ for k, v in channels.items():
140
+ try:
141
+ values[k] = v.checkpoint()
142
+ except EmptyChannelError:
143
+ pass
144
+ return Checkpoint(
145
+ v=1,
146
+ ts=ts,
147
+ id=id or str(uuid6(clock_seq=step)),
148
+ channel_values=values,
149
+ channel_versions=checkpoint["channel_versions"],
150
+ versions_seen=checkpoint["versions_seen"],
151
+ pending_sends=checkpoint.get("pending_sends", []),
152
+ current_tasks={},
153
+ )
154
+
155
+
156
+ class CheckpointTuple(NamedTuple):
157
+ """A tuple containing a checkpoint and its associated data."""
158
+
159
+ config: RunnableConfig
160
+ checkpoint: Checkpoint
161
+ metadata: CheckpointMetadata
162
+ parent_config: Optional[RunnableConfig] = None
163
+ pending_writes: Optional[List[PendingWrite]] = None
164
+
165
+
166
+ CheckpointThreadId = ConfigurableFieldSpec(
167
+ id="thread_id",
168
+ annotation=str,
169
+ name="Thread ID",
170
+ description=None,
171
+ default="",
172
+ is_shared=True,
173
+ )
174
+
175
+ CheckpointNS = ConfigurableFieldSpec(
176
+ id="checkpoint_ns",
177
+ annotation=str,
178
+ name="Checkpoint NS",
179
+ description='Checkpoint namespace. Denotes the path to the subgraph node the checkpoint originates from, separated by `|` character, e.g. `"child|grandchild"`. Defaults to "" (root graph).',
180
+ default=None,
181
+ is_shared=True,
182
+ )
183
+
184
+ CheckpointId = ConfigurableFieldSpec(
185
+ id="checkpoint_id",
186
+ annotation=Optional[str],
187
+ name="Checkpoint ID",
188
+ description="Pass to fetch a past checkpoint. If None, fetches the latest checkpoint.",
189
+ default=None,
190
+ is_shared=True,
191
+ )
192
+
193
+
194
+ class BaseCheckpointSaver(ABC):
195
+ """Base class for creating a graph checkpointer.
196
+
197
+ Checkpointers allow LangGraph agents to persist their state
198
+ within and across multiple interactions.
199
+
200
+ Attributes:
201
+ serde (SerializerProtocol): Serializer for encoding/decoding checkpoints.
202
+
203
+ Note:
204
+ When creating a custom checkpoint saver, consider implementing async
205
+ versions to avoid blocking the main thread.
206
+ """
207
+
208
+ serde: SerializerProtocol = JsonPlusSerializer()
209
+
210
+ def __init__(
211
+ self,
212
+ *,
213
+ serde: Optional[SerializerProtocol] = None,
214
+ ) -> None:
215
+ self.serde = maybe_add_typed_methods(serde or self.serde)
216
+
217
+ @property
218
+ def config_specs(self) -> list[ConfigurableFieldSpec]:
219
+ """Define the configuration options for the checkpoint saver.
220
+
221
+ Returns:
222
+ list[ConfigurableFieldSpec]: List of configuration field specs.
223
+ """
224
+ return [CheckpointThreadId, CheckpointNS, CheckpointId]
225
+
226
+ def get(self, config: RunnableConfig) -> Optional[Checkpoint]:
227
+ """Fetch a checkpoint using the given configuration.
228
+
229
+ Args:
230
+ config (RunnableConfig): Configuration specifying which checkpoint to retrieve.
231
+
232
+ Returns:
233
+ Optional[Checkpoint]: The requested checkpoint, or None if not found.
234
+ """
235
+ if value := self.get_tuple(config):
236
+ return value.checkpoint
237
+
238
+ def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
239
+ """Fetch a checkpoint tuple using the given configuration.
240
+
241
+ Args:
242
+ config (RunnableConfig): Configuration specifying which checkpoint to retrieve.
243
+
244
+ Returns:
245
+ Optional[CheckpointTuple]: The requested checkpoint tuple, or None if not found.
246
+
247
+ Raises:
248
+ NotImplementedError: Implement this method in your custom checkpoint saver.
249
+ """
250
+ raise NotImplementedError
251
+
252
+ def list(
253
+ self,
254
+ config: Optional[RunnableConfig],
255
+ *,
256
+ filter: Optional[Dict[str, Any]] = None,
257
+ before: Optional[RunnableConfig] = None,
258
+ limit: Optional[int] = None,
259
+ ) -> Iterator[CheckpointTuple]:
260
+ """List checkpoints that match the given criteria.
261
+
262
+ Args:
263
+ config (Optional[RunnableConfig]): Base configuration for filtering checkpoints.
264
+ filter (Optional[Dict[str, Any]]): Additional filtering criteria.
265
+ before (Optional[RunnableConfig]): List checkpoints created before this configuration.
266
+ limit (Optional[int]): Maximum number of checkpoints to return.
267
+
268
+ Returns:
269
+ Iterator[CheckpointTuple]: Iterator of matching checkpoint tuples.
270
+
271
+ Raises:
272
+ NotImplementedError: Implement this method in your custom checkpoint saver.
273
+ """
274
+ raise NotImplementedError
275
+
276
+ def put(
277
+ self,
278
+ config: RunnableConfig,
279
+ checkpoint: Checkpoint,
280
+ metadata: CheckpointMetadata,
281
+ ) -> RunnableConfig:
282
+ """Store a checkpoint with its configuration and metadata.
283
+
284
+ Args:
285
+ config (RunnableConfig): Configuration for the checkpoint.
286
+ checkpoint (Checkpoint): The checkpoint to store.
287
+ metadata (CheckpointMetadata): Additional metadata for the checkpoint.
288
+
289
+ Returns:
290
+ RunnableConfig: Updated configuration after storing the checkpoint.
291
+
292
+ Raises:
293
+ NotImplementedError: Implement this method in your custom checkpoint saver.
294
+ """
295
+ raise NotImplementedError
296
+
297
+ def put_writes(
298
+ self,
299
+ config: RunnableConfig,
300
+ writes: List[Tuple[str, Any]],
301
+ task_id: str,
302
+ ) -> None:
303
+ """Store intermediate writes linked to a checkpoint.
304
+
305
+ Args:
306
+ config (RunnableConfig): Configuration of the related checkpoint.
307
+ writes (List[Tuple[str, Any]]): List of writes to store.
308
+ task_id (str): Identifier for the task creating the writes.
309
+
310
+ Raises:
311
+ NotImplementedError: Implement this method in your custom checkpoint saver.
312
+ """
313
+ raise NotImplementedError
314
+
315
+ async def aget(self, config: RunnableConfig) -> Optional[Checkpoint]:
316
+ """Asynchronously fetch a checkpoint using the given configuration.
317
+
318
+ Args:
319
+ config (RunnableConfig): Configuration specifying which checkpoint to retrieve.
320
+
321
+ Returns:
322
+ Optional[Checkpoint]: The requested checkpoint, or None if not found.
323
+ """
324
+ if value := await self.aget_tuple(config):
325
+ return value.checkpoint
326
+
327
+ async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
328
+ """Asynchronously fetch a checkpoint tuple using the given configuration.
329
+
330
+ Args:
331
+ config (RunnableConfig): Configuration specifying which checkpoint to retrieve.
332
+
333
+ Returns:
334
+ Optional[CheckpointTuple]: The requested checkpoint tuple, or None if not found.
335
+
336
+ Raises:
337
+ NotImplementedError: Implement this method in your custom checkpoint saver.
338
+ """
339
+ raise NotImplementedError
340
+
341
+ async def alist(
342
+ self,
343
+ config: Optional[RunnableConfig],
344
+ *,
345
+ filter: Optional[Dict[str, Any]] = None,
346
+ before: Optional[RunnableConfig] = None,
347
+ limit: Optional[int] = None,
348
+ ) -> AsyncIterator[CheckpointTuple]:
349
+ """Asynchronously list checkpoints that match the given criteria.
350
+
351
+ Args:
352
+ config (Optional[RunnableConfig]): Base configuration for filtering checkpoints.
353
+ filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata.
354
+ before (Optional[RunnableConfig]): List checkpoints created before this configuration.
355
+ limit (Optional[int]): Maximum number of checkpoints to return.
356
+
357
+ Returns:
358
+ AsyncIterator[CheckpointTuple]: Async iterator of matching checkpoint tuples.
359
+
360
+ Raises:
361
+ NotImplementedError: Implement this method in your custom checkpoint saver.
362
+ """
363
+ raise NotImplementedError
364
+ yield
365
+
366
+ async def aput(
367
+ self,
368
+ config: RunnableConfig,
369
+ checkpoint: Checkpoint,
370
+ metadata: CheckpointMetadata,
371
+ ) -> RunnableConfig:
372
+ """Asynchronously store a checkpoint with its configuration and metadata.
373
+
374
+ Args:
375
+ config (RunnableConfig): Configuration for the checkpoint.
376
+ checkpoint (Checkpoint): The checkpoint to store.
377
+ metadata (CheckpointMetadata): Additional metadata for the checkpoint.
378
+
379
+ Returns:
380
+ RunnableConfig: Updated configuration after storing the checkpoint.
381
+
382
+ Raises:
383
+ NotImplementedError: Implement this method in your custom checkpoint saver.
384
+ """
385
+ raise NotImplementedError
386
+
387
+ async def aput_writes(
388
+ self,
389
+ config: RunnableConfig,
390
+ writes: List[Tuple[str, Any]],
391
+ task_id: str,
392
+ ) -> None:
393
+ """Asynchronously store intermediate writes linked to a checkpoint.
394
+
395
+ Args:
396
+ config (RunnableConfig): Configuration of the related checkpoint.
397
+ writes (List[Tuple[str, Any]]): List of writes to store.
398
+ task_id (str): Identifier for the task creating the writes.
399
+
400
+ Raises:
401
+ NotImplementedError: Implement this method in your custom checkpoint saver.
402
+ """
403
+ raise NotImplementedError
404
+
405
+ def get_next_version(self, current: Optional[V], channel: ChannelProtocol) -> V:
406
+ """Generate the next version ID for a channel.
407
+
408
+ Default is to use integer versions, incrementing by 1. If you override, you can use str/int/float versions,
409
+ as long as they are monotonically increasing.
410
+
411
+ Args:
412
+ current (Optional[V]): The current version identifier (int, float, or str).
413
+ channel (BaseChannel): The channel being versioned.
414
+
415
+ Returns:
416
+ V: The next version identifier, which must be increasing.
417
+ """
418
+ return current + 1 if current is not None else 1
419
+
420
+
421
+ class EmptyChannelError(Exception):
422
+ """Raised when attempting to get the value of a channel that hasn't been updated
423
+ for the first time yet."""
424
+
425
+ pass
426
+
427
+
428
+ def get_checkpoint_id(config: RunnableConfig) -> Optional[str]:
429
+ """Get checkpoint ID in a backwards-compatible manner (fallback on thread_ts)."""
430
+ return config["configurable"].get(
431
+ "checkpoint_id", config["configurable"].get("thread_ts")
432
+ )
@@ -0,0 +1,108 @@
1
+ """Adapted from
2
+ https://github.com/oittaa/uuid6-python/blob/main/src/uuid6/__init__.py#L95
3
+ Bundled in to avoid install issues with uuid6 package
4
+ """
5
+
6
+ import secrets
7
+ import time
8
+ import uuid
9
+ from typing import Optional, Tuple
10
+
11
+ _last_v6_timestamp = None
12
+
13
+
14
+ class UUID(uuid.UUID):
15
+ r"""UUID draft version objects"""
16
+
17
+ __slots__ = ()
18
+
19
+ def __init__(
20
+ self,
21
+ hex: Optional[str] = None,
22
+ bytes: Optional[bytes] = None,
23
+ bytes_le: Optional[bytes] = None,
24
+ fields: Optional[Tuple[int, int, int, int, int, int]] = None,
25
+ int: Optional[int] = None,
26
+ version: Optional[int] = None,
27
+ *,
28
+ is_safe: uuid.SafeUUID = uuid.SafeUUID.unknown,
29
+ ) -> None:
30
+ r"""Create a UUID."""
31
+
32
+ if int is None or [hex, bytes, bytes_le, fields].count(None) != 4:
33
+ return super().__init__(
34
+ hex=hex,
35
+ bytes=bytes,
36
+ bytes_le=bytes_le,
37
+ fields=fields,
38
+ int=int,
39
+ version=version,
40
+ is_safe=is_safe,
41
+ )
42
+ if not 0 <= int < 1 << 128:
43
+ raise ValueError("int is out of range (need a 128-bit value)")
44
+ if version is not None:
45
+ if not 6 <= version <= 8:
46
+ raise ValueError("illegal version number")
47
+ # Set the variant to RFC 4122.
48
+ int &= ~(0xC000 << 48)
49
+ int |= 0x8000 << 48
50
+ # Set the version number.
51
+ int &= ~(0xF000 << 64)
52
+ int |= version << 76
53
+ super().__init__(int=int, is_safe=is_safe)
54
+
55
+ @property
56
+ def subsec(self) -> int:
57
+ return ((self.int >> 64) & 0x0FFF) << 8 | ((self.int >> 54) & 0xFF)
58
+
59
+ @property
60
+ def time(self) -> int:
61
+ if self.version == 6:
62
+ return (
63
+ (self.time_low << 28)
64
+ | (self.time_mid << 12)
65
+ | (self.time_hi_version & 0x0FFF)
66
+ )
67
+ if self.version == 7:
68
+ return self.int >> 80
69
+ if self.version == 8:
70
+ return (self.int >> 80) * 10**6 + _subsec_decode(self.subsec)
71
+ return super().time
72
+
73
+
74
+ def _subsec_decode(value: int) -> int:
75
+ return -(-value * 10**6 // 2**20)
76
+
77
+
78
+ def uuid6(node: Optional[int] = None, clock_seq: Optional[int] = None) -> UUID:
79
+ r"""UUID version 6 is a field-compatible version of UUIDv1, reordered for
80
+ improved DB locality. It is expected that UUIDv6 will primarily be
81
+ used in contexts where there are existing v1 UUIDs. Systems that do
82
+ not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
83
+
84
+ If 'node' is not given, a random 48-bit number is chosen.
85
+
86
+ If 'clock_seq' is given, it is used as the sequence number;
87
+ otherwise a random 14-bit sequence number is chosen."""
88
+
89
+ global _last_v6_timestamp
90
+
91
+ nanoseconds = time.time_ns()
92
+ # 0x01b21dd213814000 is the number of 100-ns intervals between the
93
+ # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
94
+ timestamp = nanoseconds // 100 + 0x01B21DD213814000
95
+ if _last_v6_timestamp is not None and timestamp <= _last_v6_timestamp:
96
+ timestamp = _last_v6_timestamp + 1
97
+ _last_v6_timestamp = timestamp
98
+ if clock_seq is None:
99
+ clock_seq = secrets.randbits(14) # instead of stable storage
100
+ if node is None:
101
+ node = secrets.randbits(48)
102
+ time_high_and_time_mid = (timestamp >> 12) & 0xFFFFFFFFFFFF
103
+ time_low_and_version = timestamp & 0x0FFF
104
+ uuid_int = time_high_and_time_mid << 80
105
+ uuid_int |= time_low_and_version << 64
106
+ uuid_int |= (clock_seq & 0x3FFF) << 48
107
+ uuid_int |= node & 0xFFFFFFFFFFFF
108
+ return UUID(int=uuid_int, version=6)
@@ -0,0 +1,351 @@
1
+ import asyncio
2
+ from collections import defaultdict
3
+ from functools import partial
4
+ from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Tuple
5
+
6
+ from langchain_core.runnables import RunnableConfig
7
+
8
+ from langgraph.checkpoint.base import (
9
+ BaseCheckpointSaver,
10
+ Checkpoint,
11
+ CheckpointMetadata,
12
+ CheckpointTuple,
13
+ SerializerProtocol,
14
+ get_checkpoint_id,
15
+ )
16
+
17
+
18
+ class MemorySaver(BaseCheckpointSaver):
19
+ """An in-memory checkpoint saver.
20
+
21
+ This checkpoint saver stores checkpoints in memory using a defaultdict.
22
+
23
+ Note:
24
+ Since checkpoints are saved in memory, they will be lost when the program exits.
25
+ Only use this saver for debugging or testing purposes.
26
+
27
+ Args:
28
+ serde (Optional[SerializerProtocol]): The serializer to use for serializing and deserializing checkpoints. Defaults to None.
29
+
30
+ Examples:
31
+
32
+ import asyncio
33
+
34
+ from langgraph.checkpoint.memory import MemorySaver
35
+ from langgraph.graph import StateGraph
36
+
37
+ builder = StateGraph(int)
38
+ builder.add_node("add_one", lambda x: x + 1)
39
+ builder.set_entry_point("add_one")
40
+ builder.set_finish_point("add_one")
41
+
42
+ memory = MemorySaver()
43
+ graph = builder.compile(checkpointer=memory)
44
+ coro = graph.ainvoke(1, {"configurable": {"thread_id": "thread-1"}})
45
+ asyncio.run(coro) # Output: 2
46
+ """
47
+
48
+ # thread ID -> checkpoint NS -> checkpoint ID -> checkpoint mapping
49
+ storage: defaultdict[str, dict[str, dict[str, tuple[bytes, bytes, Optional[str]]]]]
50
+
51
+ def __init__(
52
+ self,
53
+ *,
54
+ serde: Optional[SerializerProtocol] = None,
55
+ ) -> None:
56
+ super().__init__(serde=serde)
57
+ self.storage = defaultdict(lambda: defaultdict(dict))
58
+ self.writes = defaultdict(list)
59
+
60
+ def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
61
+ """Get a checkpoint tuple from the in-memory storage.
62
+
63
+ This method retrieves a checkpoint tuple from the in-memory storage based on the
64
+ provided config. If the config contains a "checkpoint_id" key, the checkpoint with
65
+ the matching thread ID and timestamp is retrieved. Otherwise, the latest checkpoint
66
+ for the given thread ID is retrieved.
67
+
68
+ Args:
69
+ config (RunnableConfig): The config to use for retrieving the checkpoint.
70
+
71
+ Returns:
72
+ Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
73
+ """
74
+ thread_id = config["configurable"]["thread_id"]
75
+ checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
76
+ if checkpoint_id := get_checkpoint_id(config):
77
+ if saved := self.storage[thread_id][checkpoint_ns].get(checkpoint_id):
78
+ checkpoint, metadata, parent_checkpoint_id = saved
79
+ writes = self.writes[(thread_id, checkpoint_ns, checkpoint_id)]
80
+ return CheckpointTuple(
81
+ config=config,
82
+ checkpoint=self.serde.loads_typed(checkpoint),
83
+ metadata=self.serde.loads_typed(metadata),
84
+ pending_writes=[
85
+ (id, c, self.serde.loads_typed(v)) for id, c, v in writes
86
+ ],
87
+ parent_config={
88
+ "configurable": {
89
+ "thread_id": thread_id,
90
+ "checkpoint_ns": checkpoint_ns,
91
+ "checkpoint_id": parent_checkpoint_id,
92
+ }
93
+ }
94
+ if parent_checkpoint_id
95
+ else None,
96
+ )
97
+ else:
98
+ if checkpoints := self.storage[thread_id][checkpoint_ns]:
99
+ checkpoint_id = max(checkpoints.keys())
100
+ checkpoint, metadata, parent_checkpoint_id = checkpoints[checkpoint_id]
101
+ writes = self.writes[(thread_id, checkpoint_ns, checkpoint_id)]
102
+ return CheckpointTuple(
103
+ config={
104
+ "configurable": {
105
+ "thread_id": thread_id,
106
+ "checkpoint_ns": checkpoint_ns,
107
+ "checkpoint_id": checkpoint_id,
108
+ }
109
+ },
110
+ checkpoint=self.serde.loads_typed(checkpoint),
111
+ metadata=self.serde.loads_typed(metadata),
112
+ pending_writes=[
113
+ (id, c, self.serde.loads_typed(v)) for id, c, v in writes
114
+ ],
115
+ parent_config={
116
+ "configurable": {
117
+ "thread_id": thread_id,
118
+ "checkpoint_ns": checkpoint_ns,
119
+ "checkpoint_id": parent_checkpoint_id,
120
+ }
121
+ }
122
+ if parent_checkpoint_id
123
+ else None,
124
+ )
125
+
126
+ def list(
127
+ self,
128
+ config: Optional[RunnableConfig],
129
+ *,
130
+ filter: Optional[Dict[str, Any]] = None,
131
+ before: Optional[RunnableConfig] = None,
132
+ limit: Optional[int] = None,
133
+ ) -> Iterator[CheckpointTuple]:
134
+ """List checkpoints from the in-memory storage.
135
+
136
+ This method retrieves a list of checkpoint tuples from the in-memory storage based
137
+ on the provided criteria.
138
+
139
+ Args:
140
+ config (Optional[RunnableConfig]): Base configuration for filtering checkpoints.
141
+ filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata.
142
+ before (Optional[RunnableConfig]): List checkpoints created before this configuration.
143
+ limit (Optional[int]): Maximum number of checkpoints to return.
144
+
145
+ Yields:
146
+ Iterator[CheckpointTuple]: An iterator of matching checkpoint tuples.
147
+ """
148
+ thread_ids = (config["configurable"]["thread_id"],) if config else self.storage
149
+ checkpoint_ns = (
150
+ config["configurable"].get("checkpoint_ns", "") if config else ""
151
+ )
152
+ for thread_id in thread_ids:
153
+ for checkpoint_id, (checkpoint, metadata_b, parent_checkpoint_id) in sorted(
154
+ self.storage[thread_id][checkpoint_ns].items(),
155
+ key=lambda x: x[0],
156
+ reverse=True,
157
+ ):
158
+ # filter by checkpoint ID
159
+ if (
160
+ before
161
+ and (before_checkpoint_id := get_checkpoint_id(before))
162
+ and checkpoint_id >= before_checkpoint_id
163
+ ):
164
+ continue
165
+
166
+ # filter by metadata
167
+ metadata = self.serde.loads_typed(metadata_b)
168
+ if filter and not all(
169
+ query_value == metadata[query_key]
170
+ for query_key, query_value in filter.items()
171
+ ):
172
+ continue
173
+
174
+ # limit search results
175
+ if limit is not None and limit <= 0:
176
+ break
177
+ elif limit is not None:
178
+ limit -= 1
179
+
180
+ yield CheckpointTuple(
181
+ config={
182
+ "configurable": {
183
+ "thread_id": thread_id,
184
+ "checkpoint_ns": checkpoint_ns,
185
+ "checkpoint_id": checkpoint_id,
186
+ }
187
+ },
188
+ checkpoint=self.serde.loads_typed(checkpoint),
189
+ metadata=metadata,
190
+ parent_config={
191
+ "configurable": {
192
+ "thread_id": thread_id,
193
+ "checkpoint_ns": checkpoint_ns,
194
+ "checkpoint_id": parent_checkpoint_id,
195
+ }
196
+ }
197
+ if parent_checkpoint_id
198
+ else None,
199
+ )
200
+
201
+ def put(
202
+ self,
203
+ config: RunnableConfig,
204
+ checkpoint: Checkpoint,
205
+ metadata: CheckpointMetadata,
206
+ ) -> RunnableConfig:
207
+ """Save a checkpoint to the in-memory storage.
208
+
209
+ This method saves a checkpoint to the in-memory storage. The checkpoint is associated
210
+ with the provided config.
211
+
212
+ Args:
213
+ config (RunnableConfig): The config to associate with the checkpoint.
214
+ checkpoint (Checkpoint): The checkpoint to save.
215
+ metadata (CheckpointMetadata): Additional metadata to save with the checkpoint.
216
+
217
+ Returns:
218
+ RunnableConfig: The updated config containing the saved checkpoint's timestamp.
219
+ """
220
+ thread_id = config["configurable"]["thread_id"]
221
+ checkpoint_ns = config["configurable"]["checkpoint_ns"]
222
+ self.storage[thread_id][checkpoint_ns].update(
223
+ {
224
+ checkpoint["id"]: (
225
+ self.serde.dumps_typed(checkpoint),
226
+ self.serde.dumps_typed(metadata),
227
+ config["configurable"].get("checkpoint_id"), # parent
228
+ )
229
+ }
230
+ )
231
+ return {
232
+ "configurable": {
233
+ "thread_id": thread_id,
234
+ "checkpoint_ns": checkpoint_ns,
235
+ "checkpoint_id": checkpoint["id"],
236
+ }
237
+ }
238
+
239
+ def put_writes(
240
+ self,
241
+ config: RunnableConfig,
242
+ writes: List[Tuple[str, Any]],
243
+ task_id: str,
244
+ ) -> RunnableConfig:
245
+ """Save a list of writes to the in-memory storage.
246
+
247
+ This method saves a list of writes to the in-memory storage. The writes are associated
248
+ with the provided config.
249
+
250
+ Args:
251
+ config (RunnableConfig): The config to associate with the writes.
252
+ writes (list[tuple[str, Any]]): The writes to save.
253
+ task_id (str): Identifier for the task creating the writes.
254
+
255
+ Returns:
256
+ RunnableConfig: The updated config containing the saved writes' timestamp.
257
+ """
258
+ thread_id = config["configurable"]["thread_id"]
259
+ checkpoint_ns = config["configurable"]["checkpoint_ns"]
260
+ checkpoint_id = config["configurable"]["checkpoint_id"]
261
+ key = (thread_id, checkpoint_ns, checkpoint_id)
262
+ self.writes[key].extend(
263
+ [(task_id, c, self.serde.dumps_typed(v)) for c, v in writes]
264
+ )
265
+
266
+ async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
267
+ """Asynchronous version of get_tuple.
268
+
269
+ This method is an asynchronous wrapper around get_tuple that runs the synchronous
270
+ method in a separate thread using asyncio.
271
+
272
+ Args:
273
+ config (RunnableConfig): The config to use for retrieving the checkpoint.
274
+
275
+ Returns:
276
+ Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.
277
+ """
278
+ return await asyncio.get_running_loop().run_in_executor(
279
+ None, self.get_tuple, config
280
+ )
281
+
282
+ async def alist(
283
+ self,
284
+ config: Optional[RunnableConfig],
285
+ *,
286
+ filter: Optional[Dict[str, Any]] = None,
287
+ before: Optional[RunnableConfig] = None,
288
+ limit: Optional[int] = None,
289
+ ) -> AsyncIterator[CheckpointTuple]:
290
+ """Asynchronous version of list.
291
+
292
+ This method is an asynchronous wrapper around list that runs the synchronous
293
+ method in a separate thread using asyncio.
294
+
295
+ Args:
296
+ config (RunnableConfig): The config to use for listing the checkpoints.
297
+
298
+ Yields:
299
+ AsyncIterator[CheckpointTuple]: An asynchronous iterator of checkpoint tuples.
300
+ """
301
+ loop = asyncio.get_running_loop()
302
+ iter = await loop.run_in_executor(
303
+ None, partial(self.list, before=before, limit=limit, filter=filter), config
304
+ )
305
+ while True:
306
+ # handling StopIteration exception inside coroutine won't work
307
+ # as expected, so using next() with default value to break the loop
308
+ if item := await loop.run_in_executor(None, next, iter, None):
309
+ yield item
310
+ else:
311
+ break
312
+
313
+ async def aput(
314
+ self,
315
+ config: RunnableConfig,
316
+ checkpoint: Checkpoint,
317
+ metadata: CheckpointMetadata,
318
+ ) -> RunnableConfig:
319
+ """Asynchronous version of put.
320
+
321
+ Args:
322
+ config (RunnableConfig): The config to associate with the checkpoint.
323
+ checkpoint (Checkpoint): The checkpoint to save.
324
+ metadata (CheckpointMetadata): Additional metadata to save with the checkpoint.
325
+
326
+ Returns:
327
+ RunnableConfig: The updated config containing the saved checkpoint's timestamp.
328
+ """
329
+ return await asyncio.get_running_loop().run_in_executor(
330
+ None, self.put, config, checkpoint, metadata
331
+ )
332
+
333
+ async def aput_writes(
334
+ self,
335
+ config: RunnableConfig,
336
+ writes: List[Tuple[str, Any]],
337
+ task_id: str,
338
+ ) -> RunnableConfig:
339
+ """Asynchronous version of put_writes.
340
+
341
+ This method is an asynchronous wrapper around put_writes that runs the synchronous
342
+ method in a separate thread using asyncio.
343
+
344
+ Args:
345
+ config (RunnableConfig): The config to associate with the writes.
346
+ writes (List[Tuple[str, Any]]): The writes to save, each as a (channel, value) pair.
347
+ task_id (str): Identifier for the task creating the writes.
348
+ """
349
+ return await asyncio.get_running_loop().run_in_executor(
350
+ None, self.put_writes, config, writes, task_id
351
+ )
File without changes
File without changes
@@ -0,0 +1,45 @@
1
+ from typing import Any, Protocol
2
+
3
+
4
+ class SerializerProtocol(Protocol):
5
+ """Protocol for serialization and deserialization of objects.
6
+
7
+ - `dumps`: Serialize an object to bytes.
8
+ - `dumps_typed`: Serialize an object to a tuple (type, bytes).
9
+ - `loads`: Deserialize an object from bytes.
10
+ - `loads_typed`: Deserialize an object from a tuple (type, bytes).
11
+
12
+ Valid implementations include the `pickle`, `json` and `orjson` modules.
13
+ """
14
+
15
+ def dumps(self, obj: Any) -> bytes:
16
+ ...
17
+
18
+ def dumps_typed(self, obj: Any) -> tuple[str, bytes]:
19
+ ...
20
+
21
+ def loads(self, data: bytes) -> Any:
22
+ ...
23
+
24
+ def loads_typed(self, data: tuple[str, bytes]) -> Any:
25
+ ...
26
+
27
+
28
+ class SerializerCompat(SerializerProtocol):
29
+ def __init__(self, serde: SerializerProtocol) -> None:
30
+ self.serde = serde
31
+
32
+ def dumps_typed(self, obj: Any) -> tuple[str, bytes]:
33
+ return type(obj).__name__, self.serde.dumps(obj)
34
+
35
+ def loads_typed(self, data: tuple[str, bytes]) -> Any:
36
+ return self.serde.loads(data[1])
37
+
38
+
39
+ def maybe_add_typed_methods(serde: SerializerProtocol) -> SerializerProtocol:
40
+ """Wrap serde old serde implementations in a class with loads_typed and dumps_typed for backwards compatibility."""
41
+
42
+ if not hasattr(serde, "loads_typed") or not hasattr(serde, "dumps_typed"):
43
+ return SerializerCompat(serde)
44
+
45
+ return serde
@@ -0,0 +1,112 @@
1
+ import dataclasses
2
+ import importlib
3
+ import json
4
+ from datetime import datetime, timedelta, timezone
5
+ from enum import Enum
6
+ from typing import Any, Optional
7
+ from uuid import UUID
8
+
9
+ from langchain_core.load.load import Reviver
10
+ from langchain_core.load.serializable import Serializable
11
+
12
+ from langgraph.checkpoint.serde.base import SerializerProtocol
13
+ from langgraph.checkpoint.serde.types import SendProtocol
14
+
15
+ LC_REVIVER = Reviver()
16
+
17
+
18
+ class JsonPlusSerializer(SerializerProtocol):
19
+ def _encode_constructor_args(
20
+ self,
21
+ constructor: type[Any],
22
+ *,
23
+ method: Optional[str] = None,
24
+ args: Optional[list[Any]] = None,
25
+ kwargs: Optional[dict[str, Any]] = None,
26
+ ):
27
+ return {
28
+ "lc": 2,
29
+ "type": "constructor",
30
+ "id": [*constructor.__module__.split("."), constructor.__name__],
31
+ "method": method,
32
+ "args": args if args is not None else [],
33
+ "kwargs": kwargs if kwargs is not None else {},
34
+ }
35
+
36
+ def _default(self, obj):
37
+ if isinstance(obj, Serializable):
38
+ return obj.to_json()
39
+ elif hasattr(obj, "model_dump") and callable(obj.model_dump):
40
+ return self._encode_constructor_args(obj.__class__, kwargs=obj.model_dump())
41
+ elif hasattr(obj, "dict") and callable(obj.dict):
42
+ return self._encode_constructor_args(obj.__class__, kwargs=obj.dict())
43
+ elif isinstance(obj, UUID):
44
+ return self._encode_constructor_args(UUID, args=[obj.hex])
45
+ elif isinstance(obj, (set, frozenset)):
46
+ return self._encode_constructor_args(type(obj), args=[list(obj)])
47
+ elif isinstance(obj, datetime):
48
+ return self._encode_constructor_args(
49
+ datetime, method="fromisoformat", args=[obj.isoformat()]
50
+ )
51
+ elif isinstance(obj, timezone):
52
+ return self._encode_constructor_args(timezone, args=obj.__getinitargs__())
53
+ elif isinstance(obj, timedelta):
54
+ return self._encode_constructor_args(
55
+ timedelta, args=[obj.days, obj.seconds, obj.microseconds]
56
+ )
57
+ elif dataclasses.is_dataclass(obj):
58
+ return self._encode_constructor_args(
59
+ obj.__class__,
60
+ kwargs={
61
+ field.name: getattr(obj, field.name)
62
+ for field in dataclasses.fields(obj)
63
+ },
64
+ )
65
+ elif isinstance(obj, Enum):
66
+ return self._encode_constructor_args(obj.__class__, args=[obj.value])
67
+ elif isinstance(obj, SendProtocol):
68
+ return self._encode_constructor_args(
69
+ obj.__class__, kwargs={"node": obj.node, "arg": obj.arg}
70
+ )
71
+ else:
72
+ raise TypeError(
73
+ f"Object of type {obj.__class__.__name__} is not JSON serializable"
74
+ )
75
+
76
+ def _reviver(self, value: dict[str, Any]) -> Any:
77
+ if (
78
+ value.get("lc", None) == 2
79
+ and value.get("type", None) == "constructor"
80
+ and value.get("id", None) is not None
81
+ ):
82
+ # Get module and class name
83
+ [*module, name] = value["id"]
84
+ # Import module
85
+ mod = importlib.import_module(".".join(module))
86
+ # Import class
87
+ cls = getattr(mod, name)
88
+ # Instantiate class
89
+ if value["method"] is not None:
90
+ method = getattr(cls, value["method"])
91
+ return method(*value["args"], **value["kwargs"])
92
+ else:
93
+ return cls(*value["args"], **value["kwargs"])
94
+
95
+ return LC_REVIVER(value)
96
+
97
+ def dumps(self, obj: Any) -> bytes:
98
+ return json.dumps(obj, default=self._default, ensure_ascii=False).encode(
99
+ "utf-8", "ignore"
100
+ )
101
+
102
+ def dumps_typed(self, obj: Any) -> tuple[str, bytes]:
103
+ return "json", self.dumps(obj)
104
+
105
+ def loads(self, data: bytes) -> Any:
106
+ return json.loads(data, object_hook=self._reviver)
107
+
108
+ def loads_typed(self, data: tuple[str, bytes]) -> Any:
109
+ type_, data_ = data
110
+ if type_ != "json":
111
+ raise ValueError("JsonPlusSerializer can only deserialize `json` data")
112
+ return self.loads(data_)
@@ -0,0 +1,66 @@
1
+ from typing import (
2
+ Any,
3
+ AsyncGenerator,
4
+ Generator,
5
+ Optional,
6
+ Protocol,
7
+ Sequence,
8
+ TypeVar,
9
+ runtime_checkable,
10
+ )
11
+
12
+ from langchain_core.runnables import RunnableConfig
13
+ from typing_extensions import Self
14
+
15
+ Value = TypeVar("Value")
16
+ Update = TypeVar("Update")
17
+ C = TypeVar("C")
18
+
19
+
20
+ class ChannelProtocol(Protocol[Value, Update, C]):
21
+ # Mirrors langgraph.channels.base.BaseChannel
22
+ @property
23
+ def ValueType(self) -> Any:
24
+ ...
25
+
26
+ @property
27
+ def UpdateType(self) -> Any:
28
+ ...
29
+
30
+ def checkpoint(self) -> Optional[C]:
31
+ ...
32
+
33
+ def from_checkpoint(
34
+ self, checkpoint: Optional[C], config: RunnableConfig
35
+ ) -> Generator[Self, None, None]:
36
+ ...
37
+
38
+ async def afrom_checkpoint(
39
+ self, checkpoint: Optional[C], config: RunnableConfig
40
+ ) -> AsyncGenerator[Self, None]:
41
+ ...
42
+
43
+ def update(self, values: Sequence[Update]) -> bool:
44
+ ...
45
+
46
+ def get(self) -> Value:
47
+ ...
48
+
49
+ def consume(self) -> bool:
50
+ ...
51
+
52
+
53
+ @runtime_checkable
54
+ class SendProtocol(Protocol):
55
+ # Mirrors langgraph.constants.Send
56
+ node: str
57
+ arg: Any
58
+
59
+ def __hash__(self) -> int:
60
+ ...
61
+
62
+ def __repr__(self) -> str:
63
+ ...
64
+
65
+ def __eq__(self, value: object) -> bool:
66
+ ...
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 LangChain, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.1
2
+ Name: langgraph-checkpoint
3
+ Version: 1.0.0
4
+ Summary: Library with base interfaces for LangGraph checkpoint savers.
5
+ Home-page: https://www.github.com/langchain-ai/langgraph
6
+ License: MIT
7
+ Requires-Python: >=3.9.0,<4.0.0
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Dist: langchain-core (>=0.2.22,<0.3)
15
+ Project-URL: Repository, https://www.github.com/langchain-ai/langgraph
16
+ Description-Content-Type: text/markdown
17
+
18
+ # LangGraph Checkpoint
19
+
20
+ This library defines the base interface for LangGraph checkpointers. Checkpointers provide persistence layer for LangGraph. They allow you to interact with and manage the graph's state. When you use a graph with a checkpointer, the checkpointer saves a _checkpoint_ of the graph state at every superstep, enabling several powerful capabilities like human-in-the-loop, "memory" between interactions and more.
21
+
22
+ ## Key concepts
23
+
24
+ ### Checkpoint
25
+
26
+ Checkpoint is a snapshot of the graph state at a given point in time. Checkpoint tuple refers to an object containing checkpoint and the associated config, metadata and pending writes.
27
+
28
+ ### Thread
29
+
30
+ Threads enable the checkpointing of multiple different runs, making them essential for multi-tenant chat applications and other scenarios where maintaining separate states is necessary. A thread is a unique ID assigned to a series of checkpoints saved by a checkpointer. When using a checkpointer, you must specify a `thread_id` and optionally `checkpoint_id` when running the graph.
31
+
32
+ - `thread_id` is simply the ID of a thread. This is always required
33
+ - `checkpoint_id` can optionally be passed. This identifier refers to a specific checkpoint within a thread. This can be used to kick of a run of a graph from some point halfway through a thread.
34
+
35
+ You must pass these when invoking the graph as part of the configurable part of the config, e.g.
36
+
37
+ ```python
38
+ {"configurable": {"thread_id": "1"}} # valid config
39
+ {"configurable": {"thread_id": "1", "checkpoint_id": "0c62ca34-ac19-445d-bbb0-5b4984975b2a"}} # also valid config
40
+ ```
41
+
42
+ ### Serde
43
+
44
+ `langgraph_checkpoint` also defines protocol for serialization/deserialization (serde) and provides an default implementation (`langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer`) that handles a wide variety of types, including LangChain and LangGraph primitives, datetimes, enums and more.
45
+
46
+ ### Pending writes
47
+
48
+ When a graph node fails mid-execution at a given superstep, LangGraph stores pending checkpoint writes from any other nodes that completed successfully at that superstep, so that whenever we resume graph execution from that superstep we don't re-run the successful nodes.
49
+
50
+ ## Interface
51
+
52
+ Each checkpointer should conform to `langgraph.checkpoint.base.BaseCheckpointSaver` interface and must implement the following methods:
53
+
54
+ - `.put` - Store a checkpoint with its configuration and metadata.
55
+ - `.put_writes` - Store intermediate writes linked to a checkpoint (i.e. pending writes).
56
+ - `.get_tuple` - Fetch a checkpoint tuple using for a given configuration (`thread_id` and `thread_ts`).
57
+ - `.list` - List checkpoints that match a given configuration and filter criteria.
58
+
59
+ If the checkpointer will be used with asynchronous graph execution (i.e. executing the graph via `.ainvoke`, `.astream`, `.abatch`), checkpointer must implement asynchronous versions of the above methods (`.aput`, `.aput_writes`, `.aget_tuple`, `.alist`).
60
+
61
+ ## Usage
62
+
63
+ ```python
64
+ from langgraph.checkpoint.memory import MemorySaver
65
+
66
+ checkpointer = MemorySaver()
67
+ checkpoint = {
68
+ "v": 1,
69
+ "ts": "2024-07-31T20:14:19.804150+00:00",
70
+ "id": "1ef4f797-8335-6428-8001-8a1503f9b875",
71
+ "channel_values": {
72
+ "my_key": "meow",
73
+ "node": "node"
74
+ },
75
+ "channel_versions": {
76
+ "__start__": 2,
77
+ "my_key": 3,
78
+ "start:node": 3,
79
+ "node": 3
80
+ },
81
+ "versions_seen": {
82
+ "__input__": {},
83
+ "__start__": {
84
+ "__start__": 1
85
+ },
86
+ "node": {
87
+ "start:node": 2
88
+ }
89
+ },
90
+ "pending_sends": [],
91
+ "current_tasks": {}
92
+ }
93
+
94
+ # store checkpoint
95
+ checkpointer.put(thread_config, checkpoint, {})
96
+
97
+ # load checkpoint
98
+ checkpointer.get(thread_config)
99
+
100
+ # list checkpoints
101
+ list(checkpointer.list(thread_config))
102
+ ```
103
+
@@ -0,0 +1,12 @@
1
+ langgraph/checkpoint/base/__init__.py,sha256=BUx44KPtMETXa80VToEyF4p72IEUGUDB-QO-fIK91ko,14301
2
+ langgraph/checkpoint/base/id.py,sha256=LpzsCxAwjOAVK3tTQqnuV2f7NXV2V7z3HuIe1SDHqBU,3667
3
+ langgraph/checkpoint/memory.py,sha256=5onxgUIKJtaKJodSPoOcoeYc85BW2tKmM2pZTEQ0BS4,13696
4
+ langgraph/checkpoint/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ langgraph/checkpoint/serde/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ langgraph/checkpoint/serde/base.py,sha256=Iow-xSaQppHUlE2Wffn3KGWhkYBthcnVIo1WtO6Rwfk,1392
7
+ langgraph/checkpoint/serde/jsonplus.py,sha256=0bvmxTbQKIghi49eccTVca4MNcLa3Q52vxgYkw8YgFA,4228
8
+ langgraph/checkpoint/serde/types.py,sha256=zji7QcYQCzmj1NnU4aC_zdFQJ-skYkENPK-i9KjMEbg,1277
9
+ langgraph_checkpoint-1.0.0.dist-info/LICENSE,sha256=2btS8uNUDWD_UNjw9ba6ZJt_00aUjEw9CGyK-xIHY8c,1072
10
+ langgraph_checkpoint-1.0.0.dist-info/METADATA,sha256=OM6wQ-AQceYHsqRlkXWzCh9ctgeUk_cLgJe6X5D1gLM,4401
11
+ langgraph_checkpoint-1.0.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
12
+ langgraph_checkpoint-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.8.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any