From b229c080278db064165db7ee1d0e6884a6538f1b Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Thu, 5 Sep 2024 17:02:07 -0700 Subject: [PATCH] langgraph 0.2.18 --- .../langgraph/checkpoint/base/__init__.py | 6 +- libs/langgraph/langgraph/constants.py | 2 + libs/langgraph/langgraph/errors.py | 6 + libs/langgraph/langgraph/pregel/__init__.py | 74 +++++------- libs/langgraph/langgraph/pregel/algo.py | 14 ++- libs/langgraph/langgraph/pregel/loop.py | 49 +++++--- libs/langgraph/langgraph/pregel/types.py | 2 + libs/langgraph/poetry.lock | 107 ++++++++++-------- libs/langgraph/pyproject.toml | 2 +- libs/langgraph/tests/test_algo.py | 4 +- libs/langgraph/tests/test_pregel.py | 4 +- libs/langgraph/tests/test_pregel_async.py | 24 ++-- 12 files changed, 153 insertions(+), 141 deletions(-) diff --git a/libs/checkpoint/langgraph/checkpoint/base/__init__.py b/libs/checkpoint/langgraph/checkpoint/base/__init__.py index 94d34622c..db1467149 100644 --- a/libs/checkpoint/langgraph/checkpoint/base/__init__.py +++ b/libs/checkpoint/langgraph/checkpoint/base/__init__.py @@ -25,6 +25,7 @@ from langgraph.checkpoint.serde.types import ( ChannelProtocol, SendProtocol, ) +from langgraph.constants import SCHEDULED V = TypeVar("V", int, float, str) PendingWrite = Tuple[str, str, Any] @@ -440,8 +441,7 @@ def get_checkpoint_id(config: RunnableConfig) -> Optional[str]: Mapping from error type to error index. Regular writes just map to their index in the list of writes being saved. Special writes (e.g. errors) map to negative indices, to avoid those writes from -saving regular writes. +conflicting with regular writes. Each Checkpointer implementation should use this mapping in put_writes. """ -WRITES_IDX_MAP = {ERROR: -1} -# TODO To store scheduled status of tasks, add a special channel here +WRITES_IDX_MAP = {ERROR: -1, SCHEDULED: -2} diff --git a/libs/langgraph/langgraph/constants.py b/libs/langgraph/langgraph/constants.py index 64d4644f6..f19c562f5 100644 --- a/libs/langgraph/langgraph/constants.py +++ b/libs/langgraph/langgraph/constants.py @@ -13,10 +13,12 @@ CONFIG_KEY_TASK_ID = "__pregel_task_id" CONFIG_KEY_CHECKPOINT_MAP = "checkpoint_map" INTERRUPT = "__interrupt__" ERROR = "__error__" +SCHEDULED = "__scheduled__" TASKS = "__pregel_tasks" SUBSCRIPTIONS = "__pregel_subscriptions" RUNTIME_PLACEHOLDER = "__pregel_runtime_placeholder__" RESERVED = { + SCHEDULED, INTERRUPT, ERROR, TASKS, diff --git a/libs/langgraph/langgraph/errors.py b/libs/langgraph/langgraph/errors.py index 25ba93a72..aa5b57857 100644 --- a/libs/langgraph/langgraph/errors.py +++ b/libs/langgraph/langgraph/errors.py @@ -49,6 +49,12 @@ class EmptyInputError(Exception): pass +class TaskNotFound(Exception): + """Raised when the executor is unable to find a task.""" + + pass + + __all__ = [ "GraphRecursionError", "InvalidUpdateError", diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 2537ca0e4..acb225090 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -68,6 +68,7 @@ from langgraph.constants import ( from langgraph.errors import GraphRecursionError, InvalidUpdateError from langgraph.managed.base import ManagedValueSpec from langgraph.pregel.algo import ( + PregelTaskWrites, apply_writes, local_read, local_write, @@ -80,15 +81,8 @@ from langgraph.pregel.manager import AsyncChannelsManager, ChannelsManager from langgraph.pregel.read import PregelNode from langgraph.pregel.retry import RetryPolicy from langgraph.pregel.runner import PregelRunner -from langgraph.pregel.types import ( - All, - PregelExecutableTask, - StateSnapshot, - StreamMode, -) -from langgraph.pregel.utils import ( - get_new_channel_versions, -) +from langgraph.pregel.types import All, StateSnapshot, StreamMode +from langgraph.pregel.utils import get_new_channel_versions from langgraph.pregel.validate import validate_graph, validate_keys from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry from langgraph.store.base import BaseStore @@ -425,7 +419,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): subgraphs = dict(self.get_subgraphs()) parent_ns = saved.config["configurable"].get("checkpoint_ns", "") task_states: dict[str, Union[RunnableConfig, StateSnapshot]] = {} - for task in next_tasks: + for task in next_tasks.values(): if task.name not in subgraphs: continue # assemble checkpoint_ns for this task @@ -456,12 +450,12 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): # assemble the state snapshot return StateSnapshot( read_channels(channels, self.stream_channels_asis), - tuple(t.name for t in next_tasks), + tuple(t.name for t in next_tasks.values()), patch_checkpoint_map(saved.config, saved.metadata), saved.metadata, saved.checkpoint["ts"], saved.parent_config, - tasks_w_writes(next_tasks, saved.pending_writes, task_states), + tasks_w_writes(next_tasks.values(), saved.pending_writes, task_states), ) async def _aprepare_state_snapshot( @@ -501,7 +495,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): subgraphs = {n: g async for n, g in self.aget_subgraphs()} parent_ns = saved.config["configurable"].get("checkpoint_ns", "") task_states: dict[str, Union[RunnableConfig, StateSnapshot]] = {} - for task in next_tasks: + for task in next_tasks.values(): if task.name not in subgraphs: continue # assemble checkpoint_ns for this task @@ -532,12 +526,12 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): # assemble the state snapshot return StateSnapshot( read_channels(channels, self.stream_channels_asis), - tuple(t.name for t in next_tasks), + tuple(t.name for t in next_tasks.values()), patch_checkpoint_map(saved.config, saved.metadata), saved.metadata, saved.checkpoint["ts"], saved.parent_config, - tasks_w_writes(next_tasks, saved.pending_writes, task_states), + tasks_w_writes(next_tasks.values(), saved.pending_writes, task_states), ) def get_state( @@ -809,20 +803,13 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): writers = self.nodes[as_node].flat_writers if not writers: raise InvalidUpdateError(f"Node {as_node} has no writers") - task = PregelExecutableTask( - as_node, - values, - RunnableSequence(*writers) if len(writers) > 1 else writers[0], - deque(), - None, - [INTERRUPT], - None, - None, - str(uuid5(UUID(checkpoint["id"]), INTERRUPT)), - ) + writes = deque() + task = PregelTaskWrites(as_node, writes, [INTERRUPT]) + task_id = str(uuid5(UUID(checkpoint["id"]), INTERRUPT)) + run = RunnableSequence(*writers) if len(writers) > 1 else writers[0] # execute task - task.proc.invoke( - task.input, + run.invoke( + values, patch_config( config, run_name=self.name + "UpdateState", @@ -831,7 +818,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): CONFIG_KEY_SEND: partial( local_write, step + 1, - task.writes.extend, + writes.extend, self.nodes, channels, managed, @@ -850,7 +837,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): ) # save task writes if saved: - checkpointer.put_writes(checkpoint_config, task.writes, task.id) + checkpointer.put_writes(checkpoint_config, task.writes, task_id) # apply to checkpoint and save assert not apply_writes( checkpoint, channels, [task], checkpointer.get_next_version @@ -973,20 +960,13 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): writers = self.nodes[as_node].flat_writers if not writers: raise InvalidUpdateError(f"Node {as_node} has no writers") - task = PregelExecutableTask( - as_node, - values, - RunnableSequence(*writers) if len(writers) > 1 else writers[0], - deque(), - None, - [INTERRUPT], - None, - None, - str(uuid5(UUID(checkpoint["id"]), INTERRUPT)), - ) + writes = deque() + task = PregelTaskWrites(as_node, writes, [INTERRUPT]) + task_id = str(uuid5(UUID(checkpoint["id"]), INTERRUPT)) + run = RunnableSequence(*writers) if len(writers) > 1 else writers[0] # execute task - await task.proc.ainvoke( - task.input, + await run.ainvoke( + values, patch_config( config, run_name=self.name + "UpdateState", @@ -995,7 +975,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): CONFIG_KEY_SEND: partial( local_write, step + 1, - task.writes.extend, + writes.extend, self.nodes, channels, managed, @@ -1014,7 +994,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): ) # save task writes if saved: - await checkpointer.aput_writes(checkpoint_config, task.writes, task.id) + await checkpointer.aput_writes(checkpoint_config, writes, task_id) # apply to checkpoint and save assert not apply_writes( checkpoint, channels, [task], checkpointer.get_next_version @@ -1239,7 +1219,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): manager=run_manager, ): for _ in runner.tick( - loop.tasks, + loop.tasks.values(), timeout=self.step_timeout, retry_policy=self.retry_policy, ): @@ -1429,7 +1409,7 @@ class Pregel(Runnable[Union[dict[str, Any], Any], Union[dict[str, Any], Any]]): manager=run_manager, ): async for _ in runner.atick( - loop.tasks, + loop.tasks.values(), timeout=self.step_timeout, retry_policy=self.retry_policy, ): diff --git a/libs/langgraph/langgraph/pregel/algo.py b/libs/langgraph/langgraph/pregel/algo.py index 26920297f..033fab8e0 100644 --- a/libs/langgraph/langgraph/pregel/algo.py +++ b/libs/langgraph/langgraph/pregel/algo.py @@ -245,7 +245,7 @@ def prepare_next_tasks( is_resuming: bool = False, checkpointer: Literal[None] = None, manager: Literal[None] = None, -) -> list[PregelTask]: ... +) -> dict[str, PregelTask]: ... @overload @@ -261,7 +261,7 @@ def prepare_next_tasks( is_resuming: bool, checkpointer: Optional[BaseCheckpointSaver], manager: Union[None, ParentRunManager, AsyncParentRunManager], -) -> list[PregelExecutableTask]: ... +) -> dict[str, PregelExecutableTask]: ... def prepare_next_tasks( @@ -276,8 +276,8 @@ def prepare_next_tasks( is_resuming: bool = False, checkpointer: Optional[BaseCheckpointSaver] = None, manager: Union[None, ParentRunManager, AsyncParentRunManager] = None, -) -> Union[list[PregelTask], list[PregelExecutableTask]]: - tasks: Union[list[PregelTask], list[PregelExecutableTask]] = [] +) -> Union[dict[str, PregelTask], dict[str, PregelExecutableTask]]: + tasks: Union[dict[str, PregelTask], dict[str, PregelExecutableTask]] = {} # Consume pending packets for idx, _ in enumerate(checkpoint["pending_sends"]): if task := prepare_single_task( @@ -294,7 +294,7 @@ def prepare_next_tasks( checkpointer=checkpointer, manager=manager, ): - tasks.append(task) + tasks[task.id] = task # Check if any processes should be run in next step # If so, prepare the values to be passed to them for name in processes: @@ -312,7 +312,7 @@ def prepare_next_tasks( checkpointer=checkpointer, manager=manager, ): - tasks.append(task) + tasks[task.id] = task return tasks @@ -425,6 +425,7 @@ def prepare_single_task( proc.retry_policy, None, task_id, + task_path, ) else: @@ -532,6 +533,7 @@ def prepare_single_task( proc.retry_policy, None, task_id, + task_path, ) else: return PregelTask(task_id, name) diff --git a/libs/langgraph/langgraph/pregel/loop.py b/libs/langgraph/langgraph/pregel/loop.py index 703882ab2..afd29ec1b 100644 --- a/libs/langgraph/langgraph/pregel/loop.py +++ b/libs/langgraph/langgraph/pregel/loop.py @@ -45,6 +45,7 @@ from langgraph.constants import ( ERROR, INPUT, INTERRUPT, + SCHEDULED, TAG_HIDDEN, ) from langgraph.errors import EmptyInputError, GraphInterrupt @@ -149,7 +150,7 @@ class PregelLoop: status: Literal[ "pending", "done", "interrupt_before", "interrupt_after", "out_of_steps" ] - tasks: Sequence[PregelExecutableTask] + tasks: dict[str, PregelExecutableTask] output: Union[None, dict[str, Any], Any] = None # public @@ -243,8 +244,8 @@ class PregelLoop: if self.input not in (INPUT_DONE, INPUT_RESUMING): self._first(input_keys=input_keys) - elif all(task.writes for task in self.tasks): - writes = [w for t in self.tasks for w in t.writes] + elif all(task.writes for task in self.tasks.values()): + writes = [w for t in self.tasks.values() for w in t.writes] # debug flag if self.debug: print_step_writes( @@ -258,7 +259,7 @@ class PregelLoop: mv_writes = apply_writes( self.checkpoint, self.channels, - self.tasks, + self.tasks.values(), self.checkpointer_get_next_version, ) # apply writes to managed values @@ -277,13 +278,14 @@ class PregelLoop: "source": "loop", "writes": single( map_output_updates( - self.output_keys, [(t, t.writes) for t in self.tasks] + self.output_keys, + [(t, t.writes) for t in self.tasks.values()], ) ), } ) # after execution, check if we should interrupt - if should_interrupt(self.checkpoint, interrupt_after, self.tasks): + if should_interrupt(self.checkpoint, interrupt_after, self.tasks.values()): self.status = "interrupt_after" if self.is_nested: raise GraphInterrupt() @@ -322,7 +324,7 @@ class PregelLoop: self.stream_keys, self.checkpoint_metadata, self.checkpoint, - self.tasks, + self.tasks.values(), self.checkpoint_pending_writes, ) ) @@ -337,15 +339,18 @@ class PregelLoop: for tid, k, v in self.checkpoint_pending_writes: if k in (ERROR, INTERRUPT): continue - if task := next((t for t in self.tasks if t.id == tid), None): - task.writes.append((k, v)) + if task := self.tasks.get(tid): + if k == SCHEDULED: + self.tasks[tid] = task._replace(scheduled=True) + else: + task.writes.append((k, v)) # print output for any tasks we applied previous writes to - for task in self.tasks: + for task in self.tasks.values(): if task.writes: self._output_writes(task.id, task.writes, cached=True) # if all tasks have finished, re-tick - if all(task.writes for task in self.tasks): + if all(task.writes for task in self.tasks.values()): return self.tick( input_keys=input_keys, interrupt_after=interrupt_after, @@ -354,7 +359,7 @@ class PregelLoop: ) # before execution, check if we should interrupt - if should_interrupt(self.checkpoint, interrupt_before, self.tasks): + if should_interrupt(self.checkpoint, interrupt_before, self.tasks.values()): self.status = "interrupt_before" if self.is_nested: raise GraphInterrupt() @@ -364,12 +369,12 @@ class PregelLoop: # produce debug output self._emit( (self.config["configurable"].get("checkpoint_ns", ""), "debug", v) - for v in map_debug_tasks(self.step, self.tasks) + for v in map_debug_tasks(self.step, self.tasks.values()) ) # debug flag if self.debug: - print_step_tasks(self.step, self.tasks) + print_step_tasks(self.step, self.tasks.values()) return True @@ -413,7 +418,7 @@ class PregelLoop: assert not apply_writes( self.checkpoint, self.channels, - discard_tasks + [PregelTaskWrites(INPUT, input_writes, [])], + [*discard_tasks.values(), PregelTaskWrites(INPUT, input_writes, [])], self.checkpointer_get_next_version, ), "Can't write to SharedValues in graph input" # save input checkpoint @@ -506,7 +511,7 @@ class PregelLoop: def _output_writes( self, task_id: str, writes: Sequence[tuple[str, Any]], *, cached: bool = False ) -> None: - if task := next((t for t in self.tasks if t.id == task_id), None): + if task := self.tasks.get(task_id): if task.config is not None and TAG_HIDDEN in task.config.get("tags"): return if writes[0][0] != ERROR and writes[0][0] != INTERRUPT: @@ -596,7 +601,11 @@ class SyncPregelLoop(PregelLoop, ContextManager): } self.checkpoint = copy_checkpoint(saved.checkpoint) self.checkpoint_metadata = saved.metadata - self.checkpoint_pending_writes = saved.pending_writes or [] + self.checkpoint_pending_writes = ( + [(str(tid), k, v) for tid, k, v in saved.pending_writes] + if saved.pending_writes is not None + else [] + ) self.submit = self.stack.enter_context(BackgroundExecutor(self.config)) self.channels, self.managed = self.stack.enter_context( @@ -694,7 +703,11 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager): } self.checkpoint = copy_checkpoint(saved.checkpoint) self.checkpoint_metadata = saved.metadata - self.checkpoint_pending_writes = saved.pending_writes or [] + self.checkpoint_pending_writes = ( + [(str(tid), k, v) for tid, k, v in saved.pending_writes] + if saved.pending_writes is not None + else [] + ) self.submit = await self.stack.enter_async_context(AsyncBackgroundExecutor()) self.channels, self.managed = await self.stack.enter_async_context( diff --git a/libs/langgraph/langgraph/pregel/types.py b/libs/langgraph/langgraph/pregel/types.py index 6da463286..80d0d8a23 100644 --- a/libs/langgraph/langgraph/pregel/types.py +++ b/libs/langgraph/langgraph/pregel/types.py @@ -81,6 +81,8 @@ class PregelExecutableTask(NamedTuple): retry_policy: Optional[RetryPolicy] cache_policy: Optional[CachePolicy] id: str + path: tuple[str, ...] + scheduled: bool = False class StateSnapshot(NamedTuple): diff --git a/libs/langgraph/poetry.lock b/libs/langgraph/poetry.lock index 1fb11b7ac..c9bf1c7fe 100644 --- a/libs/langgraph/poetry.lock +++ b/libs/langgraph/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiosqlite" @@ -1611,57 +1611,68 @@ test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync" [[package]] name = "orjson" -version = "3.10.5" +version = "3.10.7" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.10.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:545d493c1f560d5ccfc134803ceb8955a14c3fcb47bbb4b2fee0232646d0b932"}, - {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4324929c2dd917598212bfd554757feca3e5e0fa60da08be11b4aa8b90013c1"}, - {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c13ca5e2ddded0ce6a927ea5a9f27cae77eee4c75547b4297252cb20c4d30e6"}, - {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6c8e30adfa52c025f042a87f450a6b9ea29649d828e0fec4858ed5e6caecf63"}, - {file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:338fd4f071b242f26e9ca802f443edc588fa4ab60bfa81f38beaedf42eda226c"}, - {file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6970ed7a3126cfed873c5d21ece1cd5d6f83ca6c9afb71bbae21a0b034588d96"}, - {file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:235dadefb793ad12f7fa11e98a480db1f7c6469ff9e3da5e73c7809c700d746b"}, - {file = "orjson-3.10.5-cp310-none-win32.whl", hash = "sha256:be79e2393679eda6a590638abda16d167754393f5d0850dcbca2d0c3735cebe2"}, - {file = "orjson-3.10.5-cp310-none-win_amd64.whl", hash = "sha256:c4a65310ccb5c9910c47b078ba78e2787cb3878cdded1702ac3d0da71ddc5228"}, - {file = "orjson-3.10.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cdf7365063e80899ae3a697def1277c17a7df7ccfc979990a403dfe77bb54d40"}, - {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b68742c469745d0e6ca5724506858f75e2f1e5b59a4315861f9e2b1df77775a"}, - {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d10cc1b594951522e35a3463da19e899abe6ca95f3c84c69e9e901e0bd93d38"}, - {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcbe82b35d1ac43b0d84072408330fd3295c2896973112d495e7234f7e3da2e1"}, - {file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c0eb7e0c75e1e486c7563fe231b40fdd658a035ae125c6ba651ca3b07936f5"}, - {file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:53ed1c879b10de56f35daf06dbc4a0d9a5db98f6ee853c2dbd3ee9d13e6f302f"}, - {file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:099e81a5975237fda3100f918839af95f42f981447ba8f47adb7b6a3cdb078fa"}, - {file = "orjson-3.10.5-cp311-none-win32.whl", hash = "sha256:1146bf85ea37ac421594107195db8bc77104f74bc83e8ee21a2e58596bfb2f04"}, - {file = "orjson-3.10.5-cp311-none-win_amd64.whl", hash = "sha256:36a10f43c5f3a55c2f680efe07aa93ef4a342d2960dd2b1b7ea2dd764fe4a37c"}, - {file = "orjson-3.10.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:68f85ecae7af14a585a563ac741b0547a3f291de81cd1e20903e79f25170458f"}, - {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28afa96f496474ce60d3340fe8d9a263aa93ea01201cd2bad844c45cd21f5268"}, - {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cd684927af3e11b6e754df80b9ffafd9fb6adcaa9d3e8fdd5891be5a5cad51e"}, - {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d21b9983da032505f7050795e98b5d9eee0df903258951566ecc358f6696969"}, - {file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ad1de7fef79736dde8c3554e75361ec351158a906d747bd901a52a5c9c8d24b"}, - {file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d97531cdfe9bdd76d492e69800afd97e5930cb0da6a825646667b2c6c6c0211"}, - {file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d69858c32f09c3e1ce44b617b3ebba1aba030e777000ebdf72b0d8e365d0b2b3"}, - {file = "orjson-3.10.5-cp312-none-win32.whl", hash = "sha256:64c9cc089f127e5875901ac05e5c25aa13cfa5dbbbd9602bda51e5c611d6e3e2"}, - {file = "orjson-3.10.5-cp312-none-win_amd64.whl", hash = "sha256:b2efbd67feff8c1f7728937c0d7f6ca8c25ec81373dc8db4ef394c1d93d13dc5"}, - {file = "orjson-3.10.5-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:03b565c3b93f5d6e001db48b747d31ea3819b89abf041ee10ac6988886d18e01"}, - {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:584c902ec19ab7928fd5add1783c909094cc53f31ac7acfada817b0847975f26"}, - {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a35455cc0b0b3a1eaf67224035f5388591ec72b9b6136d66b49a553ce9eb1e6"}, - {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1670fe88b116c2745a3a30b0f099b699a02bb3482c2591514baf5433819e4f4d"}, - {file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185c394ef45b18b9a7d8e8f333606e2e8194a50c6e3c664215aae8cf42c5385e"}, - {file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ca0b3a94ac8d3886c9581b9f9de3ce858263865fdaa383fbc31c310b9eac07c9"}, - {file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dfc91d4720d48e2a709e9c368d5125b4b5899dced34b5400c3837dadc7d6271b"}, - {file = "orjson-3.10.5-cp38-none-win32.whl", hash = "sha256:c05f16701ab2a4ca146d0bca950af254cb7c02f3c01fca8efbbad82d23b3d9d4"}, - {file = "orjson-3.10.5-cp38-none-win_amd64.whl", hash = "sha256:8a11d459338f96a9aa7f232ba95679fc0c7cedbd1b990d736467894210205c09"}, - {file = "orjson-3.10.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:85c89131d7b3218db1b24c4abecea92fd6c7f9fab87441cfc342d3acc725d807"}, - {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66215277a230c456f9038d5e2d84778141643207f85336ef8d2a9da26bd7ca"}, - {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51bbcdea96cdefa4a9b4461e690c75ad4e33796530d182bdd5c38980202c134a"}, - {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbead71dbe65f959b7bd8cf91e0e11d5338033eba34c114f69078d59827ee139"}, - {file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df58d206e78c40da118a8c14fc189207fffdcb1f21b3b4c9c0c18e839b5a214"}, - {file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c4057c3b511bb8aef605616bd3f1f002a697c7e4da6adf095ca5b84c0fd43595"}, - {file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b39e006b00c57125ab974362e740c14a0c6a66ff695bff44615dcf4a70ce2b86"}, - {file = "orjson-3.10.5-cp39-none-win32.whl", hash = "sha256:eded5138cc565a9d618e111c6d5c2547bbdd951114eb822f7f6309e04db0fb47"}, - {file = "orjson-3.10.5-cp39-none-win_amd64.whl", hash = "sha256:cc28e90a7cae7fcba2493953cff61da5a52950e78dc2dacfe931a317ee3d8de7"}, - {file = "orjson-3.10.5.tar.gz", hash = "sha256:7a5baef8a4284405d96c90c7c62b755e9ef1ada84c2406c24a9ebec86b89f46d"}, + {file = "orjson-3.10.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf6ba8ebc8ef5792e2337fb0419f8009729335bb400ece005606336b7fd7bab7"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac7cf6222b29fbda9e3a472b41e6a5538b48f2c8f99261eecd60aafbdb60690c"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de817e2f5fc75a9e7dd350c4b0f54617b280e26d1631811a43e7e968fa71e3e9"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:348bdd16b32556cf8d7257b17cf2bdb7ab7976af4af41ebe79f9796c218f7e91"}, + {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:479fd0844ddc3ca77e0fd99644c7fe2de8e8be1efcd57705b5c92e5186e8a250"}, + {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fdf5197a21dd660cf19dfd2a3ce79574588f8f5e2dbf21bda9ee2d2b46924d84"}, + {file = "orjson-3.10.7-cp310-none-win32.whl", hash = "sha256:d374d36726746c81a49f3ff8daa2898dccab6596864ebe43d50733275c629175"}, + {file = "orjson-3.10.7-cp310-none-win_amd64.whl", hash = "sha256:cb61938aec8b0ffb6eef484d480188a1777e67b05d58e41b435c74b9d84e0b9c"}, + {file = "orjson-3.10.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6"}, + {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6"}, + {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0"}, + {file = "orjson-3.10.7-cp311-none-win32.whl", hash = "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f"}, + {file = "orjson-3.10.7-cp311-none-win_amd64.whl", hash = "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5"}, + {file = "orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09"}, + {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5"}, + {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b"}, + {file = "orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb"}, + {file = "orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1"}, + {file = "orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149"}, + {file = "orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe"}, + {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c"}, + {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad"}, + {file = "orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2"}, + {file = "orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024"}, + {file = "orjson-3.10.7-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6ea2b2258eff652c82652d5e0f02bd5e0463a6a52abb78e49ac288827aaa1469"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430ee4d85841e1483d487e7b81401785a5dfd69db5de01314538f31f8fbf7ee1"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b6146e439af4c2472c56f8540d799a67a81226e11992008cb47e1267a9b3225"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:084e537806b458911137f76097e53ce7bf5806dda33ddf6aaa66a028f8d43a23"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829cf2195838e3f93b70fd3b4292156fc5e097aac3739859ac0dcc722b27ac0"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1193b2416cbad1a769f868b1749535d5da47626ac29445803dae7cc64b3f5c98"}, + {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4e6c3da13e5a57e4b3dca2de059f243ebec705857522f188f0180ae88badd354"}, + {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c31008598424dfbe52ce8c5b47e0752dca918a4fdc4a2a32004efd9fab41d866"}, + {file = "orjson-3.10.7-cp38-none-win32.whl", hash = "sha256:7122a99831f9e7fe977dc45784d3b2edc821c172d545e6420c375e5a935f5a1c"}, + {file = "orjson-3.10.7-cp38-none-win_amd64.whl", hash = "sha256:a763bc0e58504cc803739e7df040685816145a6f3c8a589787084b54ebc9f16e"}, + {file = "orjson-3.10.7-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e76be12658a6fa376fcd331b1ea4e58f5a06fd0220653450f0d415b8fd0fbe20"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed350d6978d28b92939bfeb1a0570c523f6170efc3f0a0ef1f1df287cd4f4960"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144888c76f8520e39bfa121b31fd637e18d4cc2f115727865fdf9fa325b10412"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09b2d92fd95ad2402188cf51573acde57eb269eddabaa60f69ea0d733e789fe9"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b24a579123fa884f3a3caadaed7b75eb5715ee2b17ab5c66ac97d29b18fe57f"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591bcfe7512353bd609875ab38050efe3d55e18934e2f18950c108334b4ff"}, + {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f4db56635b58cd1a200b0a23744ff44206ee6aa428185e2b6c4a65b3197abdcd"}, + {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0fa5886854673222618638c6df7718ea7fe2f3f2384c452c9ccedc70b4a510a5"}, + {file = "orjson-3.10.7-cp39-none-win32.whl", hash = "sha256:8272527d08450ab16eb405f47e0f4ef0e5ff5981c3d82afe0efd25dcbef2bcd2"}, + {file = "orjson-3.10.7-cp39-none-win_amd64.whl", hash = "sha256:974683d4618c0c7dbf4f69c95a979734bf183d0658611760017f6e70a145af58"}, + {file = "orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3"}, ] [[package]] diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index 714007045..dfe6c7217 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph" -version = "0.2.17" +version = "0.2.18" description = "Building stateful, multi-actor applications with LLMs" authors = [] license = "MIT" diff --git a/libs/langgraph/tests/test_algo.py b/libs/langgraph/tests/test_algo.py index 2102261ae..203280488 100644 --- a/libs/langgraph/tests/test_algo.py +++ b/libs/langgraph/tests/test_algo.py @@ -13,13 +13,13 @@ def test_prepare_next_tasks() -> None: prepare_next_tasks( checkpoint, processes, channels, managed, config, 0, for_execution=False ) - == [] + == {} ) assert ( prepare_next_tasks( checkpoint, processes, channels, managed, config, 0, for_execution=True ) - == [] + == {} ) # TODO: add more tests diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index 3fd4e73b8..335ccc2a5 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -1451,8 +1451,8 @@ def test_pending_writes_resume( def reset(self): self.calls = 0 - one = AwhileMaker(0.2, {"value": 2}) - two = AwhileMaker(0.6, ConnectionError("I'm not good")) + one = AwhileMaker(0.1, {"value": 2}) + two = AwhileMaker(0.3, ConnectionError("I'm not good")) builder = StateGraph(State) builder.add_node("one", one) builder.add_node("two", two, retry=RetryPolicy(max_attempts=2)) diff --git a/libs/langgraph/tests/test_pregel_async.py b/libs/langgraph/tests/test_pregel_async.py index a9acaf478..7697610cf 100644 --- a/libs/langgraph/tests/test_pregel_async.py +++ b/libs/langgraph/tests/test_pregel_async.py @@ -1635,23 +1635,23 @@ async def test_pending_writes_resume( def reset(self): self.calls = 0 - one = AwhileMaker(0.2, {"value": 2}) - two = AwhileMaker(0.6, ValueError("I'm not good")) + one = AwhileMaker(0.1, {"value": 2}) + two = AwhileMaker(0.3, ConnectionError("I'm not good")) builder = StateGraph(State) builder.add_node("one", one) - builder.add_node("two", two) + builder.add_node("two", two, retry=RetryPolicy(max_attempts=2)) builder.add_edge(START, "one") builder.add_edge(START, "two") async with awith_checkpointer(checkpointer_name) as checkpointer: graph = builder.compile(checkpointer=checkpointer) thread1: RunnableConfig = {"configurable": {"thread_id": "1"}} - with pytest.raises(ValueError, match="I'm not good"): + with pytest.raises(ConnectionError, match="I'm not good"): await graph.ainvoke({"value": 1}, thread1) # both nodes should have been called once assert one.calls == 1 - assert two.calls == 1 + assert two.calls == 2 # latest checkpoint should be before nodes "one", "two" state = await graph.aget_state(thread1) @@ -1660,7 +1660,7 @@ async def test_pending_writes_resume( assert state.next == ("one", "two") assert state.tasks == ( PregelTask(AnyStr(), "one"), - PregelTask(AnyStr(), "two", 'ValueError("I\'m not good")'), + PregelTask(AnyStr(), "two", 'ConnectionError("I\'m not good")'), ) assert state.metadata == { "parents": {}, @@ -1675,7 +1675,7 @@ async def test_pending_writes_resume( expected_writes = [ (AnyStr(), "one", "one"), (AnyStr(), "value", 2), - (AnyStr(), ERROR, 'ValueError("I\'m not good")'), + (AnyStr(), ERROR, 'ConnectionError("I\'m not good")'), ] assert len(checkpoint.pending_writes) == 3 assert all(w in expected_writes for w in checkpoint.pending_writes) @@ -1686,18 +1686,14 @@ async def test_pending_writes_resume( error_write = next(w for w in checkpoint.pending_writes if w[1] == ERROR) assert error_write[0] != non_error_writes[0][0] - # TODO arguably this shouldn't even run the failed task again, - # and should require empty update_state (ie new checkpoint_id) - # in order to try again - # resume execution - with pytest.raises(ValueError, match="I'm not good"): + with pytest.raises(ConnectionError, match="I'm not good"): await graph.ainvoke(None, thread1) # node "one" succeeded previously, so shouldn't be called again assert one.calls == 1 # node "two" should have been called once again - assert two.calls == 2 + assert two.calls == 4 # confirm no new checkpoints saved state_two = await graph.aget_state(thread1) @@ -1818,7 +1814,7 @@ async def test_pending_writes_resume( pending_writes=UnsortedSequence( (AnyStr(), "one", "one"), (AnyStr(), "value", 2), - (AnyStr(), "__error__", 'ValueError("I\'m not good")'), + (AnyStr(), "__error__", 'ConnectionError("I\'m not good")'), (AnyStr(), "two", "two"), (AnyStr(), "value", 3), ),