Skip to main content

lellm_graph/graph/
graph_builder.rs

1//! GraphBuilder — Graph AST 构建器。
2//!
3//! 提供链式 API 构建 [`Graph`](crate::Graph),支持 `build()`(仅验证)
4//! 和 `compile()`(验证 + 优化 pass)。
5
6use std::sync::Arc;
7
8use indexmap::IndexMap;
9
10use super::{Edge, EdgeAnalysis, Graph};
11use crate::error::{BuildError, BuildErrors};
12use crate::node::NodeKind;
13use crate::state::workflow_state::{MergeStrategy, WorkflowState};
14use crate::state::{State, StateMerge};
15
16// ─── PendingEdge ──────────────────────────────────────────────
17
18/// 待完成的边 — 链式调用的中间句柄。
19pub struct PendingEdge<'a, S: WorkflowState = State, M: MergeStrategy<S> = StateMerge> {
20    builder: &'a mut GraphBuilder<S, M>,
21    edge_index: usize,
22}
23
24impl<'a, S: WorkflowState, M: MergeStrategy<S>> PendingEdge<'a, S, M> {
25    pub fn max_visits(self, n: usize) -> &'a mut GraphBuilder<S, M> {
26        self.builder.edges[self.edge_index].analysis = Some(EdgeAnalysis {
27            max_visits: Some(n),
28        });
29        self.builder
30    }
31}
32
33// ─── GraphBuilder ─────────────────────────────────────────────
34
35/// Graph 构建器。
36pub struct GraphBuilder<S: WorkflowState = State, M: MergeStrategy<S> = StateMerge> {
37    name: String,
38    nodes: IndexMap<String, NodeKind<S, M>>,
39    edges: Vec<Edge<S>>,
40    start: Option<String>,
41    end: Option<String>,
42    /// P0-2: 可选的 canonical hash — 如果 DSL 层设置了就使用,否则计算结构 hash。
43    canonical_hash: Option<u64>,
44}
45
46impl<S: WorkflowState, M: MergeStrategy<S>> GraphBuilder<S, M> {
47    /// 创建 GraphBuilder。
48    pub fn new(name: impl Into<String>) -> Self {
49        Self {
50            name: name.into(),
51            nodes: IndexMap::new(),
52            edges: Vec::new(),
53            start: None,
54            end: None,
55            canonical_hash: None,
56        }
57    }
58
59    /// P0-2: 设置 canonical hash — 由 DSL 层(如 AgentBuilder)调用。
60    pub fn canonical_hash(&mut self, hash: u64) -> &mut Self {
61        self.canonical_hash = Some(hash);
62        self
63    }
64
65    pub fn start(&mut self, node: impl Into<String>) -> &mut Self {
66        self.start = Some(node.into());
67        self
68    }
69
70    pub fn end(&mut self, node: impl Into<String>) -> &mut Self {
71        self.end = Some(node.into());
72        self
73    }
74
75    pub fn node(&mut self, name: impl Into<String>, kind: NodeKind<S, M>) -> &mut Self {
76        self.nodes.insert(name.into(), kind);
77        self
78    }
79
80    /// 便捷方法 — 添加 Subgraph 节点。
81    pub fn subgraph<Inner: WorkflowState, IM: MergeStrategy<Inner>, L: crate::StateLens<S, Inner>>(
82        &mut self,
83        name: impl Into<String>,
84        spec: crate::SubgraphSpec<S, Inner, IM, L>,
85    ) -> &mut Self
86    where
87        S: 'static,
88        Inner: 'static,
89        IM: 'static,
90        L: 'static,
91    {
92        let compiled = spec.compile();
93        self.nodes.insert(name.into(), NodeKind::Subgraph(compiled));
94        self
95    }
96
97    pub fn edge(
98        &mut self,
99        from: impl Into<String>,
100        to: impl Into<String>,
101    ) -> PendingEdge<'_, S, M> {
102        let edge_index = self.edges.len();
103        self.edges.push(Edge {
104            from: from.into(),
105            to: to.into(),
106            condition: None,
107            analysis: None,
108            fallback: false,
109        });
110        PendingEdge {
111            builder: self,
112            edge_index,
113        }
114    }
115
116    pub fn edge_if(
117        &mut self,
118        from: impl Into<String>,
119        to: impl Into<String>,
120        condition: impl Fn(&S) -> bool + Send + Sync + 'static,
121    ) -> PendingEdge<'_, S, M> {
122        let edge_index = self.edges.len();
123        self.edges.push(Edge {
124            from: from.into(),
125            to: to.into(),
126            condition: Some(Arc::new(condition)),
127            analysis: None,
128            fallback: false,
129        });
130        PendingEdge {
131            builder: self,
132            edge_index,
133        }
134    }
135
136    pub fn edge_fallback(
137        &mut self,
138        from: impl Into<String>,
139        to: impl Into<String>,
140    ) -> PendingEdge<'_, S, M> {
141        let edge_index = self.edges.len();
142        self.edges.push(Edge {
143            from: from.into(),
144            to: to.into(),
145            condition: None,
146            analysis: None,
147            fallback: true,
148        });
149        PendingEdge {
150            builder: self,
151            edge_index,
152        }
153    }
154
155    pub fn build(self) -> Result<Graph<S, M>, BuildErrors> {
156        let mut errors = BuildErrors::new();
157
158        let start = match self.start {
159            Some(s) => s,
160            None => {
161                errors.push(BuildError::MissingEntryPoint);
162                return Err(errors);
163            }
164        };
165        let end = match self.end {
166            Some(s) => s,
167            None => {
168                errors.push(BuildError::MissingExitPoint);
169                return Err(errors);
170            }
171        };
172
173        let mut seen_nodes = std::collections::HashSet::new();
174        for name in self.nodes.keys() {
175            if !seen_nodes.insert(name.clone()) {
176                errors.push(BuildError::DuplicateNode { id: name.clone() });
177            }
178        }
179
180        for edge in &self.edges {
181            if !self.nodes.contains_key(&edge.from) {
182                errors.push(BuildError::MissingNode {
183                    from: edge.from.clone(),
184                    to: edge.to.clone(),
185                });
186            }
187            if !self.nodes.contains_key(&edge.to) {
188                errors.push(BuildError::MissingNode {
189                    from: edge.from.clone(),
190                    to: edge.to.clone(),
191                });
192            }
193        }
194
195        if !errors.is_empty() {
196            return Err(errors);
197        }
198
199        let structural_hash = compute_structural_hash(&self.nodes, &self.edges);
200
201        let graph = Graph {
202            name: self.name,
203            nodes: self.nodes,
204            edges: self.edges,
205            start,
206            end,
207            canonical_hash: self.canonical_hash.unwrap_or(structural_hash),
208        };
209
210        if let Err(e) = graph.validate() {
211            return Err(BuildErrors(vec![BuildError::InvalidEdgeDefinition {
212                from: "graph".to_string(),
213                to: "graph".to_string(),
214                reason: e.to_string(),
215            }]));
216        }
217
218        Ok(graph)
219    }
220
221    pub fn name(&self) -> &str {
222        &self.name
223    }
224
225    /// 构建并编译 — 在 `build()` 之后运行 Compiler Pass(如 InlinePass)。
226    pub fn compile(self) -> Result<Graph<S, M>, BuildErrors> {
227        use crate::compiler::CompilerPass;
228
229        let mut graph = self.build()?;
230
231        let mut ctx = crate::compiler::CompilerContext::<S>::new();
232        let pass = crate::compiler::InlinePass::new();
233        pass.run(&mut graph, &mut ctx);
234
235        if ctx.debug {
236            tracing::debug!(
237                inlined = ctx.stats.inlined_count,
238                skipped = ctx.stats.not_inlined_count,
239                "compile passes complete"
240            );
241        }
242
243        Ok(graph)
244    }
245}
246
247// ─── Utilities ─────────────────────────────────────────────────
248
249pub(crate) fn fnv_hash(s: &str) -> u64 {
250    let mut hash: u64 = 0xcbf29ce484222325;
251    for &byte in s.as_bytes() {
252        hash ^= byte as u64;
253        hash = hash.wrapping_mul(0x100000001b3);
254    }
255    hash
256}
257
258/// 计算图结构 hash — 不依赖 HashMap 迭代顺序。
259fn compute_structural_hash<S: WorkflowState, M: MergeStrategy<S>>(
260    nodes: &IndexMap<String, NodeKind<S, M>>,
261    edges: &[Edge<S>],
262) -> u64 {
263    let mut s = String::new();
264    let mut names: Vec<&str> = nodes.keys().map(|k| k.as_str()).collect();
265    names.sort();
266    s.push_str(&names.join(","));
267    s.push('|');
268    let mut edge_strs: Vec<String> = edges
269        .iter()
270        .map(|e| {
271            format!(
272                "{}->{}{:?}{}",
273                e.from,
274                e.to,
275                if e.condition.is_some() { "?" } else { "" },
276                if e.fallback { "!" } else { "" }
277            )
278        })
279        .collect();
280    edge_strs.sort();
281    s.push_str(&edge_strs.join(","));
282    fnv_hash(&s)
283}