mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-19 14:15:44 +02:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
febf241b30 | ||
|
|
7ab5788f25 | ||
|
|
b0a1029d55 | ||
|
|
c2ef3f3fd3 | ||
|
|
d455bd841d | ||
|
|
69a09adef6 | ||
|
|
35aa98b110 | ||
|
|
0f83d9fafe | ||
|
|
52d66df92c | ||
|
|
4ec92f9fb1 | ||
|
|
2b72953064 | ||
|
|
232014e8ef |
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 LangChain, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -97,7 +97,7 @@ class PostgresSaver(BasePostgresSaver):
|
||||
strict=False,
|
||||
):
|
||||
cur.execute(migration)
|
||||
cur.execute(f"INSERT INTO checkpoint_migrations (v) VALUES ({v})")
|
||||
cur.execute("INSERT INTO checkpoint_migrations (v) VALUES (%s)", (v,))
|
||||
if self.pipe:
|
||||
self.pipe.sync()
|
||||
|
||||
|
||||
@@ -102,7 +102,9 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
strict=False,
|
||||
):
|
||||
await cur.execute(migration)
|
||||
await cur.execute(f"INSERT INTO checkpoint_migrations (v) VALUES ({v})")
|
||||
await cur.execute(
|
||||
"INSERT INTO checkpoint_migrations (v) VALUES (%s)", (v,)
|
||||
)
|
||||
if self.pipe:
|
||||
await self.pipe.sync()
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@ class ShallowPostgresSaver(BasePostgresSaver):
|
||||
strict=False,
|
||||
):
|
||||
cur.execute(migration)
|
||||
cur.execute(f"INSERT INTO checkpoint_migrations (v) VALUES ({v})")
|
||||
cur.execute("INSERT INTO checkpoint_migrations (v) VALUES (%s)", (v,))
|
||||
if self.pipe:
|
||||
self.pipe.sync()
|
||||
|
||||
@@ -614,7 +614,9 @@ class AsyncShallowPostgresSaver(BasePostgresSaver):
|
||||
strict=False,
|
||||
):
|
||||
await cur.execute(migration)
|
||||
await cur.execute(f"INSERT INTO checkpoint_migrations (v) VALUES ({v})")
|
||||
await cur.execute(
|
||||
"INSERT INTO checkpoint_migrations (v) VALUES (%s)", (v,)
|
||||
)
|
||||
if self.pipe:
|
||||
await self.pipe.sync()
|
||||
|
||||
|
||||
@@ -19,7 +19,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://www.github.com/langchain-ai/langgraph"
|
||||
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-postgres"
|
||||
Twitter = "https://x.com/LangChainAI"
|
||||
Slack = "https://www.langchain.com/join-community"
|
||||
Reddit = "https://www.reddit.com/r/LangChain/"
|
||||
|
||||
[dependency-groups]
|
||||
test = [
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 LangChain, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -18,7 +18,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://www.github.com/langchain-ai/langgraph"
|
||||
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint-sqlite"
|
||||
Twitter = "https://x.com/LangChainAI"
|
||||
Slack = "https://www.langchain.com/join-community"
|
||||
Reddit = "https://www.reddit.com/r/LangChain/"
|
||||
|
||||
[dependency-groups]
|
||||
test = [
|
||||
|
||||
@@ -17,7 +17,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://www.github.com/langchain-ai/langgraph"
|
||||
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/checkpoint"
|
||||
Twitter = "https://x.com/LangChainAI"
|
||||
Slack = "https://www.langchain.com/join-community"
|
||||
Reddit = "https://www.reddit.com/r/LangChain/"
|
||||
|
||||
[dependency-groups]
|
||||
test = [
|
||||
|
||||
@@ -25,7 +25,10 @@ inmem = [
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://www.github.com/langchain-ai/langgraph"
|
||||
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/cli"
|
||||
Twitter = "https://x.com/LangChainAI"
|
||||
Slack = "https://www.langchain.com/join-community"
|
||||
Reddit = "https://www.reddit.com/r/LangChain/"
|
||||
|
||||
[project.scripts]
|
||||
langgraph = "langgraph_cli.cli:cli"
|
||||
|
||||
@@ -30,6 +30,7 @@ __all__ = (
|
||||
"add_messages",
|
||||
"MessagesState",
|
||||
"MessageGraph",
|
||||
"REMOVE_ALL_MESSAGES",
|
||||
)
|
||||
|
||||
Messages = list[MessageLikeRepresentation] | MessageLikeRepresentation
|
||||
|
||||
@@ -289,7 +289,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
|
||||
destinations: dict[str, str] | tuple[str, ...] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> Self:
|
||||
"""Add a new node to the state graph, input schema is inferred as the state schema.
|
||||
"""Add a new node to the `StateGraph`, input schema is inferred as the state schema.
|
||||
Will take the name of the function/runnable as the node name.
|
||||
"""
|
||||
...
|
||||
@@ -307,7 +307,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
|
||||
destinations: dict[str, str] | tuple[str, ...] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> Self:
|
||||
"""Add a new node to the state graph, input schema is specified.
|
||||
"""Add a new node to the `StateGraph`, input schema is specified.
|
||||
Will take the name of the function/runnable as the node name.
|
||||
"""
|
||||
...
|
||||
@@ -326,7 +326,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
|
||||
destinations: dict[str, str] | tuple[str, ...] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> Self:
|
||||
"""Add a new node to the state graph, input schema is inferred as the state schema."""
|
||||
"""Add a new node to the `StateGraph`, input schema is inferred as the state schema."""
|
||||
...
|
||||
|
||||
@overload
|
||||
@@ -343,7 +343,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
|
||||
destinations: dict[str, str] | tuple[str, ...] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> Self:
|
||||
"""Add a new node to the state graph, input schema is specified."""
|
||||
"""Add a new node to the `StateGraph`, input schema is specified."""
|
||||
...
|
||||
|
||||
def add_node(
|
||||
@@ -359,7 +359,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
|
||||
destinations: dict[str, str] | tuple[str, ...] | None = None,
|
||||
**kwargs: Unpack[DeprecatedKwargs],
|
||||
) -> Self:
|
||||
"""Add a new node to the state graph.
|
||||
"""Add a new node to the `StateGraph`.
|
||||
|
||||
Args:
|
||||
node: The function or runnable this node will run.
|
||||
@@ -416,7 +416,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
|
||||
```
|
||||
|
||||
Returns:
|
||||
Self: The instance of the state graph, allowing for method chaining.
|
||||
Self: The instance of the `StateGraph`, allowing for method chaining.
|
||||
"""
|
||||
if (retry := kwargs.get("retry", MISSING)) is not MISSING:
|
||||
warnings.warn(
|
||||
@@ -571,7 +571,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
|
||||
ValueError: If the start key is `'END'` or if the start key or end key is not present in the graph.
|
||||
|
||||
Returns:
|
||||
Self: The instance of the state graph, allowing for method chaining.
|
||||
Self: The instance of the `StateGraph`, allowing for method chaining.
|
||||
"""
|
||||
if self.compiled:
|
||||
logger.warning(
|
||||
@@ -676,7 +676,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
|
||||
ValueError: If the sequence contains duplicate node names.
|
||||
|
||||
Returns:
|
||||
Self: The instance of the state graph, allowing for method chaining.
|
||||
Self: The instance of the `StateGraph`, allowing for method chaining.
|
||||
"""
|
||||
if len(nodes) < 1:
|
||||
raise ValueError("Sequence requires at least one node.")
|
||||
@@ -809,7 +809,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
|
||||
debug: bool = False,
|
||||
name: str | None = None,
|
||||
) -> CompiledStateGraph[StateT, ContextT, InputT, OutputT]:
|
||||
"""Compiles the state graph into a `CompiledStateGraph` object.
|
||||
"""Compiles the `StateGraph` into a `CompiledStateGraph` object.
|
||||
|
||||
The compiled graph implements the `Runnable` interface and can be invoked,
|
||||
streamed, batched, and run asynchronously.
|
||||
@@ -826,7 +826,7 @@ class StateGraph(Generic[StateT, ContextT, InputT, OutputT]):
|
||||
name: The name to use for the compiled graph.
|
||||
|
||||
Returns:
|
||||
CompiledStateGraph: The compiled state graph.
|
||||
CompiledStateGraph: The compiled `StateGraph`.
|
||||
"""
|
||||
# assign default values
|
||||
interrupt_before = interrupt_before or []
|
||||
|
||||
@@ -459,9 +459,6 @@ class PregelLoop:
|
||||
def tick(self) -> bool:
|
||||
"""Execute a single iteration of the Pregel loop.
|
||||
|
||||
Args:
|
||||
input_keys: The key(s) to read input from.
|
||||
|
||||
Returns:
|
||||
True if more iterations are needed.
|
||||
"""
|
||||
|
||||
@@ -9,7 +9,7 @@ from collections.abc import Awaitable, Callable, Sequence
|
||||
from dataclasses import replace
|
||||
from typing import Any
|
||||
|
||||
from langgraph._internal._config import patch_configurable
|
||||
from langgraph._internal._config import patch_configurable, recast_checkpoint_ns
|
||||
from langgraph._internal._constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
@@ -43,16 +43,16 @@ def run_with_retry(
|
||||
except ParentCommand as exc:
|
||||
ns: str = config[CONF][CONFIG_KEY_CHECKPOINT_NS]
|
||||
cmd = exc.args[0]
|
||||
if cmd.graph in (ns, task.name):
|
||||
if cmd.graph in (recast_checkpoint_ns(ns), task.name):
|
||||
# this command is for the current graph, handle it
|
||||
for w in task.writers:
|
||||
w.invoke(cmd, config)
|
||||
break
|
||||
elif cmd.graph == Command.PARENT:
|
||||
# this command is for the parent graph, assign it to the parent
|
||||
parts = ns.split(NS_SEP)
|
||||
if parts[-1].isdigit():
|
||||
parts.pop()
|
||||
# normalize namespace by removing task IDs
|
||||
recast_ns = recast_checkpoint_ns(ns)
|
||||
parts = recast_ns.split(NS_SEP)
|
||||
parent_ns = NS_SEP.join(parts[:-1])
|
||||
exc.args = (replace(cmd, graph=parent_ns),)
|
||||
# bubble up
|
||||
@@ -138,16 +138,16 @@ async def arun_with_retry(
|
||||
except ParentCommand as exc:
|
||||
ns: str = config[CONF][CONFIG_KEY_CHECKPOINT_NS]
|
||||
cmd = exc.args[0]
|
||||
if cmd.graph in (ns, task.name):
|
||||
if cmd.graph in (recast_checkpoint_ns(ns), task.name):
|
||||
# this command is for the current graph, handle it
|
||||
for w in task.writers:
|
||||
w.invoke(cmd, config)
|
||||
break
|
||||
elif cmd.graph == Command.PARENT:
|
||||
# this command is for the parent graph, assign it to the parent
|
||||
parts = ns.split(NS_SEP)
|
||||
if parts[-1].isdigit():
|
||||
parts.pop()
|
||||
# normalize namespace by removing task IDs
|
||||
recast_ns = recast_checkpoint_ns(ns)
|
||||
parts = recast_ns.split(NS_SEP)
|
||||
parent_ns = NS_SEP.join(parts[:-1])
|
||||
exc.args = (replace(cmd, graph=parent_ns),)
|
||||
# bubble up
|
||||
|
||||
@@ -2354,7 +2354,7 @@ class Pregel(
|
||||
validate_keys(output_keys, self.channels)
|
||||
interrupt_before = interrupt_before or self.interrupt_before_nodes
|
||||
interrupt_after = interrupt_after or self.interrupt_after_nodes
|
||||
if not isinstance(stream_mode, list):
|
||||
if isinstance(stream_mode, str):
|
||||
stream_modes = {stream_mode}
|
||||
else:
|
||||
stream_modes = set(stream_mode)
|
||||
@@ -2435,7 +2435,7 @@ class Pregel(
|
||||
You can pass a list as the `stream_mode` parameter to stream multiple modes at once.
|
||||
The streamed outputs will be tuples of `(mode, data)`.
|
||||
|
||||
See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details.
|
||||
See [LangGraph streaming guide](https://docs.langchain.com/oss/python/langgraph/streaming) for more details.
|
||||
print_mode: Accepts the same values as `stream_mode`, but only prints the output to the console, for debugging purposes. Does not affect the output of the graph in any way.
|
||||
output_keys: The keys to stream, defaults to all non-context channels.
|
||||
interrupt_before: Nodes to interrupt before, defaults to all nodes in the graph.
|
||||
@@ -2452,7 +2452,7 @@ class Pregel(
|
||||
where `namespace` is a tuple with the path to the node where a subgraph is invoked,
|
||||
e.g. `("parent_node:<task_id>", "child_node:<task_id>")`.
|
||||
|
||||
See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details.
|
||||
See [LangGraph streaming guide](https://docs.langchain.com/oss/python/langgraph/streaming) for more details.
|
||||
|
||||
Yields:
|
||||
The output of each step in the graph. The output shape depends on the `stream_mode`.
|
||||
@@ -2702,7 +2702,7 @@ class Pregel(
|
||||
You can pass a list as the `stream_mode` parameter to stream multiple modes at once.
|
||||
The streamed outputs will be tuples of `(mode, data)`.
|
||||
|
||||
See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details.
|
||||
See [LangGraph streaming guide](https://docs.langchain.com/oss/python/langgraph/streaming) for more details.
|
||||
print_mode: Accepts the same values as `stream_mode`, but only prints the output to the console, for debugging purposes. Does not affect the output of the graph in any way.
|
||||
output_keys: The keys to stream, defaults to all non-context channels.
|
||||
interrupt_before: Nodes to interrupt before, defaults to all nodes in the graph.
|
||||
@@ -2719,7 +2719,7 @@ class Pregel(
|
||||
where `namespace` is a tuple with the path to the node where a subgraph is invoked,
|
||||
e.g. `("parent_node:<task_id>", "child_node:<task_id>")`.
|
||||
|
||||
See [LangGraph streaming guide](https://langchain-ai.github.io/langgraph/how-tos/streaming/) for more details.
|
||||
See [LangGraph streaming guide](https://docs.langchain.com/oss/python/langgraph/streaming) for more details.
|
||||
|
||||
Yields:
|
||||
The output of each step in the graph. The output shape depends on the `stream_mode`.
|
||||
|
||||
@@ -111,7 +111,7 @@ class Runtime(Generic[ContextT]):
|
||||
stream_writer=other.stream_writer
|
||||
if other.stream_writer is not _no_op_stream_writer
|
||||
else self.stream_writer,
|
||||
previous=other.previous or self.previous,
|
||||
previous=self.previous if other.previous is None else other.previous,
|
||||
)
|
||||
|
||||
def override(
|
||||
|
||||
@@ -31,13 +31,13 @@ ContextT_contra = TypeVar(
|
||||
)
|
||||
|
||||
InputT = TypeVar("InputT", bound=StateLike, default=StateT)
|
||||
"""Type variable used to represent the input to a state graph.
|
||||
"""Type variable used to represent the input to a `StateGraph`.
|
||||
|
||||
Defaults to `StateT`.
|
||||
"""
|
||||
|
||||
OutputT = TypeVar("OutputT", bound=StateLike, default=StateT)
|
||||
"""Type variable used to represent the output of a state graph.
|
||||
"""Type variable used to represent the output of a `StateGraph`.
|
||||
|
||||
Defaults to `StateT`.
|
||||
"""
|
||||
|
||||
@@ -32,8 +32,15 @@ dependencies = [
|
||||
"pydantic>=2.7.4",
|
||||
]
|
||||
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://www.github.com/langchain-ai/langgraph"
|
||||
Homepage = "https://docs.langchain.com/oss/python/langgraph/overview"
|
||||
Documentation = "https://reference.langchain.com/python/langgraph/"
|
||||
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/langgraph"
|
||||
Changelog = "https://github.com/langchain-ai/langgraph/releases"
|
||||
Twitter = "https://x.com/LangChainAI"
|
||||
Slack = "https://www.langchain.com/join-community"
|
||||
Reddit = "https://www.reddit.com/r/LangChain/"
|
||||
|
||||
[dependency-groups]
|
||||
test = [
|
||||
|
||||
@@ -7911,6 +7911,78 @@ def test_parent_command_goto(
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("subgraph_persist", [True, False])
|
||||
def test_parent_command_goto_deeply_nested(
|
||||
sync_checkpointer: BaseCheckpointSaver, subgraph_persist: bool
|
||||
) -> None:
|
||||
"""Test Command.PARENT with goto in deeply nested graphs (3+ levels).
|
||||
|
||||
This tests the fix for issue #6409 where Command.PARENT with goto
|
||||
would fail in graphs with 3 or more levels of nesting due to
|
||||
namespace comparison issues.
|
||||
"""
|
||||
class State(TypedDict):
|
||||
messages: Annotated[list[str], operator.add]
|
||||
|
||||
# Level 3 (deepest): sub_sub_graph
|
||||
def sub_sub_node(state):
|
||||
"""Returns Command.PARENT to jump to grandparent's node."""
|
||||
return Command(
|
||||
graph=Command.PARENT,
|
||||
goto="sub_node_3",
|
||||
update={"messages": ["sub_sub_node"]},
|
||||
)
|
||||
|
||||
sub_sub_builder = StateGraph(State)
|
||||
sub_sub_builder.add_node("sub_sub_node", sub_sub_node)
|
||||
sub_sub_builder.add_edge(START, "sub_sub_node")
|
||||
sub_sub_graph = sub_sub_builder.compile(checkpointer=subgraph_persist)
|
||||
|
||||
# Level 2 (middle): sub_graph
|
||||
def sub_node_1(state):
|
||||
return {"messages": ["sub_node_1"]}
|
||||
|
||||
def sub_node_3(state):
|
||||
"""Target node for Command.PARENT goto."""
|
||||
return {"messages": ["sub_node_3"]}
|
||||
|
||||
sub_builder = StateGraph(State)
|
||||
sub_builder.add_node("sub_node_1", sub_node_1)
|
||||
sub_builder.add_node("sub_node_2", sub_sub_graph)
|
||||
sub_builder.add_node("sub_node_3", sub_node_3)
|
||||
sub_builder.add_edge(START, "sub_node_1")
|
||||
sub_builder.add_edge("sub_node_1", "sub_node_2")
|
||||
sub_graph = sub_builder.compile(checkpointer=subgraph_persist)
|
||||
|
||||
# Level 1 (top): main_graph
|
||||
def main_node_1(state):
|
||||
return {"messages": ["main_node_1"]}
|
||||
|
||||
main_builder = StateGraph(State)
|
||||
main_builder.add_node("main_node_1", main_node_1)
|
||||
main_builder.add_node("main_node_2", sub_graph)
|
||||
main_builder.add_edge(START, "main_node_1")
|
||||
main_builder.add_edge("main_node_1", "main_node_2")
|
||||
main_graph = main_builder.compile(sync_checkpointer, name="main")
|
||||
|
||||
config = {"configurable": {"thread_id": 1}}
|
||||
|
||||
result = main_graph.invoke(input={"messages": ["start"]}, config=config)
|
||||
|
||||
# Verify the execution order includes all expected nodes.
|
||||
# Note: When subgraphs have persistent checkpointers, parent state
|
||||
# is passed down, which may cause message duplication at subgraph boundaries.
|
||||
# The key assertion is that:
|
||||
# 1. All expected messages appear in order
|
||||
# 2. sub_node_3 executed (proving Command.PARENT goto worked)
|
||||
expected_messages = ["main_node_1", "sub_node_1", "sub_sub_node", "sub_node_3"]
|
||||
assert all(msg in result["messages"] for msg in expected_messages), (
|
||||
f"Expected all messages {expected_messages} to be in result {result['messages']}"
|
||||
)
|
||||
# Verify sub_node_3 executed last (the Command.PARENT goto target)
|
||||
assert result["messages"][-1] == "sub_node_3"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("with_timeout", [True, False])
|
||||
def test_timeout_with_parent_command(
|
||||
sync_checkpointer: BaseCheckpointSaver, with_timeout: bool
|
||||
|
||||
@@ -29,7 +29,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://www.github.com/langchain-ai/langgraph"
|
||||
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/prebuilt"
|
||||
Twitter = "https://x.com/LangChainAI"
|
||||
Slack = "https://www.langchain.com/join-community"
|
||||
Reddit = "https://www.reddit.com/r/LangChain/"
|
||||
|
||||
[dependency-groups]
|
||||
test = [
|
||||
|
||||
@@ -259,7 +259,7 @@ class Auth:
|
||||
"""
|
||||
if self._authenticate_handler is not None:
|
||||
raise ValueError(
|
||||
"Authentication handler already set as {self._authenticate_handler}."
|
||||
f"Authentication handler already set as {self._authenticate_handler}."
|
||||
)
|
||||
self._authenticate_handler = fn
|
||||
return fn
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""The LangGraph client implementations connect to the LangGraph API.
|
||||
|
||||
This module provides both asynchronous ([get_client(url="http://localhost:2024"))](#get_client) or [LangGraphClient](#LangGraphClient))
|
||||
and synchronous ([get_sync_client(url="http://localhost:2024"))](#get_sync_client) or [SyncLanggraphClient](#SyncLanggraphClient))
|
||||
clients to interacting with the LangGraph API's core resources such as
|
||||
Assistants, Threads, Runs, and Cron jobs, as well as its persistent
|
||||
document Store.
|
||||
""" # noqa: E501
|
||||
This module provides both asynchronous (`get_client(url="http://localhost:2024")` or
|
||||
`LangGraphClient`) and synchronous (`get_sync_client(url="http://localhost:2024")` or
|
||||
`SyncLanggraphClient`) clients to interacting with the LangGraph API's core resources
|
||||
such as Assistants, Threads, Runs, and Cron jobs, as well as its persistent document
|
||||
Store.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
@@ -20,7 +20,10 @@ dependencies = [
|
||||
path = "langgraph_sdk/__init__.py"
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://www.github.com/langchain-ai/langgraph"
|
||||
Source = "https://github.com/langchain-ai/langgraph/tree/main/libs/sdk-py"
|
||||
Twitter = "https://x.com/LangChainAI"
|
||||
Slack = "https://www.langchain.com/join-community"
|
||||
Reddit = "https://www.reddit.com/r/LangChain/"
|
||||
|
||||
[dependency-groups]
|
||||
test = [
|
||||
|
||||
Reference in New Issue
Block a user