use resume instead of resume_map and deprecate old mapping task_id -> resume logic

This commit is contained in:
Sydney Runkle
2025-04-24 13:26:01 -07:00
parent 847b9c13ac
commit 644a6c3b63
5 changed files with 22 additions and 39 deletions
+1 -11
View File
@@ -908,17 +908,7 @@ def _scratchpad(
if not isinstance(task_resume_write, list):
task_resume_write = [task_resume_write]
else:
# find task-specific resume value
for w in pending_writes:
if w[0] == task_id and w[1] == RESUME:
task_resume_write = w[2]
if not isinstance(task_resume_write, list):
task_resume_write = [task_resume_write]
break
else:
task_resume_write = []
# clear var
del w
task_resume_write = []
else:
null_resume_write = None
task_resume_write = []
+2 -21
View File
@@ -24,15 +24,6 @@ from langgraph.pregel.log import logger
from langgraph.types import Command, PregelExecutableTask, Send
def is_task_id(task_id: str) -> bool:
"""Check if a string is a valid task id."""
try:
UUID(task_id)
except Exception:
return False
return True
def read_channel(
channels: Mapping[str, BaseChannel],
chan: str,
@@ -66,9 +57,7 @@ def read_channels(
return values
def map_command(
cmd: Command, pending_writes: list[PendingWrite]
) -> Iterator[tuple[str, str, Any]]:
def map_command(cmd: Command) -> Iterator[tuple[str, str, Any]]:
"""Map input chunk to a sequence of pending writes in the form (channel, value)."""
if cmd.graph == Command.PARENT:
raise InvalidUpdateError("There is no parent graph")
@@ -87,15 +76,7 @@ def map_command(
f"In Command.goto, expected Send/str, got {type(send).__name__}"
)
if cmd.resume is not None:
if isinstance(cmd.resume, dict) and all(is_task_id(k) for k in cmd.resume):
for tid, resume in cmd.resume.items():
existing: list[Any] = next(
(w[2] for w in pending_writes if w[0] == tid and w[1] == RESUME), []
)
existing.append(resume)
yield (tid, RESUME, existing)
else:
yield (NULL_TASK_ID, RESUME, cmd.resume)
yield (NULL_TASK_ID, RESUME, cmd.resume)
if cmd.update:
for k, v in cmd._update_as_tuples():
yield (NULL_TASK_ID, k, v)
+8 -5
View File
@@ -113,7 +113,7 @@ from langgraph.pregel.io import (
)
from langgraph.pregel.manager import AsyncChannelsManager, ChannelsManager
from langgraph.pregel.read import PregelNode
from langgraph.pregel.utils import get_new_channel_versions
from langgraph.pregel.utils import get_new_channel_versions, is_xxh3_128_hexdigest
from langgraph.store.base import BaseStore
from langgraph.types import (
All,
@@ -650,17 +650,20 @@ class PregelLoop(LoopProtocol):
# map command to writes
if isinstance(self.input, Command):
if self.input.resume_map:
self.config[CONF][CONFIG_KEY_RESUME_MAP] = self.input.resume_map
if (resume := self.input.resume) is not None:
if isinstance(resume, dict) and all(
is_xxh3_128_hexdigest(k) for k in resume
):
self.config[CONF][CONFIG_KEY_RESUME_MAP] = self.input.resume
if self.input.resume is not None and not self.checkpointer:
raise RuntimeError(
"Cannot use Command(resume=...) without checkpointer"
)
writes: defaultdict[str, list[tuple[str, Any]]] = defaultdict(list)
# group writes by task ID
for tid, c, v in map_command(self.input, self.checkpoint_pending_writes):
for tid, c, v in map_command(self.input):
writes[tid].append((c, v))
if not writes and not self.input.resume_map:
if not writes:
raise EmptyInputError("Received empty Command input")
# save writes
for tid, ws in writes.items():
+6
View File
@@ -1,5 +1,6 @@
import ast
import inspect
import re
import textwrap
from typing import Any, Callable, Optional
@@ -207,3 +208,8 @@ class NonLocals(ast.NodeVisitor):
parent = parent.value
if isinstance(parent, ast.Name):
self.loads.add(parent.id + "." + attr_expr)
def is_xxh3_128_hexdigest(value: str) -> bool:
"""Check if the given string matches the format of xxh3_128_hexdigest."""
return bool(re.fullmatch(r"[0-9a-f]{32}", value))
+5 -2
View File
@@ -294,6 +294,10 @@ class Command(Generic[N], ToolOutputMixin):
- Command.PARENT: closest parent graph
update: update to apply to the graph's state.
resume: value to resume execution with. To be used together with [`interrupt()`][langgraph.types.interrupt].
Can be one of the following:
- mapping of interrupt ids to resume values
- a single value with which to resume the next interrupt
goto: can be one of the following:
- name of the node to navigate to next (any node that belongs to the specified `graph`)
@@ -305,9 +309,8 @@ class Command(Generic[N], ToolOutputMixin):
graph: Optional[str] = None
update: Optional[Any] = None
resume: Optional[Union[Any, dict[str, Any]]] = None
resume: Optional[Union[dict[str, Any], Any]] = None
goto: Union[Send, Sequence[Union[Send, str]], str] = ()
resume_map: Optional[dict[str, Any]] = None
def __repr__(self) -> str:
# get all non-None values