Skip to main content

lc_langgraph/
graph.rs

1// crates/lc-langgraph/src/graph.rs
2//! StateGraph - Main graph class for LangGraph
3
4use crate::compiled::CompiledGraph;
5use crate::edge::{ConditionalEdge, GraphEdge};
6use crate::errors::{GraphError, GraphResult};
7use crate::node::{AsyncFn, AsyncNode, GraphNode, SyncNode};
8use crate::state::{Reducer, ReplaceReducer, StateSchema, StateUpdate};
9use crate::subgraph::SubgraphNode;
10use std::collections::HashMap;
11use std::sync::Arc;
12
13/// START sentinel node identifier
14pub const START: &str = "__start__";
15/// END sentinel node identifier
16pub const END: &str = "__end__";
17
18/// StateGraph - Main graph builder
19///
20/// Manages nodes, edges, and state schema for graph execution.
21/// After building, compile to get an executable CompiledGraph.
22pub struct StateGraph<S: StateSchema> {
23    nodes: HashMap<String, Arc<dyn GraphNode<S>>>,
24    edges: Vec<GraphEdge>,
25    entry_point: Option<String>,
26    reducers: HashMap<String, Arc<dyn Reducer<S>>>,
27    default_reducer: Arc<dyn Reducer<S>>,
28    conditional_routers: HashMap<String, Arc<dyn ConditionalEdge<S>>>,
29}
30
31impl<S: StateSchema + 'static> StateGraph<S> {
32    /// Create a new empty state graph.
33    pub fn new() -> Self {
34        Self {
35            nodes: HashMap::new(),
36            edges: Vec::new(),
37            entry_point: None,
38            reducers: HashMap::new(),
39            default_reducer: Arc::new(ReplaceReducer),
40            conditional_routers: HashMap::new(),
41        }
42    }
43
44    /// Add a node to the graph.
45    pub fn add_node<N: GraphNode<S> + 'static>(&mut self, node: N) -> &mut Self {
46        let name = node.name().to_string();
47        self.nodes.insert(name, Arc::new(node));
48        self
49    }
50
51    /// Add a synchronous function node to the graph.
52    pub fn add_node_fn<F>(&mut self, name: impl Into<String>, func: F) -> &mut Self
53    where
54        F: Fn(&S) -> Result<StateUpdate<S>, GraphError> + Send + Sync + 'static,
55    {
56        let node = SyncNode::new(name, func);
57        let node_name = node.name().to_string();
58        self.nodes.insert(node_name, Arc::new(node));
59        self
60    }
61
62    /// Add an async function node to the graph.
63    pub fn add_async_node<F>(&mut self, name: impl Into<String>, func: F) -> &mut Self
64    where
65        F: AsyncFn<S> + 'static,
66    {
67        let node = AsyncNode::new(name, func);
68        let node_name = node.name().to_string();
69        self.nodes.insert(node_name, Arc::new(node));
70        self
71    }
72
73    /// Add a subgraph node with input/output mappers.
74    pub fn add_subgraph<SubS: StateSchema + 'static>(
75        &mut self,
76        name: impl Into<String>,
77        subgraph: CompiledGraph<SubS>,
78        input_mapper: impl Fn(&S) -> SubS + Send + Sync + 'static,
79        output_mapper: impl Fn(&SubS, &mut S) + Send + Sync + 'static,
80    ) -> &mut Self {
81        let node = SubgraphNode::new(name, subgraph, input_mapper, output_mapper);
82        let node_name = node.name().to_string();
83        self.nodes.insert(node_name, Arc::new(node));
84        self
85    }
86
87    /// Add a subgraph node that shares the same state type.
88    pub fn add_subgraph_same_state(
89        &mut self,
90        name: impl Into<String>,
91        subgraph: CompiledGraph<S>,
92    ) -> &mut Self {
93        let node: SubgraphNode<S, S> = SubgraphNode::same_state(name, subgraph);
94        let node_name = node.name().to_string();
95        self.nodes.insert(node_name, Arc::new(node));
96        self
97    }
98
99    /// Add a fixed edge between two nodes.
100    pub fn add_edge(&mut self, source: impl Into<String>, target: impl Into<String>) -> &mut Self {
101        let edge = GraphEdge::fixed(source, target);
102        self.edges.push(edge);
103        self
104    }
105
106    /// Add a conditional edge routed by a named router.
107    pub fn add_conditional_edges(
108        &mut self,
109        source: impl Into<String>,
110        router_name: impl Into<String>,
111        targets: HashMap<String, String>,
112        default: Option<String>,
113    ) -> &mut Self {
114        let edge = GraphEdge::conditional(source, router_name, targets, default);
115        self.edges.push(edge);
116        self
117    }
118
119    /// Add a FanOut edge for parallel execution.
120    pub fn add_fan_out(&mut self, source: impl Into<String>, targets: Vec<String>) -> &mut Self {
121        let edge = GraphEdge::fan_out(source, targets);
122        self.edges.push(edge);
123        self
124    }
125
126    /// Add a FanIn edge joining multiple sources into one target.
127    pub fn add_fan_in(&mut self, sources: Vec<String>, target: impl Into<String>) -> &mut Self {
128        let edge = GraphEdge::fan_in(sources, target);
129        self.edges.push(edge);
130        self
131    }
132
133    /// Register a conditional routing function by name.
134    pub fn set_conditional_router<R: ConditionalEdge<S> + 'static>(
135        &mut self,
136        name: impl Into<String>,
137        router: R,
138    ) -> &mut Self {
139        self.conditional_routers
140            .insert(name.into(), Arc::new(router));
141        self
142    }
143
144    /// Set the entry point node for the graph.
145    pub fn set_entry_point(&mut self, node: impl Into<String>) -> &mut Self {
146        self.entry_point = Some(node.into());
147        self
148    }
149
150    /// Register a reducer for a specific state field.
151    pub fn set_reducer(
152        &mut self,
153        field: impl Into<String>,
154        reducer: Arc<dyn Reducer<S>>,
155    ) -> &mut Self {
156        self.reducers.insert(field.into(), reducer);
157        self
158    }
159
160    /// Compile the graph into an executable `CompiledGraph`.
161    pub fn compile(&self) -> GraphResult<CompiledGraph<S>> {
162        if self.nodes.is_empty() {
163            return Err(GraphError::ValidationError(
164                "Graph has no nodes".to_string(),
165            ));
166        }
167
168        let entry = self
169            .entry_point
170            .clone()
171            .or_else(|| self.find_first_node_after_start())
172            .ok_or_else(|| GraphError::ValidationError("No entry point defined".to_string()))?;
173
174        if !self.nodes.contains_key(&entry) && entry != START {
175            return Err(GraphError::ValidationError(format!(
176                "Entry point '{}' not found",
177                entry
178            )));
179        }
180
181        let mut compiled = CompiledGraph::new(
182            self.nodes.clone(),
183            self.edges.clone(),
184            entry,
185            self.default_reducer.clone(),
186        );
187
188        for (name, router) in &self.conditional_routers {
189            compiled.add_router(name.clone(), router.clone());
190        }
191
192        compiled.validate()?;
193        Ok(compiled)
194    }
195
196    fn find_first_node_after_start(&self) -> Option<String> {
197        for edge in &self.edges {
198            if edge.source() == START {
199                if let Some(target) = edge.fixed_target() {
200                    return Some(target.to_string());
201                }
202            }
203        }
204        None
205    }
206}
207
208impl<S: StateSchema + 'static> Default for StateGraph<S> {
209    fn default() -> Self {
210        Self::new()
211    }
212}
213
214/// GraphBuilder - Fluent builder pattern for StateGraph
215pub struct GraphBuilder<S: StateSchema> {
216    graph: StateGraph<S>,
217}
218
219impl<S: StateSchema + 'static> GraphBuilder<S> {
220    /// Create a new empty graph builder.
221    pub fn new() -> Self {
222        Self {
223            graph: StateGraph::new(),
224        }
225    }
226
227    /// Add a node to the graph.
228    pub fn add_node<N: GraphNode<S> + 'static>(mut self, node: N) -> Self {
229        self.graph.add_node(node);
230        self
231    }
232
233    /// Add a synchronous function node to the graph.
234    pub fn add_node_fn<F>(mut self, name: impl Into<String>, func: F) -> Self
235    where
236        F: Fn(&S) -> Result<StateUpdate<S>, GraphError> + Send + Sync + 'static,
237    {
238        self.graph.add_node_fn(name, func);
239        self
240    }
241
242    /// Add an async function node to the graph.
243    pub fn add_async_node<F>(mut self, name: impl Into<String>, func: F) -> Self
244    where
245        F: AsyncFn<S> + 'static,
246    {
247        self.graph.add_async_node(name, func);
248        self
249    }
250
251    /// Add a subgraph node with input/output mappers.
252    pub fn add_subgraph<SubS: StateSchema + 'static>(
253        mut self,
254        name: impl Into<String>,
255        subgraph: CompiledGraph<SubS>,
256        input_mapper: impl Fn(&S) -> SubS + Send + Sync + 'static,
257        output_mapper: impl Fn(&SubS, &mut S) + Send + Sync + 'static,
258    ) -> Self {
259        self.graph
260            .add_subgraph(name, subgraph, input_mapper, output_mapper);
261        self
262    }
263
264    /// Add a subgraph node that shares the same state type.
265    pub fn add_subgraph_same_state(
266        mut self,
267        name: impl Into<String>,
268        subgraph: CompiledGraph<S>,
269    ) -> Self {
270        self.graph.add_subgraph_same_state(name, subgraph);
271        self
272    }
273
274    /// Add a fixed edge between two nodes.
275    pub fn add_edge(mut self, source: impl Into<String>, target: impl Into<String>) -> Self {
276        self.graph.add_edge(source, target);
277        self
278    }
279
280    /// Add a conditional edge routed by a named router.
281    pub fn add_conditional_edges(
282        mut self,
283        source: impl Into<String>,
284        router_name: impl Into<String>,
285        targets: HashMap<String, String>,
286        default: Option<String>,
287    ) -> Self {
288        self.graph
289            .add_conditional_edges(source, router_name, targets, default);
290        self
291    }
292
293    /// Add a FanOut edge for parallel execution.
294    pub fn add_fan_out(mut self, source: impl Into<String>, targets: Vec<String>) -> Self {
295        self.graph.add_fan_out(source, targets);
296        self
297    }
298
299    /// Add a FanIn edge joining multiple sources into one target.
300    pub fn add_fan_in(mut self, sources: Vec<String>, target: impl Into<String>) -> Self {
301        self.graph.add_fan_in(sources, target);
302        self
303    }
304
305    /// Set the entry point node for the graph.
306    pub fn set_entry_point(mut self, node: impl Into<String>) -> Self {
307        self.graph.set_entry_point(node);
308        self
309    }
310
311    /// Compile the graph into an executable `CompiledGraph`.
312    pub fn compile(self) -> GraphResult<CompiledGraph<S>> {
313        self.graph.compile()
314    }
315
316    /// Build and return the underlying `StateGraph`.
317    pub fn build(self) -> StateGraph<S> {
318        self.graph
319    }
320}
321
322impl<S: StateSchema + 'static> Default for GraphBuilder<S> {
323    fn default() -> Self {
324        Self::new()
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331    use crate::state::AgentState;
332
333    #[test]
334    fn test_graph_creation() {
335        let graph: StateGraph<AgentState> = StateGraph::new();
336        assert!(graph.nodes.is_empty());
337        assert!(graph.edges.is_empty());
338    }
339
340    #[test]
341    fn test_add_node_fn() {
342        let mut graph: StateGraph<AgentState> = StateGraph::new();
343        graph.add_node_fn("test_node", |state: &AgentState| {
344            Ok(StateUpdate::full(state.clone()))
345        });
346        assert_eq!(graph.nodes.len(), 1);
347    }
348
349    #[test]
350    fn test_add_edge() {
351        let mut graph: StateGraph<AgentState> = StateGraph::new();
352        graph.add_edge(START, "node1");
353        graph.add_edge("node1", END);
354        assert_eq!(graph.edges.len(), 2);
355    }
356
357    #[test]
358    fn test_compile_empty_graph() {
359        let graph: StateGraph<AgentState> = StateGraph::new();
360        let result = graph.compile();
361        assert!(result.is_err());
362    }
363
364    #[test]
365    fn test_builder_pattern() {
366        let compiled = GraphBuilder::<AgentState>::new()
367            .add_node_fn("process", |state| Ok(StateUpdate::full(state.clone())))
368            .add_edge(START, "process")
369            .add_edge("process", END)
370            .compile();
371        assert!(compiled.is_ok());
372    }
373}