Skip to main content

langgraph_core_rs/runnable/
into_node_fn.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use serde_json::Value as JsonValue;
6use langgraph_checkpoint::config::RunnableConfig;
7
8use super::base::{Runnable, RunnableError};
9use super::callable::RunnableCallable;
10
11/// Type alias for the boxed future returned by async node functions.
12///
13/// Use this in return-position of factory functions that produce node closures:
14/// ```ignore
15/// fn my_node(model: Arc<dyn BaseChatModel>) -> impl Fn(JsonValue, RunnableConfig) -> NodeFnFuture + Send + Sync + 'static {
16///     move |input, config| {
17///         let model = model.clone();
18///         Box::pin(async move { ... })
19///     }
20/// }
21/// ```
22pub type NodeFnFuture = Pin<Box<dyn Future<Output = Result<JsonValue, RunnableError>> + Send>>;
23
24/// Trait for converting closures into node Runnables.
25///
26/// This enables `add_node` and `add_conditional_edges` to accept both
27/// async and sync closures with a uniform API.
28///
29/// Implemented for:
30/// - Async closures: `Fn(JsonValue, RunnableConfig) -> impl Future<...>`
31/// - Via `SyncNodeFn` wrapper: sync closures
32/// - `Arc<dyn Runnable>`: pre-built runnables
33pub trait IntoNodeFunction {
34    fn into_runnable(self, name: &str) -> Arc<dyn Runnable>;
35}
36
37// Blanket impl for async closures
38impl<F, Fut> IntoNodeFunction for F
39where
40    F: Fn(JsonValue, RunnableConfig) -> Fut + Send + Sync + 'static,
41    Fut: Future<Output = Result<JsonValue, RunnableError>> + Send + 'static,
42{
43    fn into_runnable(self, name: &str) -> Arc<dyn Runnable> {
44        Arc::new(RunnableCallable::new(name, self))
45    }
46}
47
48/// Wrapper for sync node functions.
49///
50/// Use this to pass sync closures to `add_node`:
51/// ```ignore
52/// graph.add_node("my_node", SyncNodeFn(|input, _config| {
53///     Ok(json!({"result": 42}))
54/// }));
55/// ```
56pub struct SyncNodeFn<F>(pub F);
57
58impl<F> IntoNodeFunction for SyncNodeFn<F>
59where
60    F: Fn(&JsonValue, &RunnableConfig) -> Result<JsonValue, RunnableError>
61        + Send + Sync + 'static,
62{
63    fn into_runnable(self, name: &str) -> Arc<dyn Runnable> {
64        Arc::new(RunnableCallable::new_sync(name, self.0))
65    }
66}
67
68/// Implement `IntoNodeFunction` for pre-built runnables.
69impl IntoNodeFunction for Arc<dyn Runnable> {
70    fn into_runnable(self, _name: &str) -> Arc<dyn Runnable> {
71        self
72    }
73}
74
75/// Wrapper for single-argument sync node functions that ignore config.
76///
77/// ```ignore
78/// use langgraph::prelude::*;
79///
80/// graph.add_node("doubler", NodeFn1(|input| {
81///     let n = input.get("value").and_then(|v| v.as_i64()).unwrap_or(0);
82///     Ok(json!({"value": n * 2}))
83/// }));
84/// ```
85pub struct NodeFn1<F>(pub F);
86
87impl<F> IntoNodeFunction for NodeFn1<F>
88where
89    F: Fn(&JsonValue) -> Result<JsonValue, RunnableError> + Send + Sync + 'static,
90{
91    fn into_runnable(self, name: &str) -> Arc<dyn Runnable> {
92        let f = self.0;
93        Arc::new(RunnableCallable::new_sync(name, move |input: &JsonValue, _config: &RunnableConfig| {
94            f(input)
95        }))
96    }
97}
98
99/// Wrapper for routing functions used with `add_conditional_edges`.
100///
101/// Wraps a function `Fn(&JsonValue) -> String` so it can be used
102/// directly as the `path` argument to `add_conditional_edges`.
103///
104/// ```ignore
105/// use langgraph::prelude::*;
106/// use langgraph_prebuilt::tools_condition;
107///
108/// graph.add_conditional_edges(
109///     "agent",
110///     RoutingFn(tools_condition),
111///     Some(HashMap::from([
112///         ("tools".to_string(), "tools".to_string()),
113///         (END.to_string(), END.to_string()),
114///     ])),
115/// )?;
116/// ```
117pub struct RoutingFn<F>(pub F);
118
119impl<F> IntoNodeFunction for RoutingFn<F>
120where
121    F: Fn(&JsonValue) -> String + Send + Sync + 'static,
122{
123    fn into_runnable(self, name: &str) -> Arc<dyn Runnable> {
124        let f = self.0;
125        Arc::new(RunnableCallable::new_sync(name, move |input: &JsonValue, _config: &RunnableConfig| {
126            let route = f(input);
127            Ok(JsonValue::String(route))
128        }))
129    }
130}
131
132/// Convenience macro to wrap a sync closure for use with `add_node`.
133///
134/// ```ignore
135/// use langgraph::prelude::*;
136///
137/// graph.add_node("doubler", node_fn!(|input, _config| {
138///     let n = input.as_i64().unwrap_or(0);
139///     Ok(json!(n * 2))
140/// }));
141/// ```
142#[macro_export]
143macro_rules! node_fn {
144    ($f:expr) => {
145        $crate::runnable::SyncNodeFn($f)
146    };
147}
148
149/// Wrap a routing function for use with `add_conditional_edges`.
150///
151/// ```ignore
152/// use langgraph::prelude::*;
153///
154/// // Instead of: RoutingFn(tools_condition)
155/// graph.add_conditional_edges("agent", routing!(tools_condition), None)?;
156/// ```
157#[macro_export]
158macro_rules! routing {
159    ($f:expr) => {
160        $crate::runnable::RoutingFn($f)
161    };
162}
163
164/// Simplify `add_conditional_edges` with automatic route map construction.
165///
166/// Python:
167/// ```python
168/// graph.add_conditional_edges("chatbot", tools_condition)
169/// ```
170///
171/// Rust (before):
172/// ```ignore
173/// graph.add_conditional_edges(
174///     "chatbot",
175///     RoutingFn(tools_condition),
176///     Some({
177///         let mut map = HashMap::new();
178///         map.insert("tools".to_string(), "tools".to_string());
179///         map.insert(END.to_string(), END.to_string());
180///         map
181///     }),
182/// )?;
183/// ```
184///
185/// Rust (after):
186/// ```ignore
187/// conditional_edges!(graph, "chatbot", tools_condition, "tools" => "tools", END => END)?;
188/// ```
189#[macro_export]
190macro_rules! conditional_edges {
191    ($graph:expr, $source:expr, $route_fn:expr, $($key:expr => $val:expr),+ $(,)?) => {
192        $graph.add_conditional_edges(
193            $source,
194            $crate::runnable::RoutingFn($route_fn),
195            Some({
196                let mut map = std::collections::HashMap::new();
197                $(map.insert($key.to_string(), $val.to_string());)+
198                map
199            }),
200        )
201    };
202}