diff --git a/libs/langgraph/langgraph/graph/graph.py b/libs/langgraph/langgraph/graph/graph.py index fa28243fb..07a57d596 100644 --- a/libs/langgraph/langgraph/graph/graph.py +++ b/libs/langgraph/langgraph/graph/graph.py @@ -182,7 +182,7 @@ class Graph: # validate the condition if name in self.branches[source]: raise ValueError( - f"Branch with name `{path.name}` already exists for node " f"`{source}`" + f"Branch with name `{path.name}` already exists for node `{source}`" ) # save it self.branches[source][name] = Branch.from_path(path, path_map, then, False) diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index fed12fd8b..0eba0f362 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -527,7 +527,7 @@ class StateGraph(Graph): # validate the condition if name in self.branches[source]: raise ValueError( - f"Branch with name `{path.name}` already exists for node " f"`{source}`" + f"Branch with name `{path.name}` already exists for node `{source}`" ) # save it self.branches[source][name] = Branch.from_path(path, path_map, then, True) diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index 8c7cd2e28..757d66d6f 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -108,6 +108,7 @@ from langgraph.store.base import BaseStore from langgraph.types import ( All, Checkpointer, + Interrupt, LoopProtocol, StateSnapshot, StateUpdate, @@ -2749,17 +2750,15 @@ class Pregel(PregelProtocol): **kwargs, ): if stream_mode == "values": - if isinstance(chunk, dict) and (ints := chunk.get(INTERRUPT)) is not None: - interrupts.extend(ints) + if isinstance(chunk, dict): + if (ints := chunk.get(INTERRUPT)) is not None: + interrupts.extend(ints) latest = chunk else: chunks.append(chunk) + if stream_mode == "values": - if len(interrupts) > 0: - return { - INTERRUPT: interrupts - } - return latest + return {INTERRUPT: interrupts} if interrupts else latest else: return chunks @@ -2794,10 +2793,11 @@ class Pregel(PregelProtocol): """ output_keys = output_keys if output_keys is not None else self.output_channels - if stream_mode == "values": - latest: Union[dict[str, Any], Any] = None - else: - chunks = [] + + latest: Union[dict[str, Any], Any] = None + chunks: list[Union[dict[str, Any], Any]] = [] + interrupts: list[Interrupt] = [] + async for chunk in self.astream( input, config, @@ -2810,11 +2810,15 @@ class Pregel(PregelProtocol): **kwargs, ): if stream_mode == "values": + if isinstance(chunk, dict): + if (ints := chunk.get(INTERRUPT)) is not None: + interrupts.extend(ints) latest = chunk else: chunks.append(chunk) + if stream_mode == "values": - return latest + return {INTERRUPT: interrupts} if interrupts else latest else: return chunks diff --git a/libs/langgraph/langgraph/pregel/loop.py b/libs/langgraph/langgraph/pregel/loop.py index 01471a1c0..774941926 100644 --- a/libs/langgraph/langgraph/pregel/loop.py +++ b/libs/langgraph/langgraph/pregel/loop.py @@ -916,9 +916,7 @@ class PregelLoop(LoopProtocol): v for w in writes if w[0] == INTERRUPT - for v in ( - w[1] if isinstance(w[1], Sequence) else (w[1],) - ) + for v in (w[1] if isinstance(w[1], Sequence) else (w[1],)) ) } ] diff --git a/libs/langgraph/langgraph/pregel/read.py b/libs/langgraph/langgraph/pregel/read.py index e57620ec7..606a3892e 100644 --- a/libs/langgraph/langgraph/pregel/read.py +++ b/libs/langgraph/langgraph/pregel/read.py @@ -245,12 +245,12 @@ class PregelNode(Runnable): ) def join(self, channels: Sequence[str]) -> PregelNode: - assert isinstance(channels, list) or isinstance( - channels, tuple - ), "channels must be a list or tuple" - assert isinstance( - self.channels, dict - ), "all channels must be named when using .join()" + assert isinstance(channels, list) or isinstance(channels, tuple), ( + "channels must be a list or tuple" + ) + assert isinstance(self.channels, dict), ( + "all channels must be named when using .join()" + ) return self.copy( update=dict( channels={ diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index de3e86ce6..e7f68276b 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -1,5 +1,7 @@ import dataclasses +import hashlib import sys +import uuid from collections import deque from typing import ( TYPE_CHECKING, @@ -24,8 +26,6 @@ from typing_extensions import Self from langgraph.checkpoint.base import BaseCheckpointSaver, CheckpointMetadata from langgraph.utils.fields import get_update_as_tuples -import hashlib -import uuid if TYPE_CHECKING: from langgraph.pregel.protocol import PregelProtocol @@ -145,11 +145,12 @@ class Interrupt: ns: Optional[Sequence[str]] = None when: Literal["during"] = dataclasses.field(default="during", repr=False) - @property def interrupt_id(self) -> str: """Generate a unique ID for the interrupt based on its namespace.""" - identifier = uuid.uuid4().bytes if self.ns is None else ''.join(self.ns).encode() + identifier = ( + uuid.uuid4().bytes if self.ns is None else "".join(self.ns).encode() + ) return hashlib.sha256(identifier).hexdigest() @@ -487,12 +488,12 @@ def interrupt(value: Any) -> Any: GraphInterrupt: On the first invocation within the node, halts execution and surfaces the provided value to the client. """ from langgraph.constants import ( + CONF, CONFIG_KEY_CHECKPOINT_NS, CONFIG_KEY_SCRATCHPAD, CONFIG_KEY_SEND, NS_SEP, RESUME, - CONF, ) from langgraph.errors import GraphInterrupt from langgraph.utils.config import get_config diff --git a/libs/langgraph/tests/test_checkpoint_migration.py b/libs/langgraph/tests/test_checkpoint_migration.py index 67e2efc1e..413e14138 100644 --- a/libs/langgraph/tests/test_checkpoint_migration.py +++ b/libs/langgraph/tests/test_checkpoint_migration.py @@ -1573,9 +1573,9 @@ def test_migrate_checkpoints(source: str, target: str) -> None: migrated["versions_seen"][c][v].split(".")[0] ) # check that the migrated checkpoint matches the target checkpoint - assert ( - migrated == target_checkpoint.checkpoint - ), "Checkpoint mismatch at index {}".format(idx) + assert migrated == target_checkpoint.checkpoint, ( + "Checkpoint mismatch at index {}".format(idx) + ) @NEEDS_CONTEXTVARS diff --git a/libs/langgraph/tests/test_large_cases.py b/libs/langgraph/tests/test_large_cases.py index 7bba55ff5..a876a5864 100644 --- a/libs/langgraph/tests/test_large_cases.py +++ b/libs/langgraph/tests/test_large_cases.py @@ -2829,9 +2829,9 @@ def test_state_graph_packets( # Define decision-making logic def should_continue(data: dict) -> str: assert isinstance(data["session"], httpx.Client) - assert ( - data["something_extra"] == "hi there" - ), "nodes can pass extra data to their cond edges, which isn't saved in state" + assert data["something_extra"] == "hi there", ( + "nodes can pass extra data to their cond edges, which isn't saved in state" + ) # Logic to decide whether to continue in the loop or exit if tool_calls := data["messages"][-1].tool_calls: return [Send("tools", tool_call) for tool_call in tool_calls] diff --git a/libs/langgraph/tests/test_large_cases_async.py b/libs/langgraph/tests/test_large_cases_async.py index 4f5c688aa..0ef1228c0 100644 --- a/libs/langgraph/tests/test_large_cases_async.py +++ b/libs/langgraph/tests/test_large_cases_async.py @@ -3805,7 +3805,7 @@ async def test_in_one_fan_out_out_one_graph_state() -> None: docs: Annotated[list[str], operator.add] async def rewrite_query(data: State) -> State: - return {"query": f'query: {data["query"]}'} + return {"query": f"query: {data['query']}"} async def retriever_one(data: State) -> State: await asyncio.sleep(0.1) diff --git a/libs/langgraph/tests/test_retry.py b/libs/langgraph/tests/test_retry.py index 6ef10d4c7..940d5db18 100644 --- a/libs/langgraph/tests/test_retry.py +++ b/libs/langgraph/tests/test_retry.py @@ -226,9 +226,10 @@ def test_graph_with_jitter_retry_policy(): ) # Test graph execution with mocked random and sleep - with patch("random.uniform", return_value=0.05) as mock_random, patch( - "time.sleep" - ) as mock_sleep: + with ( + patch("random.uniform", return_value=0.05) as mock_random, + patch("time.sleep") as mock_sleep, + ): result = graph.invoke({"foo": ""}) # Verify retry behavior @@ -334,8 +335,9 @@ def test_graph_with_max_attempts_exceeded(): ) # Test graph execution - with patch("time.sleep") as mock_sleep, pytest.raises( - ValueError, match="Always fails" + with ( + patch("time.sleep") as mock_sleep, + pytest.raises(ValueError, match="Always fails"), ): graph.invoke({"foo": ""})