Avoid raise-catch strategy in BaseChannel.checkpoint()

- This mirrors the work done earlier on BaseChannel.get()
- Comparing to a sentinel value is significantly faster than raising and catching an exception
This commit is contained in:
Nuno Campos
2025-03-31 17:48:19 -07:00
parent 5e9e7b79fe
commit fd64ada9de
11 changed files with 59 additions and 5 deletions
@@ -124,6 +124,7 @@ def copy_checkpoint(checkpoint: Checkpoint) -> Checkpoint:
)
# Kept for backwards compat, newer versions of LangGraph no longer use this.
def create_checkpoint(
checkpoint: Checkpoint,
channels: Optional[Mapping[str, ChannelProtocol]],
+1 -1
View File
@@ -34,7 +34,7 @@ if __name__ == "__main__":
import uvloop
graph = create_sequential(2000).compile()
graph = create_sequential(3000).compile()
input = {"messages": []} # Empty list of messages
config = {"recursion_limit": 20000000000}
@@ -55,3 +55,6 @@ class AnyValue(Generic[Value], BaseChannel[Value, Value, Value]):
def is_available(self) -> bool:
return self.value is not MISSING
def checkpoint(self) -> Value:
return self.value
+5 -1
View File
@@ -3,6 +3,7 @@ from typing import Any, Generic, Sequence, TypeVar
from typing_extensions import Self
from langgraph.constants import MISSING
from langgraph.errors import EmptyChannelError, InvalidUpdateError
Value = TypeVar("Value")
@@ -33,7 +34,10 @@ class BaseChannel(Generic[Value, Update, C], ABC):
"""Return a serializable representation of the channel's current state.
Raises EmptyChannelError if the channel is empty (never updated yet),
or doesn't support checkpoints."""
return self.get()
try:
return self.get()
except EmptyChannelError:
return MISSING
@abstractmethod
def from_checkpoint(self, checkpoint: C) -> Self:
@@ -90,3 +90,6 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
def is_available(self) -> bool:
return self.value is not MISSING
def checkpoint(self) -> Value:
return self.value
@@ -59,3 +59,6 @@ class EphemeralValue(Generic[Value], BaseChannel[Value, Value, Value]):
def is_available(self) -> bool:
return self.value is not MISSING
def checkpoint(self) -> Value:
return self.value
@@ -61,3 +61,6 @@ class LastValue(Generic[Value], BaseChannel[Value, Value, Value]):
def is_available(self) -> bool:
return self.value is not MISSING
def checkpoint(self) -> Value:
return self.value
@@ -31,7 +31,7 @@ class UntrackedValue(Generic[Value], BaseChannel[Value, Value, Value]):
return self.typ
def checkpoint(self) -> Value:
raise EmptyChannelError()
return MISSING
def from_checkpoint(self, checkpoint: Value) -> Self:
empty = self.__class__(self.typ, self.guard)
+1 -1
View File
@@ -50,7 +50,6 @@ from langgraph.checkpoint.base import (
BaseCheckpointSaver,
CheckpointTuple,
copy_checkpoint,
create_checkpoint,
empty_checkpoint,
)
from langgraph.constants import (
@@ -91,6 +90,7 @@ from langgraph.pregel.algo import (
local_write,
prepare_next_tasks,
)
from langgraph.pregel.checkpoint import create_checkpoint
from langgraph.pregel.debug import tasks_w_writes
from langgraph.pregel.io import map_input, read_channels
from langgraph.pregel.loop import AsyncPregelLoop, StreamProtocol, SyncPregelLoop
@@ -0,0 +1,37 @@
from datetime import datetime, timezone
from typing import Mapping, Optional
from langgraph.channels.base import BaseChannel
from langgraph.checkpoint.base import LATEST_VERSION, Checkpoint
from langgraph.checkpoint.base.id import uuid6
from langgraph.constants import MISSING
def create_checkpoint(
checkpoint: Checkpoint,
channels: Optional[Mapping[str, BaseChannel]],
step: int,
*,
id: Optional[str] = None,
) -> Checkpoint:
"""Create a checkpoint for the given channels."""
ts = datetime.now(timezone.utc).isoformat()
if channels is None:
values = checkpoint["channel_values"]
else:
values = {}
for k in channels:
if k not in checkpoint["channel_versions"]:
continue
v = channels[k].checkpoint()
if v is not MISSING:
values[k] = v
return Checkpoint(
v=LATEST_VERSION,
ts=ts,
id=id or str(uuid6(clock_seq=step)),
channel_values=values,
channel_versions=checkpoint["channel_versions"],
versions_seen=checkpoint["versions_seen"],
pending_sends=checkpoint.get("pending_sends", []),
)
+1 -1
View File
@@ -38,7 +38,6 @@ from langgraph.checkpoint.base import (
CheckpointTuple,
PendingWrite,
copy_checkpoint,
create_checkpoint,
empty_checkpoint,
)
from langgraph.constants import (
@@ -88,6 +87,7 @@ from langgraph.pregel.algo import (
should_interrupt,
task_path_str,
)
from langgraph.pregel.checkpoint import create_checkpoint
from langgraph.pregel.debug import (
map_debug_checkpoint,
map_debug_task_results,