1use 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#[async_trait]
19pub trait GraphNode<S: StateSchema>: Send + Sync + 'static {
20 async fn execute(
29 &self,
30 state: &S,
31 config: Option<NodeConfig>,
32 ) -> Result<StateUpdate<S>, GraphError>;
33
34 fn name(&self) -> &str;
36}
37
38pub const INTERRUPT_RESUME_KEY: &str = "__lc_interrupt_resume";
42
43#[derive(Debug, Clone, Default)]
45pub struct NodeConfig {
46 pub recursion_limit: usize,
48
49 pub metadata: std::collections::HashMap<String, serde_json::Value>,
51
52 pub debug: bool,
54}
55
56pub type NodeResult<S> = Result<StateUpdate<S>, GraphError>;
58
59pub type AsyncNodeFn<S> =
61 Box<dyn Fn(&S) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>> + Send + Sync>;
62
63pub trait AsyncFn<S: StateSchema>: Send + Sync {
65 fn call(&self, state: &S) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>>;
67}
68
69impl<S: StateSchema, F, Fut> AsyncFn<S> for F
70where
71 F: Fn(&S) -> Fut + Send + Sync + 'static,
72 Fut: Future<Output = NodeResult<S>> + Send + 'static,
73{
74 fn call(&self, state: &S) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>> {
75 Box::pin((self)(state))
76 }
77}
78
79pub struct AsyncNode<S: StateSchema, F: AsyncFn<S>> {
81 name: String,
82 func: F,
83 _marker: PhantomData<S>,
84}
85
86impl<S: StateSchema, F: AsyncFn<S>> AsyncNode<S, F> {
87 pub fn new(name: impl Into<String>, func: F) -> Self {
89 Self {
90 name: name.into(),
91 func,
92 _marker: PhantomData,
93 }
94 }
95}
96
97#[async_trait]
98impl<S: StateSchema, F: AsyncFn<S> + 'static> GraphNode<S> for AsyncNode<S, F> {
99 async fn execute(&self, state: &S, _config: Option<NodeConfig>) -> NodeResult<S> {
100 self.func.call(state).await
101 }
102
103 fn name(&self) -> &str {
104 &self.name
105 }
106}
107
108pub struct FunctionNode<S: StateSchema, F> {
112 name: String,
113 func: F,
114 _marker: PhantomData<S>,
115}
116
117impl<S: StateSchema, F> FunctionNode<S, F>
118where
119 F: Fn(&S) -> Pin<Box<dyn Future<Output = Result<StateUpdate<S>, GraphError>> + Send>>
120 + Send
121 + Sync,
122{
123 pub fn new(name: impl Into<String>, func: F) -> Self {
125 Self {
126 name: name.into(),
127 func,
128 _marker: PhantomData,
129 }
130 }
131}
132
133#[async_trait]
134impl<S: StateSchema, F: 'static> GraphNode<S> for FunctionNode<S, F>
135where
136 F: Fn(&S) -> Pin<Box<dyn Future<Output = Result<StateUpdate<S>, GraphError>> + Send>>
137 + Send
138 + Sync,
139{
140 async fn execute(
141 &self,
142 state: &S,
143 _config: Option<NodeConfig>,
144 ) -> Result<StateUpdate<S>, GraphError> {
145 (self.func)(state).await
146 }
147
148 fn name(&self) -> &str {
149 &self.name
150 }
151}
152
153pub struct SyncNode<S: StateSchema, F> {
157 name: String,
158 func: F,
159 _marker: PhantomData<S>,
160}
161
162impl<S: StateSchema, F> SyncNode<S, F>
163where
164 F: Fn(&S) -> Result<StateUpdate<S>, GraphError> + Send + Sync,
165{
166 pub fn new(name: impl Into<String>, func: F) -> Self {
168 Self {
169 name: name.into(),
170 func,
171 _marker: PhantomData,
172 }
173 }
174}
175
176#[async_trait]
177impl<S: StateSchema, F: 'static> GraphNode<S> for SyncNode<S, F>
178where
179 F: Fn(&S) -> Result<StateUpdate<S>, GraphError> + Send + Sync,
180{
181 async fn execute(
182 &self,
183 state: &S,
184 _config: Option<NodeConfig>,
185 ) -> Result<StateUpdate<S>, GraphError> {
186 (self.func)(state)
187 }
188
189 fn name(&self) -> &str {
190 &self.name
191 }
192}
193
194pub struct InterruptibleNode<S: StateSchema, F> {
206 name: String,
207 func: F,
208 _marker: PhantomData<S>,
209}
210
211impl<S: StateSchema, F> InterruptibleNode<S, F>
212where
213 F: Fn(&S, Option<&serde_json::Value>) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>>
214 + Send
215 + Sync,
216{
217 pub fn new(name: impl Into<String>, func: F) -> Self {
220 Self {
221 name: name.into(),
222 func,
223 _marker: PhantomData,
224 }
225 }
226}
227
228#[async_trait]
229impl<S: StateSchema, F: 'static> GraphNode<S> for InterruptibleNode<S, F>
230where
231 F: Fn(&S, Option<&serde_json::Value>) -> Pin<Box<dyn Future<Output = NodeResult<S>> + Send>>
232 + Send
233 + Sync,
234{
235 async fn execute(
236 &self,
237 state: &S,
238 config: Option<NodeConfig>,
239 ) -> Result<StateUpdate<S>, GraphError> {
240 let resume = config
241 .as_ref()
242 .and_then(|c| c.metadata.get(INTERRUPT_RESUME_KEY))
243 .cloned();
244 (self.func)(state, resume.as_ref()).await
245 }
246
247 fn name(&self) -> &str {
248 &self.name
249 }
250}
251
252pub struct SentinelNode {
254 name: String,
255}
256
257impl SentinelNode {
258 pub fn start() -> Self {
260 Self {
261 name: crate::START.to_string(),
262 }
263 }
264
265 pub fn end() -> Self {
267 Self {
268 name: crate::END.to_string(),
269 }
270 }
271
272 pub fn custom(name: impl Into<String>) -> Self {
274 Self { name: name.into() }
275 }
276}
277
278#[async_trait]
279impl<S: StateSchema> GraphNode<S> for SentinelNode {
280 async fn execute(
281 &self,
282 _state: &S,
283 _config: Option<NodeConfig>,
284 ) -> Result<StateUpdate<S>, GraphError> {
285 Ok(StateUpdate::unchanged())
287 }
288
289 fn name(&self) -> &str {
290 &self.name
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use crate::state::AgentState;
298
299 #[tokio::test]
300 async fn test_sync_node() {
301 let node = SyncNode::new("test", |state: &AgentState| {
302 Ok(StateUpdate::full(AgentState::new(state.input.clone())))
303 });
304
305 let state = AgentState::new("Hello".to_string());
306 let result = node.execute(&state, None).await;
307 assert!(result.is_ok());
308 }
309
310 #[test]
311 fn test_sentinel_nodes() {
312 let start: SentinelNode = SentinelNode::start();
313 assert_eq!(GraphNode::<AgentState>::name(&start), crate::START);
314
315 let end: SentinelNode = SentinelNode::end();
316 assert_eq!(GraphNode::<AgentState>::name(&end), crate::END);
317 }
318}