Skip to main content

lc_langgraph/
node.rs

1// crates/lc-langgraph/src/node.rs
2//! Node definition for LangGraph
3//!
4//! Nodes are the execution units in a graph. Each node receives the current
5//! state and returns a state update.
6
7use crate::errors::GraphError;
8use crate::state::{StateSchema, StateUpdate};
9use async_trait::async_trait;
10use std::future::Future;
11use std::marker::PhantomData;
12use std::pin::Pin;
13
14/// Graph Node trait
15///
16/// Nodes are async functions that take a state and return a state update.
17/// They represent the work units in the graph.
18#[async_trait]
19pub trait GraphNode<S: StateSchema>: Send + Sync + 'static {
20    /// Execute the node
21    ///
22    /// # Parameters
23    /// - `state`: Current state of the graph
24    /// - `config`: Optional configuration for this execution
25    ///
26    /// # Returns
27    /// A state update that will be merged into the current state
28    async fn execute(
29        &self,
30        state: &S,
31        config: Option<NodeConfig>,
32    ) -> Result<StateUpdate<S>, GraphError>;
33
34    /// Get node name
35    fn name(&self) -> &str;
36}
37
38/// Node configuration
39#[derive(Debug, Clone, Default)]
40pub struct NodeConfig {
41    /// Maximum recursion depth
42    pub recursion_limit: usize,
43
44    /// Custom metadata
45    pub metadata: std::collections::HashMap<String, serde_json::Value>,
46
47    /// Enable debug tracing
48    pub debug: bool,
49}
50
51/// Node execution result (alias for clarity)
52pub type NodeResult<S> = Result<StateUpdate<S>, GraphError>;
53
54/// Async node function type (boxed future)
55pub type AsyncNodeFn<S> =
56    Box<dyn Fn(&S) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>> + Send + Sync>;
57
58/// AsyncFn trait for simpler async node creation
59pub trait AsyncFn<S: StateSchema>: Send + Sync {
60    /// Call the async function with the given state.
61    fn call(&self, state: &S) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>>;
62}
63
64impl<S: StateSchema, F, Fut> AsyncFn<S> for F
65where
66    F: Fn(&S) -> Fut + Send + Sync + 'static,
67    Fut: Future<Output = NodeResult<S>> + Send + 'static,
68{
69    fn call(&self, state: &S) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>> {
70        Box::pin((self)(state))
71    }
72}
73
74/// AsyncNode - Simple async node wrapper
75pub struct AsyncNode<S: StateSchema, F: AsyncFn<S>> {
76    name: String,
77    func: F,
78    _marker: PhantomData<S>,
79}
80
81impl<S: StateSchema, F: AsyncFn<S>> AsyncNode<S, F> {
82    /// Create a new async node with the given name and function.
83    pub fn new(name: impl Into<String>, func: F) -> Self {
84        Self {
85            name: name.into(),
86            func,
87            _marker: PhantomData,
88        }
89    }
90}
91
92#[async_trait]
93impl<S: StateSchema, F: AsyncFn<S> + 'static> GraphNode<S> for AsyncNode<S, F> {
94    async fn execute(&self, state: &S, _config: Option<NodeConfig>) -> NodeResult<S> {
95        self.func.call(state).await
96    }
97
98    fn name(&self) -> &str {
99        &self.name
100    }
101}
102
103/// Function-based node implementation
104///
105/// Wraps an async function as a GraphNode.
106pub struct FunctionNode<S: StateSchema, F> {
107    name: String,
108    func: F,
109    _marker: PhantomData<S>,
110}
111
112impl<S: StateSchema, F> FunctionNode<S, F>
113where
114    F: Fn(&S) -> Pin<Box<dyn Future<Output = Result<StateUpdate<S>, GraphError>> + Send>>
115        + Send
116        + Sync,
117{
118    /// Create a new function node
119    pub fn new(name: impl Into<String>, func: F) -> Self {
120        Self {
121            name: name.into(),
122            func,
123            _marker: PhantomData,
124        }
125    }
126}
127
128#[async_trait]
129impl<S: StateSchema, F: 'static> GraphNode<S> for FunctionNode<S, F>
130where
131    F: Fn(&S) -> Pin<Box<dyn Future<Output = Result<StateUpdate<S>, GraphError>> + Send>>
132        + Send
133        + Sync,
134{
135    async fn execute(
136        &self,
137        state: &S,
138        _config: Option<NodeConfig>,
139    ) -> Result<StateUpdate<S>, GraphError> {
140        (self.func)(state).await
141    }
142
143    fn name(&self) -> &str {
144        &self.name
145    }
146}
147
148/// Simple sync node wrapper
149///
150/// For nodes that don't need async execution.
151pub struct SyncNode<S: StateSchema, F> {
152    name: String,
153    func: F,
154    _marker: PhantomData<S>,
155}
156
157impl<S: StateSchema, F> SyncNode<S, F>
158where
159    F: Fn(&S) -> Result<StateUpdate<S>, GraphError> + Send + Sync,
160{
161    /// Create a new sync node
162    pub fn new(name: impl Into<String>, func: F) -> Self {
163        Self {
164            name: name.into(),
165            func,
166            _marker: PhantomData,
167        }
168    }
169}
170
171#[async_trait]
172impl<S: StateSchema, F: 'static> GraphNode<S> for SyncNode<S, F>
173where
174    F: Fn(&S) -> Result<StateUpdate<S>, GraphError> + Send + Sync,
175{
176    async fn execute(
177        &self,
178        state: &S,
179        _config: Option<NodeConfig>,
180    ) -> Result<StateUpdate<S>, GraphError> {
181        (self.func)(state)
182    }
183
184    fn name(&self) -> &str {
185        &self.name
186    }
187}
188
189/// Placeholder node for entry/exit points
190pub struct SentinelNode {
191    name: String,
192}
193
194impl SentinelNode {
195    /// Create a sentinel node for the `START` marker.
196    pub fn start() -> Self {
197        Self {
198            name: crate::START.to_string(),
199        }
200    }
201
202    /// Create a sentinel node for the `END` marker.
203    pub fn end() -> Self {
204        Self {
205            name: crate::END.to_string(),
206        }
207    }
208
209    /// Create a custom-named sentinel node.
210    pub fn custom(name: impl Into<String>) -> Self {
211        Self { name: name.into() }
212    }
213}
214
215#[async_trait]
216impl<S: StateSchema> GraphNode<S> for SentinelNode {
217    async fn execute(
218        &self,
219        _state: &S,
220        _config: Option<NodeConfig>,
221    ) -> Result<StateUpdate<S>, GraphError> {
222        // Sentinel nodes don't modify state
223        Ok(StateUpdate::unchanged())
224    }
225
226    fn name(&self) -> &str {
227        &self.name
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234    use crate::state::AgentState;
235
236    #[tokio::test]
237    async fn test_sync_node() {
238        let node = SyncNode::new("test", |state: &AgentState| {
239            Ok(StateUpdate::full(AgentState::new(state.input.clone())))
240        });
241
242        let state = AgentState::new("Hello".to_string());
243        let result = node.execute(&state, None).await;
244        assert!(result.is_ok());
245    }
246
247    #[test]
248    fn test_sentinel_nodes() {
249        let start: SentinelNode = SentinelNode::start();
250        assert_eq!(GraphNode::<AgentState>::name(&start), crate::START);
251
252        let end: SentinelNode = SentinelNode::end();
253        assert_eq!(GraphNode::<AgentState>::name(&end), crate::END);
254    }
255}