Skip to main content

lellm_agent/runtime/react/
graph_builder.rs

1//! ReAct Graph 构建 — 组装内部有环图。
2//!
3//! ```text
4//! START → budget_check
5//!
6//! budget_check --budget_ok--> [llm]
7//!          --need_compact--> [compactor] → [llm]
8//!
9//! [llm] → [post_llm_check]
10//!    --budget_exceeded--> [end]
11//!    --has_tool_calls--> [tool] → [budget_check] (循环)
12//!    --no_tool_calls--> [end]
13//! ```
14
15use std::sync::Arc;
16
17use lellm_graph::{Graph, GraphBuilder, NodeKind, TaskNode};
18
19use super::super::typed_state::{AgentState, AgentStateMerge};
20use super::guards::{BudgetCondition, CompactorNode, PostLLMGuard, StopConfig};
21use super::llm_node::LLMNode;
22use super::tool_node::ToolNode;
23
24/// 构建 ReAct 内部图。
25///
26/// 使用 `Graph<AgentState>` — 节点直接读写强类型 AgentState,零序列化。
27pub fn build_react_graph(
28    llm_node: LLMNode,
29    tool_node: ToolNode,
30    compactor_node: CompactorNode,
31) -> Graph<AgentState, AgentStateMerge> {
32    let llm_name = llm_node.name.clone();
33    let budget = llm_node.config.context_budget.clone();
34    let stop_config = StopConfig::from_tool_use_config(&llm_node.config);
35
36    let mut builder =
37        GraphBuilder::<AgentState, AgentStateMerge>::new(format!("react_{}", llm_name));
38    builder.start("budget_check");
39    builder.end("end");
40
41    // 节点注册 — 全部迁移到 ExternalLeaf(LeafNode + LeafContext)
42    builder.node("llm", NodeKind::ExternalLeaf(Arc::new(llm_node)));
43    builder.node("tool", NodeKind::ExternalLeaf(Arc::new(tool_node)));
44    builder.node(
45        "post_llm_check",
46        NodeKind::ExternalLeaf(Arc::new(PostLLMGuard::new(
47            format!("{}_post_llm", llm_name),
48            stop_config,
49        ))),
50    );
51    builder.node(
52        "budget_check",
53        NodeKind::ExternalLeaf(Arc::new(BudgetCondition::new(
54            format!("{}_budget", llm_name),
55            budget,
56        ))),
57    );
58    builder.node(
59        "compactor",
60        NodeKind::ExternalLeaf(Arc::new(compactor_node)),
61    );
62    // End 节点 — no-op 终端节点
63    builder.node(
64        "end",
65        NodeKind::Task(TaskNode::<AgentState>::new("end", |_| Ok(()))),
66    );
67
68    // 注意:以下 edges 仅用于静态分析(analyze/diagnostics),运行时不使用。
69    // 条件节点通过 ctx.goto()/ctx.end() 控制路由,NextAction::Goto 优先于 edge 解析。
70    //
71    // 静态边与运行时路由的对应关系:
72    //   budget_check → llm          (BudgetCondition: 预算充足时 goto("llm"))
73    //   budget_check → compactor    (BudgetCondition: 需要压缩时 goto("compactor"))
74    //   compactor → llm             (CompactorNode: 压缩后走下一步,无显式 goto)
75    //   llm → post_llm_check        (LLMNode: 调用完走下一步,无显式 goto)
76    //   post_llm_check → tool       (PostLLMGuard: 有 tool_calls 时 goto("tool"))
77    //   post_llm_check → end        (PostLLMGuard: 无 tool_calls 或 budget 超限时 end())
78    //   tool → budget_check         (ToolNode: 执行完走下一步,无显式 goto)
79    builder.edge("budget_check", "llm");
80    builder.edge_fallback("budget_check", "compactor");
81    builder.edge("compactor", "llm");
82    builder.edge("llm", "post_llm_check");
83    builder.edge("post_llm_check", "tool");
84    builder.edge_fallback("post_llm_check", "end");
85    builder.edge("tool", "budget_check");
86
87    builder.build().expect("ReAct graph should be valid")
88}