From 61e47cb137ed8735e863a54d9ef13deabf30f633 Mon Sep 17 00:00:00 2001 From: vbarda Date: Wed, 27 Nov 2024 09:37:22 -0500 Subject: [PATCH 1/2] langgraph: relax graph validation to handle nodes without return typehints --- libs/langgraph/langgraph/graph/graph.py | 10 +++++--- libs/langgraph/tests/test_pregel.py | 33 +++++++++++++++---------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/libs/langgraph/langgraph/graph/graph.py b/libs/langgraph/langgraph/graph/graph.py index e91ac4a47..37fe90ebe 100644 --- a/libs/langgraph/langgraph/graph/graph.py +++ b/libs/langgraph/langgraph/graph/graph.py @@ -374,6 +374,11 @@ class Graph: if source not in self.nodes and source != START: raise ValueError(f"Found edge starting at unknown node '{source}'") + if START not in all_sources: + raise ValueError( + "Graph must have an entrypoint: add at least one edge from START to another node" + ) + # assemble targets all_targets = {end for _, end in self._all_edges} for start, branches in self.branches.items(): @@ -392,13 +397,10 @@ class Graph: for node in self.nodes: if node != start and node != branch.then: all_targets.add(node) + for name, spec in self.nodes.items(): if spec.ends: all_targets.update(spec.ends) - # validate targets - for node in self.nodes: - if node not in all_targets: - raise ValueError(f"Node `{node}` is not reachable") for target in all_targets: if target not in self.nodes and target != END: raise ValueError(f"Found edge ending at unknown node `{target}`") diff --git a/libs/langgraph/tests/test_pregel.py b/libs/langgraph/tests/test_pregel.py index fda881b15..f5a5cd673 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -164,7 +164,7 @@ def test_graph_validation() -> None: workflow = Graph() workflow.add_node("agent", logic) workflow.set_finish_point("agent") - with pytest.raises(ValueError, match="not reachable"): + with pytest.raises(ValueError, match="must have an entrypoint"): workflow.compile() workflow = Graph() @@ -211,18 +211,6 @@ def test_graph_validation() -> None: with pytest.raises(ValueError, match="unknown"): # extra is not defined workflow.compile() - workflow = Graph() - workflow.add_node("agent", logic) - workflow.add_node("tools", logic) - workflow.add_node("extra", logic) - workflow.set_entry_point("agent") - workflow.add_conditional_edges("agent", logic, {"continue": "tools", "exit": END}) - workflow.add_edge("tools", "agent") - with pytest.raises( - ValueError, match="Node `extra` is not reachable" - ): # extra is not reachable - workflow.compile() - workflow = Graph() workflow.add_node("agent", logic) workflow.add_node("tools", logic) @@ -280,6 +268,25 @@ def test_graph_validation() -> None: graph.invoke({"hello": "there"}) +def test_graph_validation_with_command() -> None: + class State(TypedDict): + foo: str + bar: str + + def node_a(state: State): + return GraphCommand(goto="b", update={"foo": "bar"}) + + def node_b(state: State): + return GraphCommand(goto=END, update={"bar": "baz"}) + + builder = StateGraph(State) + builder.add_node("a", node_a) + builder.add_node("b", node_b) + builder.add_edge(START, "a") + graph = builder.compile() + assert graph.invoke({"foo": ""}) == {"foo": "bar", "bar": "baz"} + + def test_checkpoint_errors() -> None: class FaultyGetCheckpointer(MemorySaver): def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]: From f416480e9d81eaf3018eda2e80facd87a3251252 Mon Sep 17 00:00:00 2001 From: vbarda Date: Wed, 27 Nov 2024 10:01:24 -0500 Subject: [PATCH 2/2] nit --- libs/langgraph/langgraph/graph/graph.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/langgraph/langgraph/graph/graph.py b/libs/langgraph/langgraph/graph/graph.py index 37fe90ebe..50a4dadfb 100644 --- a/libs/langgraph/langgraph/graph/graph.py +++ b/libs/langgraph/langgraph/graph/graph.py @@ -397,7 +397,6 @@ class Graph: for node in self.nodes: if node != start and node != branch.then: all_targets.add(node) - for name, spec in self.nodes.items(): if spec.ends: all_targets.update(spec.ends)