Remove Checkpoint.writes

- This has been superseded by saving the individual writes of each task through put_writes()
- Removing this speeds up checkpoint operations as it was duplicating data saved elsewhere already
This commit is contained in:
Nuno Campos
2025-05-25 19:06:46 -07:00
parent 4e8fbe4525
commit 936176eb21
15 changed files with 36 additions and 1399 deletions
@@ -181,11 +181,11 @@ class PostgresSaver(BasePostgresSaver):
"checkpoint_id": value["checkpoint_id"],
}
},
self._load_checkpoint(
value["checkpoint"],
value["channel_values"],
),
self._load_metadata(value["metadata"]),
{
**value["checkpoint"],
"channel_values": self._load_blobs(value["channel_values"]),
},
value["metadata"],
(
{
"configurable": {
@@ -277,11 +277,11 @@ class PostgresSaver(BasePostgresSaver):
"checkpoint_id": value["checkpoint_id"],
}
},
self._load_checkpoint(
value["checkpoint"],
value["channel_values"],
),
self._load_metadata(value["metadata"]),
{
**value["checkpoint"],
"channel_values": self._load_blobs(value["channel_values"]),
},
value["metadata"],
(
{
"configurable": {
@@ -361,8 +361,8 @@ class PostgresSaver(BasePostgresSaver):
checkpoint_ns,
checkpoint["id"],
checkpoint_id,
Jsonb(self._dump_checkpoint(copy)),
self._dump_metadata(get_checkpoint_metadata(config, metadata)),
Jsonb(copy),
Jsonb(get_checkpoint_metadata(config, metadata)),
),
)
return next_config
@@ -168,12 +168,11 @@ class AsyncPostgresSaver(BasePostgresSaver):
"checkpoint_id": value["checkpoint_id"],
}
},
await asyncio.to_thread(
self._load_checkpoint,
value["checkpoint"],
value["channel_values"],
),
self._load_metadata(value["metadata"]),
{
**value["checkpoint"],
"channel_values": self._load_blobs(value["channel_values"]),
},
value["metadata"],
(
{
"configurable": {
@@ -245,12 +244,11 @@ class AsyncPostgresSaver(BasePostgresSaver):
"checkpoint_id": value["checkpoint_id"],
}
},
await asyncio.to_thread(
self._load_checkpoint,
value["checkpoint"],
value["channel_values"],
),
self._load_metadata(value["metadata"]),
{
**value["checkpoint"],
"channel_values": self._load_blobs(value["channel_values"]),
},
value["metadata"],
(
{
"configurable": {
@@ -320,8 +318,8 @@ class AsyncPostgresSaver(BasePostgresSaver):
checkpoint_ns,
checkpoint["id"],
checkpoint_id,
Jsonb(self._dump_checkpoint(copy)),
self._dump_metadata(get_checkpoint_metadata(config, metadata)),
Jsonb(copy),
Jsonb(get_checkpoint_metadata(config, metadata)),
),
)
return next_config
@@ -9,11 +9,8 @@ from langgraph.checkpoint.base import (
WRITES_IDX_MAP,
BaseCheckpointSaver,
ChannelVersions,
Checkpoint,
CheckpointMetadata,
get_checkpoint_id,
)
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
from langgraph.checkpoint.serde.types import TASKS
MetadataInput = Optional[dict[str, Any]]
@@ -150,7 +147,6 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
UPSERT_CHECKPOINT_WRITES_SQL = UPSERT_CHECKPOINT_WRITES_SQL
INSERT_CHECKPOINT_WRITES_SQL = INSERT_CHECKPOINT_WRITES_SQL
jsonplus_serde = JsonPlusSerializer()
supports_pipeline: bool
def _migrate_pending_sends(
@@ -173,19 +169,6 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
else self.get_next_version(None)
)
def _load_checkpoint(
self,
checkpoint: dict[str, Any],
channel_values: list[tuple[bytes, bytes, bytes]],
) -> Checkpoint:
return {
**checkpoint,
"channel_values": self._load_blobs(channel_values),
}
def _dump_checkpoint(self, checkpoint: Checkpoint) -> dict[str, Any]:
return checkpoint
def _load_blobs(
self, blob_values: list[tuple[bytes, bytes, bytes]]
) -> dict[str, Any]:
@@ -261,14 +244,6 @@ class BasePostgresSaver(BaseCheckpointSaver[str]):
for idx, (channel, value) in enumerate(writes)
]
def _load_metadata(self, metadata: dict[str, Any]) -> CheckpointMetadata:
return self.jsonplus_serde.loads(self.jsonplus_serde.dumps(metadata))
def _dump_metadata(self, metadata: CheckpointMetadata) -> str:
serialized_metadata = self.jsonplus_serde.dumps(metadata)
# NOTE: we're using JSON serializer (not msgpack), so we need to remove null characters before writing
return serialized_metadata.decode().replace("\\u0000", "")
def get_next_version(self, current: Optional[str]) -> str:
if current is None:
current_v = 0
@@ -45,11 +45,6 @@ class CheckpointMetadata(TypedDict, total=False):
0 for the first "loop" checkpoint.
... for the nth checkpoint afterwards.
"""
writes: dict[str, Any]
"""The writes that were made between the previous checkpoint and this one.
Mapping from node name to writes emitted by that node.
"""
parents: dict[str, str]
"""The IDs of the parent checkpoints.
@@ -377,7 +372,10 @@ def get_checkpoint_metadata(
config: RunnableConfig, metadata: CheckpointMetadata
) -> CheckpointMetadata:
"""Get checkpoint metadata in a backwards-compatible manner."""
metadata = metadata.copy()
metadata = {
k: v.replace("\u0000", "") if isinstance(v, str) else v
for k, v in metadata.items()
}
for obj in (config.get("metadata"), config.get("configurable")):
if not obj:
continue
@@ -385,8 +383,10 @@ def get_checkpoint_metadata(
if key in metadata or key in EXCLUDED_METADATA_KEYS or key.startswith("__"):
continue
v = obj[key]
if isinstance(v, (str, int, bool, float)):
metadata[key] = v # type: ignore[literal-required]
if isinstance(v, str):
metadata[key] = v.replace("\u0000", "")
elif isinstance(v, (int, bool, float)):
metadata[key] = v
return metadata
+1 -1
View File
@@ -8,7 +8,7 @@ from langgraph.checkpoint.base import (
CheckpointMetadata,
)
from langgraph.checkpoint.memory import InMemorySaver
from tests.checkpoint_utils import ( # type: ignore[import-untyped]
from tests.checkpoint_utils import (
create_checkpoint,
empty_checkpoint,
)
@@ -1559,7 +1559,6 @@ class Pregel(PregelProtocol):
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"writes": {},
"parents": saved.metadata.get("parents", {}) if saved else {},
},
{},
@@ -1583,7 +1582,6 @@ class Pregel(PregelProtocol):
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"writes": {},
"parents": saved.metadata.get("parents", {}) if saved else {},
},
{},
@@ -1621,7 +1619,6 @@ class Pregel(PregelProtocol):
**checkpoint_metadata,
"source": "input",
"step": next_step,
"writes": dict(input_writes),
},
get_new_channel_versions(
checkpoint_previous_versions,
@@ -1813,7 +1810,6 @@ class Pregel(PregelProtocol):
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"writes": {as_node: values for as_node, values in valid_updates},
"parents": saved.metadata.get("parents", {}) if saved else {},
},
get_new_channel_versions(
@@ -1974,7 +1970,6 @@ class Pregel(PregelProtocol):
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"writes": {},
"parents": saved.metadata.get("parents", {}) if saved else {},
},
{},
@@ -1998,7 +1993,6 @@ class Pregel(PregelProtocol):
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"writes": {},
"parents": saved.metadata.get("parents", {}) if saved else {},
},
{},
@@ -2036,7 +2030,6 @@ class Pregel(PregelProtocol):
**checkpoint_metadata,
"source": "input",
"step": next_step,
"writes": dict(input_writes),
},
get_new_channel_versions(
checkpoint_previous_versions,
@@ -2226,7 +2219,6 @@ class Pregel(PregelProtocol):
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"writes": {as_node: values for as_node, values in valid_updates},
"parents": saved.metadata.get("parents", {}) if saved else {},
},
get_new_channel_versions(
+1 -9
View File
@@ -1,6 +1,6 @@
from collections import Counter
from collections.abc import Iterator, Mapping, Sequence
from typing import Any, Literal, Optional, TypeVar, Union
from typing import Any, Literal, Optional, Union
from langgraph.channels.base import BaseChannel, EmptyChannelError
from langgraph.constants import (
@@ -172,11 +172,3 @@ def map_output_updates(
if cached:
grouped["__metadata__"] = {"cached": cached}
yield grouped
T = TypeVar("T")
def single(iter: Iterator[T]) -> Optional[T]:
for item in iter:
return item
+2 -13
View File
@@ -112,7 +112,6 @@ from langgraph.pregel.io import (
map_output_updates,
map_output_values,
read_channels,
single,
)
from langgraph.pregel.read import PregelNode
from langgraph.pregel.utils import get_new_channel_versions, is_xxh3_128_hexdigest
@@ -520,17 +519,7 @@ class PregelLoop:
# "not skip_done_tasks" only applies to first tick after resuming
self.skip_done_tasks = True
# save checkpoint
self._put_checkpoint(
{
"source": "loop",
"writes": single(
map_output_updates(
self.output_keys,
[(t, t.writes) for t in self.tasks.values()],
)
),
}
)
self._put_checkpoint({"source": "loop"})
# after execution, check if we should interrupt
if self.interrupt_after and should_interrupt(
self.checkpoint, self.interrupt_after, self.tasks.values()
@@ -760,7 +749,7 @@ class PregelLoop:
self.trigger_to_nodes,
)
# save input checkpoint
self._put_checkpoint({"source": "input", "writes": dict(input_writes)})
self._put_checkpoint({"source": "input"})
# set flag
if (
self.input_model is not None
@@ -44,7 +44,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
},
metadata={
"source": "loop",
"writes": {"qa": {"answer": "doc1,doc2,doc3,doc4"}},
"step": 4,
"parents": {},
"thread_id": "1",
@@ -75,7 +74,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
},
metadata={
"source": "loop",
"writes": {"retriever_one": {"docs": ["doc1", "doc2"]}},
"step": 3,
"parents": {},
"thread_id": "1",
@@ -134,10 +132,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
},
metadata={
"source": "loop",
"writes": {
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
"retriever_two": {"docs": ["doc3", "doc4"]},
},
"step": 2,
"parents": {},
"thread_id": "1",
@@ -175,7 +169,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
},
metadata={
"source": "loop",
"writes": {"rewrite_query": {"query": "query: what is weather in sf"}},
"step": 1,
"parents": {},
"thread_id": "1",
@@ -226,7 +219,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
},
metadata={
"source": "loop",
"writes": None,
"step": 0,
"parents": {},
"thread_id": "1",
@@ -266,7 +258,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
},
metadata={
"source": "input",
"writes": {"__start__": {"query": "what is weather in sf"}},
"step": -1,
"parents": {},
"thread_id": "1",
@@ -353,7 +344,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {"qa": {"answer": "doc1,doc2,doc3,doc4"}},
"step": 4,
"parents": {},
"thread_id": "1",
@@ -415,7 +405,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {"retriever_one": {"docs": ["doc1", "doc2"]}},
"step": 3,
"parents": {},
"thread_id": "1",
@@ -492,10 +481,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
"retriever_two": {"docs": ["doc3", "doc4"]},
},
"step": 2,
"parents": {},
"thread_id": "1",
@@ -548,7 +533,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {"rewrite_query": {"query": "query: what is weather in sf"}},
"step": 1,
"parents": {},
"thread_id": "1",
@@ -604,7 +588,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": None,
"step": 0,
"parents": {},
"thread_id": "1",
@@ -654,7 +637,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "input",
"writes": {"__start__": {"query": "what is weather in sf"}},
"step": -1,
"parents": {},
"thread_id": "1",
@@ -741,7 +723,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {"qa": {"answer": "doc1,doc2,doc3,doc4"}},
"thread_id": "1",
"step": 4,
"parents": {},
@@ -804,7 +785,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {"retriever_one": {"docs": ["doc1", "doc2"]}},
"thread_id": "1",
"step": 3,
"parents": {},
@@ -884,10 +864,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
"retriever_two": {"docs": ["doc3", "doc4"]},
},
"thread_id": "1",
"step": 2,
"parents": {},
@@ -944,7 +920,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {"rewrite_query": {"query": "query: what is weather in sf"}},
"thread_id": "1",
"step": 1,
"parents": {},
@@ -1005,7 +980,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": None,
"thread_id": "1",
"step": 0,
"parents": {},
@@ -1055,7 +1029,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "input",
"writes": {"__start__": {"query": "what is weather in sf"}},
"thread_id": "1",
"step": -1,
"parents": {},
@@ -1142,7 +1115,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {"qa": {"answer": "doc1,doc2,doc3,doc4"}},
"thread_id": "1",
"step": 4,
"parents": {},
@@ -1205,7 +1177,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {"retriever_one": {"docs": ["doc1", "doc2"]}},
"thread_id": "1",
"step": 3,
"parents": {},
@@ -1285,10 +1256,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {
"analyzer_one": {"query": "analyzed: query: what is weather in sf"},
"retriever_two": {"docs": ["doc3", "doc4"]},
},
"thread_id": "1",
"step": 2,
"parents": {},
@@ -1345,7 +1312,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": {"rewrite_query": {"query": "query: what is weather in sf"}},
"thread_id": "1",
"step": 1,
"parents": {},
@@ -1406,7 +1372,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"writes": None,
"thread_id": "1",
"step": 0,
"parents": {},
@@ -1456,7 +1421,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "input",
"writes": {"__start__": {"query": "what is weather in sf"}},
"thread_id": "1",
"step": -1,
"parents": {},
-18
View File
@@ -1,18 +0,0 @@
from collections.abc import Iterator
from langgraph.pregel.io import single
def test_single() -> None:
closed = False
def myiter() -> Iterator[int]:
try:
yield 1
yield 2
finally:
nonlocal closed
closed = True
assert single(myiter()) == 1
assert closed
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-36
View File
@@ -1071,7 +1071,6 @@ def test_pending_writes_resume(
"parents": {},
"source": "loop",
"step": 0,
"writes": None,
"thread_id": "1",
}
# get_state with checkpoint_id should not apply any pending writes
@@ -1162,7 +1161,6 @@ def test_pending_writes_resume(
"parents": {},
"step": 1,
"source": "loop",
"writes": {"one": {"value": 2}, "two": {"value": 3}},
"thread_id": "1",
},
parent_config={
@@ -1211,7 +1209,6 @@ def test_pending_writes_resume(
"parents": {},
"step": 0,
"source": "loop",
"writes": None,
"thread_id": "1",
},
parent_config={
@@ -1264,7 +1261,6 @@ def test_pending_writes_resume(
"parents": {},
"step": -1,
"source": "input",
"writes": {"__start__": {"value": 1}},
"thread_id": "1",
},
parent_config=None,
@@ -2273,7 +2269,6 @@ def test_in_one_fan_out_state_graph_waiting_edge(
"parents": {},
"source": "update",
"step": 4,
"writes": {"retriever_one": {"docs": ["doc5"]}},
"thread_id": "2",
},
parent_config=expected_parent_config,
@@ -2544,7 +2539,6 @@ def test_in_one_fan_out_state_graph_defer_node(
"parents": {},
"source": "update",
"step": 4,
"writes": {"analyzer_one": {"docs": ["doc5"]}},
"thread_id": "2",
},
parent_config=expected_parent_config,
@@ -2814,7 +2808,6 @@ def test_in_one_fan_out_state_graph_then_defer_node(
"parents": {},
"source": "update",
"step": 4,
"writes": {"retriever_one": {"docs": ["doc5"]}},
"thread_id": "2",
},
parent_config=expected_parent_config,
@@ -5265,11 +5258,6 @@ def test_parent_command(sync_checkpointer: BaseCheckpointSaver) -> None:
},
metadata={
"source": "loop",
"writes": {
"alice": {
"user_name": "Meow",
}
},
"thread_id": "1",
"step": 1,
"parents": {},
@@ -5984,11 +5972,6 @@ def test_falsy_return_from_task(
"parents": {},
"source": "input",
"step": -1,
"writes": {
"__start__": {
"a": 5,
},
},
},
"next": [
"graph",
@@ -6097,11 +6080,6 @@ def test_falsy_return_from_task(
"source": "input",
"step": -1,
"thread_id": AnyStr(),
"writes": {
"__start__": {
"a": 5,
},
},
},
"next": [
"graph",
@@ -6191,10 +6169,6 @@ def test_falsy_return_from_task(
"parents": {},
"source": "loop",
"step": 0,
"writes": {
"falsy_task": False,
"graph": None,
},
},
"next": [],
"parent_config": {
@@ -8035,11 +8009,6 @@ def test_bulk_state_updates(
# Check if there are only two checkpoints
checkpoints = list(sync_checkpointer.list(config))
assert len(checkpoints) == 2
assert checkpoints[0].metadata["writes"] == {
"node_a": {"foo": "updated"},
"node_b": {"baz": "new"},
}
assert checkpoints[1].metadata["writes"] == {"node_a": {"foo": "bar"}}
# perform multiple steps at the same time
config = {"configurable": {"thread_id": "2"}}
@@ -8062,11 +8031,6 @@ def test_bulk_state_updates(
checkpoints = list(sync_checkpointer.list(config))
assert len(checkpoints) == 2
assert checkpoints[0].metadata["writes"] == {
"node_a": {"foo": "updated"},
"node_b": {"baz": "new"},
}
assert checkpoints[1].metadata["writes"] == {"node_a": {"foo": "bar"}}
# Should raise error if updating without as_node
with pytest.raises(InvalidUpdateError):
-125
View File
@@ -588,14 +588,12 @@ async def test_dynamic_interrupt(async_checkpointer: BaseCheckpointSaver) -> Non
"parents": {},
"source": "loop",
"step": 0,
"writes": None,
"thread_id": "1",
},
{
"parents": {},
"source": "input",
"step": -1,
"writes": {"__start__": {"my_key": "value ⛰️", "market": "DE"}},
"thread_id": "1",
},
]
@@ -623,7 +621,6 @@ async def test_dynamic_interrupt(async_checkpointer: BaseCheckpointSaver) -> Non
"parents": {},
"source": "loop",
"step": 0,
"writes": None,
"thread_id": "1",
},
parent_config=(
@@ -652,7 +649,6 @@ async def test_dynamic_interrupt(async_checkpointer: BaseCheckpointSaver) -> Non
"parents": {},
"source": "update",
"step": 1,
"writes": {},
"thread_id": "1",
},
parent_config=(
@@ -773,14 +769,12 @@ async def test_dynamic_interrupt_subgraph(
"parents": {},
"source": "loop",
"step": 0,
"writes": None,
"thread_id": "1",
},
{
"parents": {},
"source": "input",
"step": -1,
"writes": {"__start__": {"my_key": "value ⛰️", "market": "DE"}},
"thread_id": "1",
},
]
@@ -814,7 +808,6 @@ async def test_dynamic_interrupt_subgraph(
"parents": {},
"source": "loop",
"step": 0,
"writes": None,
"thread_id": "1",
},
parent_config=(
@@ -845,7 +838,6 @@ async def test_dynamic_interrupt_subgraph(
"parents": {},
"source": "update",
"step": 1,
"writes": {},
"thread_id": "1",
},
parent_config=(
@@ -965,14 +957,12 @@ async def test_copy_checkpoint(async_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 0,
"writes": None,
"thread_id": "1",
},
{
"parents": {},
"source": "input",
"step": -1,
"writes": {"__start__": {"my_key": "value ⛰️", "market": "DE"}},
"thread_id": "1",
},
]
@@ -1010,7 +1000,6 @@ async def test_copy_checkpoint(async_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 0,
"writes": None,
"thread_id": "1",
},
parent_config=(
@@ -1052,7 +1041,6 @@ async def test_copy_checkpoint(async_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "fork",
"step": 1,
"writes": None,
"thread_id": "1",
},
parent_config=(
@@ -1230,7 +1218,6 @@ async def test_cancel_graph_astream(async_checkpointer: BaseCheckpointSaver) ->
"parents": {},
"source": "loop",
"step": 0,
"writes": None,
"thread_id": "1",
}
@@ -1306,7 +1293,6 @@ async def test_cancel_graph_astream_events_v2(
"parents": {},
"source": "loop",
"step": 1,
"writes": {"alittlewhile": {"value": 2}},
"thread_id": "2",
}
@@ -1881,7 +1867,6 @@ async def test_pending_writes_resume(
"parents": {},
"source": "loop",
"step": 0,
"writes": None,
"thread_id": "1",
}
# get_state with checkpoint_id should not apply any pending writes
@@ -1972,7 +1957,6 @@ async def test_pending_writes_resume(
"parents": {},
"step": 1,
"source": "loop",
"writes": {"one": {"value": 2}, "two": {"value": 3}},
"thread_id": "1",
},
parent_config={
@@ -2021,7 +2005,6 @@ async def test_pending_writes_resume(
"parents": {},
"step": 0,
"source": "loop",
"writes": None,
"thread_id": "1",
},
parent_config={
@@ -2070,7 +2053,6 @@ async def test_pending_writes_resume(
"parents": {},
"step": -1,
"source": "input",
"writes": {"__start__": {"value": 1}},
"thread_id": "1",
},
parent_config=None,
@@ -2671,7 +2653,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"writes": {"3": ["3"]},
"thread_id": "1",
"step": 4,
"parents": {},
@@ -2708,7 +2689,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"writes": {"2": ["2|3"], "3": ["3"], "flaky": ["flaky|4"]},
"thread_id": "1",
"step": 3,
"parents": {},
@@ -2752,13 +2732,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"writes": {
"2": [
["2|Command(goto=Send(node='2', arg=3))"],
["2|Command(goto=Send(node='flaky', arg=4))"],
],
"3.1": ["3.1"],
},
"thread_id": "1",
"step": 2,
"parents": {},
@@ -2814,7 +2787,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"writes": {"1": ["1"]},
"thread_id": "1",
"step": 1,
"parents": {},
@@ -2870,7 +2842,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"writes": None,
"thread_id": "1",
"step": 0,
"parents": {},
@@ -2908,7 +2879,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "input",
"writes": {"__start__": ["0"]},
"thread_id": "1",
"step": -1,
"parents": {},
@@ -3087,22 +3057,6 @@ async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) ->
metadata={
"step": 1,
"source": "loop",
"writes": {
"agent": {
"messages": AIMessage(
content="",
id="ai1",
tool_calls=[
{
"name": "foo",
"args": {"hi": [1, 2, 3]},
"id": "",
"type": "tool_call",
}
],
)
}
},
"parents": {},
"thread_id": "2",
},
@@ -3157,14 +3111,6 @@ async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) ->
metadata={
"step": 2,
"source": "update",
"writes": {
"agent": {
"messages": _AnyIdAIMessage(
content="Bye now",
tool_calls=[],
)
}
},
"parents": {},
"thread_id": "2",
},
@@ -3243,22 +3189,6 @@ async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) ->
metadata={
"step": 1,
"source": "loop",
"writes": {
"agent": {
"messages": AIMessage(
content="",
id="ai1",
tool_calls=[
{
"name": "foo",
"args": {"hi": [1, 2, 3]},
"id": "",
"type": "tool_call",
}
],
)
}
},
"parents": {},
"thread_id": "3",
},
@@ -3334,21 +3264,6 @@ async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) ->
metadata={
"step": 2,
"source": "update",
"writes": {
"agent": {
"messages": _AnyIdAIMessage(
content="",
tool_calls=[
{
"name": "foo",
"args": {"hi": [4, 5, 6]},
"id": "tool1",
"type": "tool_call",
}
],
)
}
},
"parents": {},
"thread_id": "3",
},
@@ -3547,22 +3462,6 @@ async def test_send_react_interrupt_control(
metadata={
"step": 1,
"source": "loop",
"writes": {
"agent": {
"messages": AIMessage(
content="",
id="ai1",
tool_calls=[
{
"name": "foo",
"args": {"hi": [1, 2, 3]},
"id": "",
"type": "tool_call",
}
],
)
}
},
"parents": {},
"thread_id": "2",
},
@@ -3617,14 +3516,6 @@ async def test_send_react_interrupt_control(
metadata={
"step": 2,
"source": "update",
"writes": {
"agent": {
"messages": _AnyIdAIMessage(
content="Bye now",
tool_calls=[],
)
}
},
"parents": {},
"thread_id": "2",
},
@@ -4698,7 +4589,6 @@ async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class(
metadata={
"parents": {},
"source": "loop",
"writes": {"qa": {"answer": "doc1,doc2,doc3,doc4"}},
"step": 4,
"thread_id": "1",
},
@@ -6386,11 +6276,6 @@ async def test_parent_command(async_checkpointer: BaseCheckpointSaver) -> None:
},
metadata={
"source": "loop",
"writes": {
"alice": {
"user_name": "Meow",
}
},
"thread_id": "1",
"step": 1,
"parents": {},
@@ -8347,11 +8232,6 @@ async def test_bulk_state_updates(async_checkpointer: BaseCheckpointSaver) -> No
c async for c in async_checkpointer.alist({"configurable": {"thread_id": "1"}})
]
assert len(checkpoints) == 2
assert checkpoints[0].metadata["writes"] == {
"node_a": {"foo": "updated"},
"node_b": {"baz": "new"},
}
assert checkpoints[1].metadata["writes"] == {"node_a": {"foo": "bar"}}
# perform multiple steps at the same time
config = {"configurable": {"thread_id": "2"}}
@@ -8376,11 +8256,6 @@ async def test_bulk_state_updates(async_checkpointer: BaseCheckpointSaver) -> No
c async for c in async_checkpointer.alist({"configurable": {"thread_id": "1"}})
]
assert len(checkpoints) == 2
assert checkpoints[0].metadata["writes"] == {
"node_a": {"foo": "updated"},
"node_b": {"baz": "new"},
}
assert checkpoints[1].metadata["writes"] == {"node_a": {"foo": "bar"}}
# Should raise error if updating without as_node
with pytest.raises(InvalidUpdateError):
-2
View File
@@ -90,7 +90,6 @@ def test_no_prompt(sync_checkpointer: BaseCheckpointSaver, version: str) -> None
assert saved.metadata == {
"parents": {},
"source": "loop",
"writes": {"agent": {"messages": [AIMessage(content="hi?", id="0")]}},
"step": 1,
"thread_id": "123",
}
@@ -118,7 +117,6 @@ async def test_no_prompt_async(async_checkpointer: BaseCheckpointSaver) -> None:
assert saved.metadata == {
"parents": {},
"source": "loop",
"writes": {"agent": {"messages": [AIMessage(content="hi?", id="0")]}},
"step": 1,
"thread_id": "123",
}