diff --git a/docs/docs/concepts/human_in_the_loop.md b/docs/docs/concepts/human_in_the_loop.md index cc045b81b..bf9491531 100644 --- a/docs/docs/concepts/human_in_the_loop.md +++ b/docs/docs/concepts/human_in_the_loop.md @@ -440,6 +440,22 @@ Upon **resuming** the graph, the counter will be incremented a second time, resu The value of counter is: 2 ``` +### Resuming multiple interrupts with one invocation + +If you have multiple interrupts in the task queue, you can use `Command.resume` with a dictionary mapping +of interrupt ids to resume values to resume multiple interrupts with a single `invoke` / `stream` call. + +For example, once your graph has been interrupted (multiple times, theoretically) and is stalled: + +```python +resume_map = { + i.interrupt_id: f"human input for prompt {i.value}" + for i in parent.get_state(thread_config).interrupts +} + +parent_graph.invoke(Command(resume=resume_map), config=thread_config) +``` + ## Common Pitfalls ### Side-effects diff --git a/libs/langgraph/langgraph/constants.py b/libs/langgraph/langgraph/constants.py index 0ebab0a6f..59e72e9c9 100644 --- a/libs/langgraph/langgraph/constants.py +++ b/libs/langgraph/langgraph/constants.py @@ -102,6 +102,8 @@ CONF = cast(Literal["configurable"], sys.intern("configurable")) # key for the configurable dict in RunnableConfig NULL_TASK_ID = sys.intern("00000000-0000-0000-0000-000000000000") # the task_id to use for writes that are not associated with a task +CONFIG_KEY_RESUME_MAP = sys.intern("__pregel_resume_map") +# holds a mapping of task ns -> resume value for resuming tasks RESERVED = { TAG_HIDDEN, diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 14e98c460..e7170616a 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -853,6 +853,7 @@ class Pregel(PregelProtocol): created_at=None, parent_config=None, tasks=(), + interrupts=(), ) # migrate checkpoint if needed @@ -937,6 +938,12 @@ class Pregel(PregelProtocol): next_tasks[tid].writes.append((k, v)) if tasks := [t for t in next_tasks.values() if t.writes]: apply_writes(saved.checkpoint, channels, tasks, None) + tasks_with_writes = tasks_w_writes( + next_tasks.values(), + saved.pending_writes, + task_states, + self.stream_channels_asis, + ) # assemble the state snapshot return StateSnapshot( read_channels(channels, self.stream_channels_asis), @@ -945,12 +952,8 @@ class Pregel(PregelProtocol): saved.metadata, saved.checkpoint["ts"], patch_checkpoint_map(saved.parent_config, saved.metadata), - tasks_w_writes( - next_tasks.values(), - saved.pending_writes, - task_states, - self.stream_channels_asis, - ), + tasks_with_writes, + tuple([i for task in tasks_with_writes for i in task.interrupts]), ) async def _aprepare_state_snapshot( @@ -969,6 +972,7 @@ class Pregel(PregelProtocol): created_at=None, parent_config=None, tasks=(), + interrupts=(), ) # migrate checkpoint if needed @@ -1056,6 +1060,13 @@ class Pregel(PregelProtocol): next_tasks[tid].writes.append((k, v)) if tasks := [t for t in next_tasks.values() if t.writes]: apply_writes(saved.checkpoint, channels, tasks, None) + + tasks_with_writes = tasks_w_writes( + next_tasks.values(), + saved.pending_writes, + task_states, + self.stream_channels_asis, + ) # assemble the state snapshot return StateSnapshot( read_channels(channels, self.stream_channels_asis), @@ -1064,12 +1075,8 @@ class Pregel(PregelProtocol): saved.metadata, saved.checkpoint["ts"], patch_checkpoint_map(saved.parent_config, saved.metadata), - tasks_w_writes( - next_tasks.values(), - saved.pending_writes, - task_states, - self.stream_channels_asis, - ), + tasks_with_writes, + tuple([i for task in tasks_with_writes for i in task.interrupts]), ) def get_state( diff --git a/libs/langgraph/langgraph/pregel/algo.py b/libs/langgraph/langgraph/pregel/algo.py index 19cd9d1ff..b6ce4433d 100644 --- a/libs/langgraph/langgraph/pregel/algo.py +++ b/libs/langgraph/langgraph/pregel/algo.py @@ -40,6 +40,7 @@ from langgraph.constants import ( CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_PREVIOUS, CONFIG_KEY_READ, + CONFIG_KEY_RESUME_MAP, CONFIG_KEY_SCRATCHPAD, CONFIG_KEY_SEND, CONFIG_KEY_STORE, @@ -594,6 +595,8 @@ def prepare_single_task( config[CONF].get(CONFIG_KEY_SCRATCHPAD), pending_writes, task_id, + xxh3_128_hexdigest(task_checkpoint_ns.encode()), + config[CONF].get(CONFIG_KEY_RESUME_MAP), ), }, ), @@ -704,6 +707,8 @@ def prepare_single_task( config[CONF].get(CONFIG_KEY_SCRATCHPAD), pending_writes, task_id, + xxh3_128_hexdigest(task_checkpoint_ns.encode()), + config[CONF].get(CONFIG_KEY_RESUME_MAP), ), CONFIG_KEY_PREVIOUS: checkpoint["channel_values"].get( PREVIOUS, None @@ -830,6 +835,8 @@ def prepare_single_task( config[CONF].get(CONFIG_KEY_SCRATCHPAD), pending_writes, task_id, + xxh3_128_hexdigest(task_checkpoint_ns.encode()), + config[CONF].get(CONFIG_KEY_RESUME_MAP), ), CONFIG_KEY_PREVIOUS: checkpoint["channel_values"].get( PREVIOUS, None @@ -881,6 +888,8 @@ def _scratchpad( parent_scratchpad: Optional[PregelScratchpad], pending_writes: list[PendingWrite], task_id: str, + namespace_hash: str, + resume_map: Optional[dict[str, Any]], ) -> PregelScratchpad: if len(pending_writes) > 0: # find global resume value @@ -892,6 +901,7 @@ def _scratchpad( # None cannot be used as a resume value, because it would be difficult to # distinguish from missing when used over http null_resume_write = None + # find task-specific resume value for w in pending_writes: if w[0] == task_id and w[1] == RESUME: @@ -901,8 +911,13 @@ def _scratchpad( break else: task_resume_write = [] - # clear var del w + + # find namespace and task-specific resume value + if resume_map and namespace_hash in resume_map: + mapped_resume_write = resume_map[namespace_hash] + task_resume_write.append(mapped_resume_write) + else: null_resume_write = None task_resume_write = [] diff --git a/libs/langgraph/langgraph/pregel/io.py b/libs/langgraph/langgraph/pregel/io.py index 026051a4d..53fe7ef7f 100644 --- a/libs/langgraph/langgraph/pregel/io.py +++ b/libs/langgraph/langgraph/pregel/io.py @@ -1,12 +1,10 @@ from collections import Counter from collections.abc import Iterator, Mapping, Sequence from typing import Any, Literal, Optional, TypeVar, Union -from uuid import UUID from langchain_core.runnables.utils import AddableDict from langgraph.channels.base import BaseChannel, EmptyChannelError -from langgraph.checkpoint.base import PendingWrite from langgraph.constants import ( EMPTY_SEQ, ERROR, @@ -24,15 +22,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 +55,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 +74,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) diff --git a/libs/langgraph/langgraph/pregel/loop.py b/libs/langgraph/langgraph/pregel/loop.py index 972699b61..d0c7db986 100644 --- a/libs/langgraph/langgraph/pregel/loop.py +++ b/libs/langgraph/langgraph/pregel/loop.py @@ -47,6 +47,7 @@ from langgraph.constants import ( CONFIG_KEY_DEDUPE_TASKS, CONFIG_KEY_DELEGATE, CONFIG_KEY_ENSURE_LATEST, + CONFIG_KEY_RESUME_MAP, CONFIG_KEY_RESUMING, CONFIG_KEY_SCRATCHPAD, CONFIG_KEY_STREAM, @@ -112,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, @@ -649,14 +650,21 @@ class PregelLoop(LoopProtocol): # map command to writes if isinstance(self.input, Command): - if self.input.resume is not None and not self.checkpointer: + if resume_is_map := ( + (resume := self.input.resume) is not None + and 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 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): - writes[tid].append((c, v)) + for tid, c, v in map_command(cmd=self.input): + if not (c == RESUME and resume_is_map): + writes[tid].append((c, v)) if not writes: raise EmptyInputError("Received empty Command input") # save writes diff --git a/libs/langgraph/langgraph/pregel/remote.py b/libs/langgraph/langgraph/pregel/remote.py index 09d5f90a8..2707a7997 100644 --- a/libs/langgraph/langgraph/pregel/remote.py +++ b/libs/langgraph/langgraph/pregel/remote.py @@ -241,7 +241,7 @@ class RemoteGraph(PregelProtocol): ) def _create_state_snapshot(self, state: ThreadState) -> StateSnapshot: - tasks = [] + tasks: list[PregelTask] = [] for task in state["tasks"]: interrupts = [] for interrupt in task["interrupts"]: @@ -289,6 +289,7 @@ class RemoteGraph(PregelProtocol): if state["parent_checkpoint"] else None, tasks=tuple(tasks), + interrupts=tuple([i for task in tasks for i in task.interrupts]), ) def _get_checkpoint(self, config: Optional[RunnableConfig]) -> Optional[Checkpoint]: diff --git a/libs/langgraph/langgraph/pregel/utils.py b/libs/langgraph/langgraph/pregel/utils.py index fd62e345f..da0076f6b 100644 --- a/libs/langgraph/langgraph/pregel/utils.py +++ b/libs/langgraph/langgraph/pregel/utils.py @@ -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)) diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index 5462bafb2..86a377684 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -147,7 +147,7 @@ class Interrupt: """Generate a unique ID for the interrupt based on its namespace.""" if self.ns is None: return "placeholder-id" - return xxh3_128_hexdigest("".join(self.ns).encode()) + return xxh3_128_hexdigest("|".join(self.ns).encode()) class StateUpdate(NamedTuple): @@ -192,19 +192,21 @@ class StateSnapshot(NamedTuple): """Snapshot of the state of the graph at the beginning of a step.""" values: Union[dict[str, Any], Any] - """Current values of channels""" + """Current values of channels.""" next: tuple[str, ...] """The name of the node to execute in each task for this step.""" config: RunnableConfig - """Config used to fetch this snapshot""" + """Config used to fetch this snapshot.""" metadata: Optional[CheckpointMetadata] - """Metadata associated with this snapshot""" + """Metadata associated with this snapshot.""" created_at: Optional[str] - """Timestamp of snapshot creation""" + """Timestamp of snapshot creation.""" parent_config: Optional[RunnableConfig] - """Config used to fetch the parent snapshot, if any""" + """Config used to fetch the parent snapshot, if any.""" tasks: tuple[PregelTask, ...] """Tasks to execute in this step. If already attempted, may contain an error.""" + interrupts: tuple[Interrupt, ...] + """Interrupts that occurred in this step that are pending resolution.""" class Send: @@ -294,6 +296,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`) @@ -304,7 +310,7 @@ 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] = () def __repr__(self) -> str: diff --git a/libs/langgraph/tests/test_checkpoint_migration.py b/libs/langgraph/tests/test_checkpoint_migration.py index 21727ba0a..1ac86adba 100644 --- a/libs/langgraph/tests/test_checkpoint_migration.py +++ b/libs/langgraph/tests/test_checkpoint_migration.py @@ -63,6 +63,7 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]: } }, tasks=(), + interrupts=(), ), StateSnapshot( values={ @@ -113,6 +114,15 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]: else {"answer": "doc1,doc2,doc3,doc4"}, ), ), + interrupts=() + if exc_task_results + else ( + Interrupt( + value="", + resumable=True, + ns=[AnyStr("qa:")], + ), + ), ), StateSnapshot( values={ @@ -156,6 +166,7 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]: result=None if exc_task_results else {"docs": ["doc1", "doc2"]}, ), ), + interrupts=(), ), StateSnapshot( values={"query": "query: what is weather in sf", "docs": []}, @@ -206,6 +217,7 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]: else {"docs": ["doc3", "doc4"]}, ), ), + interrupts=(), ), StateSnapshot( values={"query": "what is weather in sf", "docs": []}, @@ -245,6 +257,7 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]: else {"query": "query: what is weather in sf"}, ), ), + interrupts=(), ), StateSnapshot( values={"docs": []}, @@ -276,6 +289,7 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]: result={"query": "what is weather in sf"}, ), ), + interrupts=(), ), ] @@ -1731,6 +1745,7 @@ def test_saved_checkpoint_state_graph( created_at=AnyStr(), parent_config=latest_state.parent_config, tasks=latest_state.tasks, + interrupts=latest_state.interrupts, ) == history[0] ) @@ -1802,6 +1817,7 @@ async def test_saved_checkpoint_state_graph_async( created_at=AnyStr(), parent_config=latest_state.parent_config, tasks=latest_state.tasks, + interrupts=latest_state.interrupts, ) == history[0] ) diff --git a/libs/langgraph/tests/test_large_cases.py b/libs/langgraph/tests/test_large_cases.py index b94585548..4b4c94173 100644 --- a/libs/langgraph/tests/test_large_cases.py +++ b/libs/langgraph/tests/test_large_cases.py @@ -144,6 +144,7 @@ def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[1].config, + interrupts=(), ), StateSnapshot( values={"inbox": 4, "output": 4, "input": 3}, @@ -165,6 +166,7 @@ def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[2].config, + interrupts=(), ), StateSnapshot( values={"inbox": 21, "output": 4, "input": 3}, @@ -186,6 +188,7 @@ def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[3].config, + interrupts=(), ), StateSnapshot( values={"inbox": 21, "output": 4, "input": 20}, @@ -207,6 +210,7 @@ def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[4].config, + interrupts=(), ), StateSnapshot( values={"inbox": 3, "output": 4, "input": 20}, @@ -228,6 +232,7 @@ def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[5].config, + interrupts=(), ), StateSnapshot( values={"inbox": 3, "output": 4, "input": 2}, @@ -249,6 +254,7 @@ def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[6].config, + interrupts=(), ), StateSnapshot( values={"inbox": 3, "input": 2}, @@ -270,6 +276,7 @@ def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[7].config, + interrupts=(), ), StateSnapshot( values={"input": 2}, @@ -291,6 +298,7 @@ def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=None, + interrupts=(), ), ] @@ -358,6 +366,7 @@ def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[1].config, + interrupts=(), ), StateSnapshot( values=5, @@ -379,6 +388,7 @@ def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[2].config, + interrupts=(), ), StateSnapshot( values=4, @@ -400,6 +410,7 @@ def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[3].config, + interrupts=(), ), StateSnapshot( values=3, @@ -421,6 +432,7 @@ def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[4].config, + interrupts=(), ), StateSnapshot( values=2, @@ -442,6 +454,7 @@ def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[5].config, + interrupts=(), ), StateSnapshot( values=1, @@ -463,6 +476,7 @@ def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[6].config, + interrupts=(), ), StateSnapshot( values=0, @@ -484,6 +498,7 @@ def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=None, + interrupts=(), ), ] @@ -776,6 +791,7 @@ def test_conditional_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert ( app_w_interrupt.checkpointer.get_tuple(config).config["configurable"][ @@ -838,6 +854,7 @@ def test_conditional_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -969,6 +986,7 @@ def test_conditional_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # test state get/update methods with interrupt_before @@ -1035,6 +1053,7 @@ def test_conditional_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) app_w_interrupt.update_state( @@ -1091,6 +1110,7 @@ def test_conditional_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -1222,6 +1242,7 @@ def test_conditional_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # test re-invoke to continue with interrupt_before @@ -1288,6 +1309,7 @@ def test_conditional_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -1684,6 +1706,7 @@ def test_conditional_state_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) with assert_ctx_once(): @@ -1737,6 +1760,7 @@ def test_conditional_state_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) with assert_ctx_once(): @@ -1824,6 +1848,7 @@ def test_conditional_state_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # test state get/update methods with interrupt_before @@ -1886,6 +1911,7 @@ def test_conditional_state_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) app_w_interrupt.update_state( @@ -1938,6 +1964,7 @@ def test_conditional_state_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -2023,6 +2050,7 @@ def test_conditional_state_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # test w interrupt before all @@ -2066,6 +2094,7 @@ def test_conditional_state_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -2116,6 +2145,7 @@ def test_conditional_state_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -2187,6 +2217,7 @@ def test_conditional_state_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -2260,6 +2291,7 @@ def test_conditional_state_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -2331,6 +2363,7 @@ def test_conditional_state_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -3071,6 +3104,7 @@ def test_state_graph_packets( if "shallow" in checkpointer_name else [*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config ), + interrupts=(), ) # modify ai message @@ -3135,6 +3169,7 @@ def test_state_graph_packets( if "shallow" in checkpointer_name else [*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -3254,6 +3289,7 @@ def test_state_graph_packets( if "shallow" in checkpointer_name else [*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config ), + interrupts=(), ) app_w_interrupt.update_state( @@ -3315,6 +3351,7 @@ def test_state_graph_packets( if "shallow" in checkpointer_name else [*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config ), + interrupts=(), ) # interrupt before tools @@ -3404,6 +3441,7 @@ def test_state_graph_packets( if "shallow" in checkpointer_name else [*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config ), + interrupts=(), ) # modify ai message @@ -3462,6 +3500,7 @@ def test_state_graph_packets( if "shallow" in checkpointer_name else [*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -3579,6 +3618,7 @@ def test_state_graph_packets( if "shallow" in checkpointer_name else [*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config ), + interrupts=(), ) app_w_interrupt.update_state( @@ -3640,6 +3680,7 @@ def test_state_graph_packets( if "shallow" in checkpointer_name else [*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config ), + interrupts=(), ) @@ -3936,6 +3977,7 @@ def test_message_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # modify ai message @@ -3987,6 +4029,7 @@ def test_message_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -4080,6 +4123,7 @@ def test_message_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) app_w_interrupt.update_state( @@ -4131,6 +4175,7 @@ def test_message_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) app_w_interrupt = workflow.compile( @@ -4206,6 +4251,7 @@ def test_message_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # modify ai message @@ -4263,6 +4309,7 @@ def test_message_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -4356,6 +4403,7 @@ def test_message_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) app_w_interrupt.update_state( @@ -4408,6 +4456,7 @@ def test_message_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # add an extra message as if it came from "tools" node @@ -4460,6 +4509,7 @@ def test_message_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) @@ -4759,6 +4809,7 @@ def test_root_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # modify ai message @@ -4810,6 +4861,7 @@ def test_root_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -4904,6 +4956,7 @@ def test_root_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) app_w_interrupt.update_state( @@ -4956,6 +5009,7 @@ def test_root_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) app_w_interrupt = workflow.compile( @@ -5031,6 +5085,7 @@ def test_root_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # modify ai message @@ -5088,6 +5143,7 @@ def test_root_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config)] == [ @@ -5182,6 +5238,7 @@ def test_root_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) app_w_interrupt.update_state( @@ -5233,6 +5290,7 @@ def test_root_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # add an extra message as if it came from "tools" node @@ -5285,6 +5343,7 @@ def test_root_graph( if "shallow" in checkpointer_name else list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # create new graph with one more state key, reuse previous thread history @@ -5368,6 +5427,7 @@ def test_root_graph( if "shallow" in checkpointer_name else list(new_app.checkpointer.list(config, limit=2))[-1].config ), + interrupts=(), ) # new input is merged to old state @@ -5775,6 +5835,13 @@ def test_dynamic_interrupt( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread1, limit=2))[-1].config ), + interrupts=( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ), ) # clear the interrupt and next tasks tool_two.update_state(thread1, None, as_node=END) @@ -5803,6 +5870,7 @@ def test_dynamic_interrupt( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread1, limit=2))[-1].config ), + interrupts=(), ) @@ -5964,6 +6032,13 @@ def test_copy_checkpoint( if "shallow" in checkpointer_name else [*tool_two.checkpointer.list(thread1, limit=2)][-1].config ), + interrupts=( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ), ) if "shallow" in checkpointer_name: @@ -6009,6 +6084,7 @@ def test_copy_checkpoint( parent_config=( [*tool_two.checkpointer.list(thread1, limit=2)][-1].parent_config ), + interrupts=(), ) @@ -6184,6 +6260,13 @@ def test_dynamic_interrupt_subgraph( ) )[-1].config ), + interrupts=( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:"), AnyStr("do:")], + ), + ), ) # clear the interrupt and next tasks tool_two.update_state(thread1, None, as_node=END) @@ -6216,6 +6299,7 @@ def test_dynamic_interrupt_subgraph( ) )[-1].config ), + interrupts=(), ) @@ -6334,6 +6418,7 @@ def test_start_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread1, limit=2))[-1].config ), + interrupts=(), ) # resume, for same result as above assert tool_two.invoke(None, thread1, debug=1) == { @@ -6365,6 +6450,7 @@ def test_start_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread1, limit=2))[-1].config ), + interrupts=(), ) thread2 = {"configurable": {"thread_id": "2", "assistant_id": "a"}} @@ -6398,6 +6484,7 @@ def test_start_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread2, limit=2))[-1].config ), + interrupts=(), ) # resume, for same result as above assert tool_two.invoke(None, thread2, debug=1) == { @@ -6429,6 +6516,7 @@ def test_start_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread2, limit=2))[-1].config ), + interrupts=(), ) thread3 = {"configurable": {"thread_id": "3", "assistant_id": "b"}} @@ -6462,6 +6550,7 @@ def test_start_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread3, limit=2))[-1].config ), + interrupts=(), ) # update state tool_two.update_state(thread3, {"my_key": "key"}) # appends to my_key @@ -6490,6 +6579,7 @@ def test_start_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread3, limit=2))[-1].config ), + interrupts=(), ) # resume, for same result as above assert tool_two.invoke(None, thread3, debug=1) == { @@ -6521,6 +6611,7 @@ def test_start_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread3, limit=2))[-1].config ), + interrupts=(), ) @@ -6900,6 +6991,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread1, limit=2))[-1].config ), + interrupts=(), ) # resume, for same result as above assert tool_two.invoke(None, thread1, debug=1) == { @@ -6930,6 +7022,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread1, limit=2))[-1].config ), + interrupts=(), ) thread2 = {"configurable": {"thread_id": "2"}} @@ -6962,6 +7055,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread2, limit=2))[-1].config ), + interrupts=(), ) # resume, for same result as above assert tool_two.invoke(None, thread2, debug=1) == { @@ -6992,6 +7086,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread2, limit=2))[-1].config ), + interrupts=(), ) tool_two = tool_two_graph.compile( @@ -7032,6 +7127,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread1, limit=2))[-1].config ), + interrupts=(), ) # update state @@ -7063,6 +7159,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread1, limit=2))[-1].config ), + interrupts=(), ) tool_two = tool_two_graph.compile( @@ -7103,6 +7200,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread1, limit=2))[-1].config ), + interrupts=(), ) # resume, for same result as above assert tool_two.invoke(None, thread1, debug=1) == { @@ -7133,6 +7231,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread1, limit=2))[-1].config ), + interrupts=(), ) thread2 = {"configurable": {"thread_id": "22"}} @@ -7165,6 +7264,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread2, limit=2))[-1].config ), + interrupts=(), ) # resume, for same result as above assert tool_two.invoke(None, thread2, debug=1) == { @@ -7195,6 +7295,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread2, limit=2))[-1].config ), + interrupts=(), ) thread3 = {"configurable": {"thread_id": "23"}} @@ -7221,6 +7322,7 @@ def test_branch_then( "thread_id": "23", }, parent_config=None, + interrupts=(), ) # run from this point assert tool_two.invoke(None, thread3) == { @@ -7252,6 +7354,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread3, limit=2))[-1].config ), + interrupts=(), ) # resume, for same result as above assert tool_two.invoke(None, thread3, debug=1) == { @@ -7282,6 +7385,7 @@ def test_branch_then( if "shallow" in checkpointer_name else list(tool_two.checkpointer.list(thread3, limit=2))[-1].config ), + interrupts=(), ) @@ -7423,6 +7527,7 @@ def test_send_dedupe_on_resume( } }, tasks=(), + interrupts=(), ), StateSnapshot( values=[ @@ -7469,6 +7574,7 @@ def test_send_dedupe_on_resume( result=["3"], ), ), + interrupts=(), ), StateSnapshot( values=[ @@ -7536,6 +7642,7 @@ def test_send_dedupe_on_resume( result=["3"], ), ), + interrupts=(Interrupt(value="Bahh", resumable=False, ns=None),), ), StateSnapshot( values=["0", "1"], @@ -7591,6 +7698,7 @@ def test_send_dedupe_on_resume( result=["3.1"], ), ), + interrupts=(), ), StateSnapshot( values=["0"], @@ -7628,6 +7736,7 @@ def test_send_dedupe_on_resume( result=["1"], ), ), + interrupts=(), ), StateSnapshot( values=[], @@ -7648,6 +7757,7 @@ def test_send_dedupe_on_resume( }, created_at=AnyStr(), parent_config=None, + interrupts=(), tasks=( PregelTask( id=AnyStr(), @@ -7762,6 +7872,7 @@ def test_nested_graph_state( } } ), + interrupts=(), ) # now, get_state with subgraphs state assert app.get_state(config, subgraphs=True) == StateSnapshot( @@ -7828,6 +7939,7 @@ def test_nested_graph_state( } } ), + interrupts=(), ), ), ), @@ -7858,6 +7970,7 @@ def test_nested_graph_state( } } ), + interrupts=(), ) # get_state_history returns outer graph checkpoints history = list(app.get_state_history(config)) @@ -7904,6 +8017,7 @@ def test_nested_graph_state( } } ), + interrupts=(), ), StateSnapshot( values={"my_key": "my value"}, @@ -7938,6 +8052,7 @@ def test_nested_graph_state( "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={}, @@ -7966,6 +8081,7 @@ def test_nested_graph_state( }, created_at=AnyStr(), parent_config=None, + interrupts=(), ), ] @@ -8022,6 +8138,7 @@ def test_nested_graph_state( } } ), + interrupts=(), tasks=(PregelTask(AnyStr(), "inner_2", (PULL, "inner_2")),), ), StateSnapshot( @@ -8071,6 +8188,7 @@ def test_nested_graph_state( }, ), ), + interrupts=(), ), StateSnapshot( values={}, @@ -8099,6 +8217,7 @@ def test_nested_graph_state( }, created_at=AnyStr(), parent_config=None, + interrupts=(), tasks=( PregelTask( AnyStr(), @@ -8150,6 +8269,7 @@ def test_nested_graph_state( } } ), + interrupts=(), ) # test full history at the end actual_history = list(app.get_state_history(config)) @@ -8186,6 +8306,7 @@ def test_nested_graph_state( } } ), + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value here and there"}, @@ -8220,6 +8341,7 @@ def test_nested_graph_state( "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value"}, @@ -8257,6 +8379,7 @@ def test_nested_graph_state( "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={"my_key": "my value"}, @@ -8291,6 +8414,7 @@ def test_nested_graph_state( "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={}, @@ -8319,6 +8443,7 @@ def test_nested_graph_state( }, created_at=AnyStr(), parent_config=None, + interrupts=(), ), ] if "shallow" in checkpointer_name: @@ -8439,6 +8564,7 @@ def test_doubly_nested_graph_state( } } ), + interrupts=(), ) child_state = app.get_state(outer_state.tasks[0].state) assert child_state == StateSnapshot( @@ -8500,6 +8626,7 @@ def test_doubly_nested_graph_state( } } ), + interrupts=(), ) grandchild_state = app.get_state(child_state.tasks[0].state) assert grandchild_state == StateSnapshot( @@ -8562,6 +8689,7 @@ def test_doubly_nested_graph_state( } } ), + interrupts=(), ) # get state with subgraphs assert app.get_state(config, subgraphs=True) == StateSnapshot( @@ -8649,6 +8777,7 @@ def test_doubly_nested_graph_state( } } ), + interrupts=(), ), ), ), @@ -8690,6 +8819,7 @@ def test_doubly_nested_graph_state( } } ), + interrupts=(), ), ), ), @@ -8720,6 +8850,7 @@ def test_doubly_nested_graph_state( } } ), + interrupts=(), ) # # resume assert [c for c in app.stream(None, config, subgraphs=True)] == [ @@ -8767,6 +8898,7 @@ def test_doubly_nested_graph_state( } } ), + interrupts=(), ) ) @@ -8804,6 +8936,7 @@ def test_doubly_nested_graph_state( "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value here and there"}, @@ -8838,6 +8971,7 @@ def test_doubly_nested_graph_state( result={"my_key": "hi my value here and there and back again"}, ), ), + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value"}, @@ -8878,6 +9012,7 @@ def test_doubly_nested_graph_state( "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={"my_key": "my value"}, @@ -8912,6 +9047,7 @@ def test_doubly_nested_graph_state( result={"my_key": "hi my value"}, ), ), + interrupts=(), ), StateSnapshot( values={}, @@ -8932,6 +9068,7 @@ def test_doubly_nested_graph_state( }, created_at=AnyStr(), parent_config=None, + interrupts=(), tasks=( PregelTask( id=AnyStr(), @@ -8982,6 +9119,7 @@ def test_doubly_nested_graph_state( } }, tasks=(), + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value"}, @@ -9033,6 +9171,7 @@ def test_doubly_nested_graph_state( result={"my_key": "hi my value here and there"}, ), ), + interrupts=(), ), StateSnapshot( values={}, @@ -9061,6 +9200,7 @@ def test_doubly_nested_graph_state( }, created_at=AnyStr(), parent_config=None, + interrupts=(), tasks=( PregelTask( id=AnyStr(), @@ -9127,6 +9267,7 @@ def test_doubly_nested_graph_state( } }, tasks=(), + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value here"}, @@ -9188,6 +9329,7 @@ def test_doubly_nested_graph_state( result={"my_key": "hi my value here and there"}, ), ), + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value"}, @@ -9249,6 +9391,7 @@ def test_doubly_nested_graph_state( result={"my_key": "hi my value here"}, ), ), + interrupts=(), ), StateSnapshot( values={}, @@ -9289,6 +9432,7 @@ def test_doubly_nested_graph_state( }, created_at=AnyStr(), parent_config=None, + interrupts=(), tasks=( PregelTask( id=AnyStr(), @@ -9565,6 +9709,7 @@ def test_send_react_interrupt( } } ), + interrupts=(), tasks=( PregelTask( id=AnyStr(), @@ -9628,6 +9773,7 @@ def test_send_react_interrupt( } } ), + interrupts=(), tasks=(), ) @@ -9725,6 +9871,7 @@ def test_send_react_interrupt( } } ), + interrupts=(), tasks=( PregelTask( id=AnyStr(), @@ -9816,6 +9963,7 @@ def test_send_react_interrupt( } } ), + interrupts=(), tasks=( PregelTask( id=AnyStr(), @@ -10046,6 +10194,7 @@ def test_send_react_interrupt_control( result=None, ), ), + interrupts=(), ) # remove the tool call, clearing the pending task @@ -10098,6 +10247,7 @@ def test_send_react_interrupt_control( } } ), + interrupts=(), tasks=(), ) @@ -10300,6 +10450,7 @@ def test_weather_subgraph( }, ), ), + interrupts=(), ) # update @@ -10442,9 +10593,11 @@ def test_weather_subgraph( path=(PULL, "weather_node"), ), ), + interrupts=(), ), ), ), + interrupts=(), ) graph.update_state( state.tasks[0].state.config, @@ -10547,10 +10700,12 @@ def test_weather_subgraph( } } ), + interrupts=(), tasks=(), ), ), ), + interrupts=(), ) assert [ c diff --git a/libs/langgraph/tests/test_large_cases_async.py b/libs/langgraph/tests/test_large_cases_async.py index 3062682f7..daed3f643 100644 --- a/libs/langgraph/tests/test_large_cases_async.py +++ b/libs/langgraph/tests/test_large_cases_async.py @@ -141,6 +141,7 @@ async def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[1].config, + interrupts=(), ), StateSnapshot( values={"inbox": 4, "output": 4, "input": 3}, @@ -164,6 +165,7 @@ async def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[2].config, + interrupts=(), ), StateSnapshot( values={"inbox": 21, "output": 4, "input": 3}, @@ -187,6 +189,7 @@ async def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[3].config, + interrupts=(), ), StateSnapshot( values={"inbox": 21, "output": 4, "input": 20}, @@ -208,6 +211,7 @@ async def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[4].config, + interrupts=(), ), StateSnapshot( values={"inbox": 3, "output": 4, "input": 20}, @@ -231,6 +235,7 @@ async def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[5].config, + interrupts=(), ), StateSnapshot( values={"inbox": 3, "output": 4, "input": 2}, @@ -252,6 +257,7 @@ async def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[6].config, + interrupts=(), ), StateSnapshot( values={"inbox": 3, "input": 2}, @@ -275,6 +281,7 @@ async def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=history[7].config, + interrupts=(), ), StateSnapshot( values={"input": 2}, @@ -298,6 +305,7 @@ async def test_invoke_two_processes_in_out_interrupt( }, created_at=AnyStr(), parent_config=None, + interrupts=(), ), ] @@ -374,6 +382,7 @@ async def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[1].config, + interrupts=(), ), StateSnapshot( values=5, @@ -395,6 +404,7 @@ async def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[2].config, + interrupts=(), ), StateSnapshot( values=4, @@ -416,6 +426,7 @@ async def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[3].config, + interrupts=(), ), StateSnapshot( values=3, @@ -437,6 +448,7 @@ async def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[4].config, + interrupts=(), ), StateSnapshot( values=2, @@ -458,6 +470,7 @@ async def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[5].config, + interrupts=(), ), StateSnapshot( values=1, @@ -479,6 +492,7 @@ async def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=history[6].config, + interrupts=(), ), StateSnapshot( values=0, @@ -502,6 +516,7 @@ async def test_fork_always_re_runs_nodes( }, created_at=AnyStr(), parent_config=None, + interrupts=(), ), ] @@ -858,6 +873,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) await app_w_interrupt.aupdate_state( @@ -916,6 +932,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) assert [c async for c in app_w_interrupt.astream(None, config)] == [ @@ -1049,6 +1066,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) # test state get/update methods with interrupt_before @@ -1124,6 +1142,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) await app_w_interrupt.aupdate_state( @@ -1182,6 +1201,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) assert [c async for c in app_w_interrupt.astream(None, config)] == [ @@ -1315,6 +1335,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) # test re-invoke to continue with interrupt_before @@ -1390,6 +1411,7 @@ async def test_conditional_graph(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) assert [c async for c in app_w_interrupt.astream(None, config)] == [ @@ -1819,6 +1841,7 @@ async def test_conditional_graph_state( c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) async with assert_ctx_once(): @@ -1874,6 +1897,7 @@ async def test_conditional_graph_state( c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) async with assert_ctx_once(): @@ -1963,6 +1987,7 @@ async def test_conditional_graph_state( c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) # test state get/update methods with interrupt_before @@ -2031,6 +2056,7 @@ async def test_conditional_graph_state( c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) await app_w_interrupt.aupdate_state( @@ -2085,6 +2111,7 @@ async def test_conditional_graph_state( c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) assert [c async for c in app_w_interrupt.astream(None, config)] == [ @@ -2172,6 +2199,7 @@ async def test_conditional_graph_state( c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) @@ -2796,6 +2824,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) # modify ai message @@ -2854,6 +2883,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) assert [c async for c in app_w_interrupt.astream(None, config)] == [ @@ -2968,6 +2998,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) await app_w_interrupt.aupdate_state( @@ -3022,6 +3053,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) # interrupt before tools @@ -3109,6 +3141,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) # modify ai message @@ -3167,6 +3200,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) assert [c async for c in app_w_interrupt.astream(None, config)] == [ @@ -3281,6 +3315,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) await app_w_interrupt.aupdate_state( @@ -3335,6 +3370,7 @@ async def test_state_graph_packets(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) @@ -3600,6 +3636,7 @@ async def test_message_graph(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) # modify ai message @@ -3654,6 +3691,7 @@ async def test_message_graph(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) assert [c async for c in app_w_interrupt.astream(None, config)] == [ @@ -3744,6 +3782,7 @@ async def test_message_graph(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) await app_w_interrupt.aupdate_state( @@ -3792,6 +3831,7 @@ async def test_message_graph(checkpointer_name: str) -> None: c async for c in app_w_interrupt.checkpointer.alist(config, limit=2) ][-1].config ), + interrupts=(), ) @@ -4131,6 +4171,7 @@ async def test_start_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) # resume, for same result as above assert await tool_two.ainvoke(None, thread1, debug=1) == { @@ -4164,6 +4205,7 @@ async def test_start_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) thread2 = {"configurable": {"thread_id": "2", "assistant_id": "a"}} @@ -4199,6 +4241,7 @@ async def test_start_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) # resume, for same result as above assert await tool_two.ainvoke(None, thread2, debug=1) == { @@ -4232,6 +4275,7 @@ async def test_start_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) thread3 = {"configurable": {"thread_id": "3", "assistant_id": "b"}} @@ -4267,6 +4311,7 @@ async def test_start_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) # update state await tool_two.aupdate_state(thread3, {"my_key": "key"}) # appends to my_key @@ -4297,6 +4342,7 @@ async def test_start_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) # resume, for same result as above assert await tool_two.ainvoke(None, thread3, debug=1) == { @@ -4330,6 +4376,7 @@ async def test_start_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) @@ -4872,6 +4919,7 @@ async def test_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) # resume, for same result as above assert await tool_two.ainvoke(None, thread1, debug=1) == { @@ -4904,6 +4952,7 @@ async def test_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) thread2 = {"configurable": {"thread_id": "12"}} @@ -4938,6 +4987,7 @@ async def test_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) # resume, for same result as above assert await tool_two.ainvoke(None, thread2, debug=1) == { @@ -4970,6 +5020,7 @@ async def test_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) tool_two = tool_two_graph.compile( @@ -5012,6 +5063,7 @@ async def test_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) # resume, for same result as above assert await tool_two.ainvoke(None, thread1, debug=1) == { @@ -5044,6 +5096,7 @@ async def test_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) thread2 = {"configurable": {"thread_id": "22"}} @@ -5078,6 +5131,7 @@ async def test_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) # resume, for same result as above assert await tool_two.ainvoke(None, thread2, debug=1) == { @@ -5110,6 +5164,7 @@ async def test_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) thread3 = {"configurable": {"thread_id": "23"}} @@ -5132,6 +5187,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "thread_id": "23", }, parent_config=None, + interrupts=(), ) # run from this point assert await tool_two.ainvoke(None, thread3) == { @@ -5159,6 +5215,7 @@ async def test_branch_then(checkpointer_name: str) -> None: "thread_id": "23", }, parent_config=(None if "shallow" in checkpointer_name else uconfig), + interrupts=(), ) # resume, for same result as above assert await tool_two.ainvoke(None, thread3, debug=1) == { @@ -5191,6 +5248,7 @@ async def test_branch_then(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) @@ -5287,6 +5345,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ) # now, get_state with subgraphs state assert await app.aget_state(config, subgraphs=True) == StateSnapshot( @@ -5353,6 +5412,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ), ), ), @@ -5383,6 +5443,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ) # get_state_history returns outer graph checkpoints history = [c async for c in app.aget_state_history(config)] @@ -5429,6 +5490,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ), StateSnapshot( values={"my_key": "my value"}, @@ -5463,6 +5525,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={}, @@ -5491,6 +5554,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: }, created_at=AnyStr(), parent_config=None, + interrupts=(), ), ] @@ -5550,6 +5614,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } ), tasks=(PregelTask(AnyStr(), "inner_2", (PULL, "inner_2")),), + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value"}, @@ -5598,6 +5663,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: }, ), ), + interrupts=(), ), StateSnapshot( values={}, @@ -5634,6 +5700,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: result={"my_key": "hi my value"}, ), ), + interrupts=(), ), ] @@ -5677,6 +5744,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ) # test full history at the end actual_history = [c async for c in app.aget_state_history(config)] @@ -5715,6 +5783,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value here and there"}, @@ -5749,6 +5818,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value"}, @@ -5789,6 +5859,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={"my_key": "my value"}, @@ -5823,6 +5894,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={}, @@ -5851,6 +5923,7 @@ async def test_nested_graph_state(checkpointer_name: str) -> None: }, created_at=AnyStr(), parent_config=None, + interrupts=(), ), ] if "shallow" in checkpointer_name: @@ -5970,6 +6043,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ) child_state = await app.aget_state(outer_state.tasks[0].state) assert child_state == StateSnapshot( @@ -6031,6 +6105,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ) grandchild_state = await app.aget_state(child_state.tasks[0].state) assert grandchild_state == StateSnapshot( @@ -6095,6 +6170,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ) # get state with subgraphs assert await app.aget_state(config, subgraphs=True) == StateSnapshot( @@ -6186,6 +6262,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ), ), ), @@ -6229,6 +6306,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ), ), ), @@ -6259,6 +6337,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ) # resume assert [c async for c in app.astream(None, config, subgraphs=True)] == [ @@ -6311,6 +6390,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } } ), + interrupts=(), ) ) @@ -6352,6 +6432,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value here and there"}, @@ -6383,6 +6464,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: id=AnyStr(), name="parent_2", path=(PULL, "parent_2") ), ), + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value"}, @@ -6422,6 +6504,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: "checkpoint_id": AnyStr(), } }, + interrupts=(), ), StateSnapshot( values={"my_key": "my value"}, @@ -6453,6 +6536,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: id=AnyStr(), name="parent_1", path=(PULL, "parent_1") ), ), + interrupts=(), ), StateSnapshot( values={}, @@ -6478,6 +6562,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: id=AnyStr(), name="__start__", path=(PULL, "__start__") ), ), + interrupts=(), ), ][0] ) @@ -6523,6 +6608,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } }, tasks=(), + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value"}, @@ -6574,6 +6660,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: result={"my_key": "hi my value here and there"}, ), ), + interrupts=(), ), StateSnapshot( values={}, @@ -6610,6 +6697,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: result={"my_key": "hi my value"}, ), ), + interrupts=(), ), ] # get grandchild graph history @@ -6674,6 +6762,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: } }, tasks=(), + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value here"}, @@ -6737,6 +6826,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: result={"my_key": "hi my value here and there"}, ), ), + interrupts=(), ), StateSnapshot( values={"my_key": "hi my value"}, @@ -6800,6 +6890,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: result={"my_key": "hi my value here"}, ), ), + interrupts=(), ), StateSnapshot( values={}, @@ -6850,6 +6941,7 @@ async def test_doubly_nested_graph_state(checkpointer_name: str) -> None: result={"my_key": "hi my value"}, ), ), + interrupts=(), ), ] @@ -7131,6 +7223,7 @@ async def test_weather_subgraph( }, ), ), + interrupts=(), ) # confirm that list() delegates to alist() correctly assert await asyncio.to_thread(get_first_in_list) == state @@ -7270,6 +7363,7 @@ async def test_weather_subgraph( } } ), + interrupts=(), tasks=( PregelTask( id=AnyStr(), @@ -7280,6 +7374,7 @@ async def test_weather_subgraph( ), ), ), + interrupts=(), ) await graph.aupdate_state( state.tasks[0].state.config, @@ -7319,6 +7414,7 @@ async def test_weather_subgraph( } } ), + interrupts=(), tasks=( PregelTask( id=AnyStr(), @@ -7385,6 +7481,7 @@ async def test_weather_subgraph( } ), tasks=(), + interrupts=(), ), ), ), diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index d8259a1e2..9d0274255 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -2491,6 +2491,7 @@ def test_in_one_fan_out_state_graph_waiting_edge( "thread_id": "2", }, parent_config=expected_parent_config, + interrupts=(), ) assert [c for c in app_w_interrupt.stream(None, config, debug=1)] == [ @@ -5224,6 +5225,7 @@ def test_parent_command(request: pytest.FixtureRequest, checkpointer_name: str) } ), tasks=(), + interrupts=(), ) diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index 01e354b6b..e5ca5248d 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -650,6 +650,13 @@ async def test_dynamic_interrupt(checkpointer_name: str) -> None: -1 ].config ), + interrupts=( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ), ) # clear the interrupt and next tasks @@ -676,6 +683,7 @@ async def test_dynamic_interrupt(checkpointer_name: str) -> None: -1 ].config ), + interrupts=(), ) @@ -848,6 +856,13 @@ async def test_dynamic_interrupt_subgraph(checkpointer_name: str) -> None: c async for c in tool_two.checkpointer.alist(thread1root, limit=2) ][-1].config ), + interrupts=( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:"), AnyStr("do:")], + ), + ), ) # clear the interrupt and next tasks @@ -874,6 +889,7 @@ async def test_dynamic_interrupt_subgraph(checkpointer_name: str) -> None: c async for c in tool_two.checkpointer.alist(thread1root, limit=2) ][-1].config ), + interrupts=(), ) @@ -1047,6 +1063,13 @@ async def test_copy_checkpoint(checkpointer_name: str) -> None: -1 ].config ), + interrupts=( + Interrupt( + value="Just because...", + resumable=True, + ns=[AnyStr("tool_two:")], + ), + ), ) if "shallow" in checkpointer_name: @@ -1088,6 +1111,7 @@ async def test_copy_checkpoint(checkpointer_name: str) -> None: -1 ].parent_config ), + interrupts=(), ) @@ -2900,6 +2924,7 @@ async def test_send_dedupe_on_resume( } }, tasks=(), + interrupts=(), ), StateSnapshot( values=[ @@ -2946,6 +2971,7 @@ async def test_send_dedupe_on_resume( result=["3"], ), ), + interrupts=(), ), StateSnapshot( values=[ @@ -3013,6 +3039,7 @@ async def test_send_dedupe_on_resume( result=["3"], ), ), + interrupts=(Interrupt(value="Bahh", resumable=False, ns=None),), ), StateSnapshot( values=["0", "1"], @@ -3068,6 +3095,7 @@ async def test_send_dedupe_on_resume( result=["3.1"], ), ), + interrupts=(), ), StateSnapshot( values=["0"], @@ -3105,6 +3133,7 @@ async def test_send_dedupe_on_resume( result=["1"], ), ), + interrupts=(), ), StateSnapshot( values=[], @@ -3136,6 +3165,7 @@ async def test_send_dedupe_on_resume( result=["0"], ), ), + interrupts=(), ), ] if checkpoint_during: @@ -3338,6 +3368,7 @@ async def test_send_react_interrupt(checkpointer_name: str) -> None: result=None, ), ), + interrupts=(), ) # remove the tool call, clearing the pending task @@ -3391,6 +3422,7 @@ async def test_send_react_interrupt(checkpointer_name: str) -> None: } ), tasks=(), + interrupts=(), ) # tool call not executed @@ -3496,6 +3528,7 @@ async def test_send_react_interrupt(checkpointer_name: str) -> None: result=None, ), ), + interrupts=(), ) # replace the tool call, should clear previous send, create new one @@ -3587,6 +3620,7 @@ async def test_send_react_interrupt(checkpointer_name: str) -> None: result=None, ), ), + interrupts=(), ) # prev tool call not executed, new tool call is @@ -3804,6 +3838,7 @@ async def test_send_react_interrupt_control( result=None, ), ), + interrupts=(), ) # remove the tool call, clearing the pending task @@ -3857,6 +3892,7 @@ async def test_send_react_interrupt_control( } ), tasks=(), + interrupts=(), ) # tool call not executed @@ -4937,6 +4973,7 @@ async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class( } } ), + interrupts=(), ) async with assert_ctx_once(): @@ -6635,6 +6672,7 @@ async def test_parent_command(checkpointer_name: str) -> None: } ), tasks=(), + interrupts=(), ) diff --git a/libs/langgraph/tests/test_remote_graph.py b/libs/langgraph/tests/test_remote_graph.py index b30ec3a05..356a8c7d5 100644 --- a/libs/langgraph/tests/test_remote_graph.py +++ b/libs/langgraph/tests/test_remote_graph.py @@ -183,6 +183,7 @@ def test_get_state(): created_at="timestamp", parent_config=None, tasks=(), + interrupts=(), ) @@ -240,6 +241,7 @@ async def test_aget_state(): } }, tasks=(), + interrupts=(), ) @@ -290,6 +292,7 @@ def test_get_state_history(): created_at="timestamp", parent_config=None, tasks=(), + interrupts=(), ) @@ -343,6 +346,7 @@ async def test_aget_state_history(): created_at="timestamp", parent_config=None, tasks=(), + interrupts=(), )