mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-12 20:57:52 +02:00
no-finish-node
This commit is contained in:
@@ -73,3 +73,52 @@ func TestInputAndStatePrimitivesCompatible(t *testing.T) {
|
||||
t.Fatalf("unexpected logs: %#v", result.Logs)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *primitiveWorkflow) startNoFinishNode(ctx *ag.Context, _ any, state primitiveState) (ag.Command, error) {
|
||||
state.Logs = append(state.Logs, "start")
|
||||
return ag.Command{
|
||||
Update: state,
|
||||
Goto: []ag.Send{
|
||||
{Node: w.middleNoFinishNode, NodeInput: "from_start"},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *primitiveWorkflow) middleNoFinishNode(ctx *ag.Context, input string, state primitiveState) (ag.Command, error) {
|
||||
state.Logs = append(state.Logs, "middle:"+input)
|
||||
state.Count += 1
|
||||
state.Done = "stopped"
|
||||
// No goto and no finish node configured: run should end automatically.
|
||||
return ag.Command{Update: state}, nil
|
||||
}
|
||||
|
||||
func TestRunEndsWithoutFinishNode(t *testing.T) {
|
||||
workflow := &primitiveWorkflow{}
|
||||
graph := ag.NewAdvancedStateGraph[primitiveState]()
|
||||
|
||||
graph.AddEntryNode(workflow.startNoFinishNode)
|
||||
graph.AddNode(workflow.middleNoFinishNode)
|
||||
|
||||
handler, err := graph.Compile().Start(nil, primitiveState{
|
||||
Count: 7,
|
||||
Logs: []string{},
|
||||
Done: "",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("start failed: %v", err)
|
||||
}
|
||||
|
||||
result, err := handler.WaitForResult()
|
||||
if err != nil {
|
||||
t.Fatalf("result failed: %v", err)
|
||||
}
|
||||
if result.Done != "stopped" {
|
||||
t.Fatalf("unexpected done: %v", result.Done)
|
||||
}
|
||||
if result.Count != 8 {
|
||||
t.Fatalf("unexpected count: %v", result.Count)
|
||||
}
|
||||
if len(result.Logs) != 2 || result.Logs[0] != "start" || result.Logs[1] != "middle:from_start" {
|
||||
t.Fatalf("unexpected logs: %#v", result.Logs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,11 +130,9 @@ class AdvancedStateGraph(Generic[StateT]):
|
||||
def compile(self) -> CompiledGraphEngine[StateT]:
|
||||
if self._entry_point is None:
|
||||
raise ValueError("Entry point is not set")
|
||||
if self._finish_point is None:
|
||||
raise ValueError("Finish point is not set")
|
||||
if self._entry_point not in self._nodes:
|
||||
raise ValueError(f"Entry point node `{self._entry_point}` does not exist")
|
||||
if self._finish_point not in self._nodes:
|
||||
if self._finish_point is not None and self._finish_point not in self._nodes:
|
||||
raise ValueError(f"Finish point node `{self._finish_point}` does not exist")
|
||||
return CompiledGraphEngine(
|
||||
nodes=dict(self._nodes),
|
||||
@@ -153,7 +151,7 @@ class CompiledGraphEngine(Generic[StateT]):
|
||||
nodes: dict[str, Callable[..., Any]],
|
||||
async_channels: dict[str, _ChannelSpec],
|
||||
entry_point: str,
|
||||
finish_point: str,
|
||||
finish_point: str | None,
|
||||
) -> None:
|
||||
self._nodes = nodes
|
||||
self._async_channels = async_channels
|
||||
@@ -220,7 +218,7 @@ class _GraphEngineRun:
|
||||
nodes: dict[str, Callable[..., Any]],
|
||||
async_channel_specs: dict[str, _ChannelSpec],
|
||||
entry_point: str,
|
||||
finish_point: str,
|
||||
finish_point: str | None,
|
||||
) -> None:
|
||||
self._nodes = nodes
|
||||
self._entry_point = entry_point
|
||||
@@ -235,12 +233,13 @@ class _GraphEngineRun:
|
||||
self.context = Context(self)
|
||||
|
||||
async def run(self, initial_state: StateT) -> StateT:
|
||||
finish_point = self._finish_point or ""
|
||||
loop = asyncio.get_running_loop()
|
||||
result_obj = await loop.run_in_executor(
|
||||
_advanced_graph_executor(),
|
||||
self._rust_engine.run_graph_py,
|
||||
self._entry_point,
|
||||
self._finish_point,
|
||||
finish_point,
|
||||
initial_state,
|
||||
self._execute_node_for_rust,
|
||||
)
|
||||
|
||||
@@ -44,3 +44,23 @@ async def test_input_and_state_primitives_are_compatible() -> None:
|
||||
"middle:input=from_start",
|
||||
"finish:input=from_middle",
|
||||
]
|
||||
|
||||
|
||||
async def test_run_ends_without_finish_node() -> None:
|
||||
graph = AdvancedStateGraph(PrimitiveState)
|
||||
|
||||
async def start_node(state: PrimitiveState) -> Command:
|
||||
state["logs"].append("start")
|
||||
return Command(update=state, goto=Send("middle_node", "from_start"))
|
||||
|
||||
async def middle_node(input: str, state: PrimitiveState) -> dict[str, object]:
|
||||
state["logs"].append(f"middle:{input}")
|
||||
return {"counter": state["counter"] + 1, "logs": state["logs"], "done": "stopped"}
|
||||
|
||||
graph.add_entry_node(start_node)
|
||||
graph.add_node(middle_node)
|
||||
|
||||
result = await graph.compile().ainvoke({"counter": 7, "logs": [], "done": None})
|
||||
assert result["counter"] == 8
|
||||
assert result["done"] == "stopped"
|
||||
assert result["logs"] == ["start", "middle:from_start"]
|
||||
|
||||
Reference in New Issue
Block a user