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    fn call(&self, state: &S) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>>;
61}
62
63impl<S: StateSchema, F, Fut> AsyncFn<S> for F
64where
65    F: Fn(&S) -> Fut + Send + Sync + 'static,
66    Fut: Future<Output = NodeResult<S>> + Send + 'static,
67{
68    fn call(&self, state: &S) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>> {
69        Box::pin((self)(state))
70    }
71}
72
73/// AsyncNode - Simple async node wrapper
74pub struct AsyncNode<S: StateSchema, F: AsyncFn<S>> {
75    name: String,
76    func: F,
77    _marker: PhantomData<S>,
78}
79
80impl<S: StateSchema, F: AsyncFn<S>> AsyncNode<S, F> {
81    pub fn new(name: impl Into<String>, func: F) -> Self {
82        Self {
83            name: name.into(),
84            func,
85            _marker: PhantomData,
86        }
87    }
88}
89
90#[async_trait]
91impl<S: StateSchema, F: AsyncFn<S> + 'static> GraphNode<S> for AsyncNode<S, F> {
92    async fn execute(&self, state: &S, _config: Option<NodeConfig>) -> NodeResult<S> {
93        self.func.call(state).await
94    }
95
96    fn name(&self) -> &str {
97        &self.name
98    }
99}
100
101/// Function-based node implementation
102///
103/// Wraps an async function as a GraphNode.
104pub struct FunctionNode<S: StateSchema, F> {
105    name: String,
106    func: F,
107    _marker: PhantomData<S>,
108}
109
110impl<S: StateSchema, F> FunctionNode<S, F>
111where
112    F: Fn(&S) -> Pin<Box<dyn Future<Output = Result<StateUpdate<S>, GraphError>> + Send>>
113        + Send
114        + Sync,
115{
116    /// Create a new function node
117    pub fn new(name: impl Into<String>, func: F) -> Self {
118        Self {
119            name: name.into(),
120            func,
121            _marker: PhantomData,
122        }
123    }
124}
125
126#[async_trait]
127impl<S: StateSchema, F: 'static> GraphNode<S> for FunctionNode<S, F>
128where
129    F: Fn(&S) -> Pin<Box<dyn Future<Output = Result<StateUpdate<S>, GraphError>> + Send>>
130        + Send
131        + Sync,
132{
133    async fn execute(
134        &self,
135        state: &S,
136        _config: Option<NodeConfig>,
137    ) -> Result<StateUpdate<S>, GraphError> {
138        (self.func)(state).await
139    }
140
141    fn name(&self) -> &str {
142        &self.name
143    }
144}
145
146/// Simple sync node wrapper
147///
148/// For nodes that don't need async execution.
149pub struct SyncNode<S: StateSchema, F> {
150    name: String,
151    func: F,
152    _marker: PhantomData<S>,
153}
154
155impl<S: StateSchema, F> SyncNode<S, F>
156where
157    F: Fn(&S) -> Result<StateUpdate<S>, GraphError> + Send + Sync,
158{
159    /// Create a new sync node
160    pub fn new(name: impl Into<String>, func: F) -> Self {
161        Self {
162            name: name.into(),
163            func,
164            _marker: PhantomData,
165        }
166    }
167}
168
169#[async_trait]
170impl<S: StateSchema, F: 'static> GraphNode<S> for SyncNode<S, F>
171where
172    F: Fn(&S) -> Result<StateUpdate<S>, GraphError> + Send + Sync,
173{
174    async fn execute(
175        &self,
176        state: &S,
177        _config: Option<NodeConfig>,
178    ) -> Result<StateUpdate<S>, GraphError> {
179        (self.func)(state)
180    }
181
182    fn name(&self) -> &str {
183        &self.name
184    }
185}
186
187/// Placeholder node for entry/exit points
188pub struct SentinelNode {
189    name: String,
190}
191
192impl SentinelNode {
193    pub fn start() -> Self {
194        Self {
195            name: crate::START.to_string(),
196        }
197    }
198
199    pub fn end() -> Self {
200        Self {
201            name: crate::END.to_string(),
202        }
203    }
204
205    pub fn custom(name: impl Into<String>) -> Self {
206        Self { name: name.into() }
207    }
208}
209
210#[async_trait]
211impl<S: StateSchema> GraphNode<S> for SentinelNode {
212    async fn execute(
213        &self,
214        _state: &S,
215        _config: Option<NodeConfig>,
216    ) -> Result<StateUpdate<S>, GraphError> {
217        // Sentinel nodes don't modify state
218        Ok(StateUpdate::unchanged())
219    }
220
221    fn name(&self) -> &str {
222        &self.name
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use crate::state::AgentState;
230
231    #[tokio::test]
232    async fn test_sync_node() {
233        let node = SyncNode::new("test", |state: &AgentState| {
234            Ok(StateUpdate::full(AgentState::new(state.input.clone())))
235        });
236
237        let state = AgentState::new("Hello".to_string());
238        let result = node.execute(&state, None).await;
239        assert!(result.is_ok());
240    }
241
242    #[test]
243    fn test_sentinel_nodes() {
244        let start: SentinelNode = SentinelNode::start();
245        assert_eq!(GraphNode::<AgentState>::name(&start), crate::START);
246
247        let end: SentinelNode = SentinelNode::end();
248        assert_eq!(GraphNode::<AgentState>::name(&end), crate::END);
249    }
250}