diff --git a/libs/langgraph/langgraph/graph/graph.py b/libs/langgraph/langgraph/graph/graph.py index e91ac4a47..50a4dadfb 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(): @@ -395,10 +400,6 @@ class Graph: 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 6ff70276f..6d1caa342 100644 --- a/libs/langgraph/tests/test_pregel.py +++ b/libs/langgraph/tests/test_pregel.py @@ -160,7 +160,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() @@ -207,18 +207,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) @@ -276,6 +264,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]: