lc_core/runnables/
lambda.rs1use super::config::RunnableConfig;
8use super::error::LcelError;
9use super::runnable_trait::Runnable;
10use async_trait::async_trait;
11use std::future::Future;
12use std::pin::Pin;
13use std::sync::Arc;
14
15type AsyncFn<I, O> = Arc<
17 dyn Fn(I) -> Pin<Box<dyn Future<Output = Result<O, LcelError>> + Send>> + Send + Sync,
18>;
19
20pub struct RunnableLambda<I: Send + Sync + 'static, O: Send + Sync + 'static> {
31 func: AsyncFn<I, O>,
32}
33
34impl<I: Send + Sync + 'static, O: Send + Sync + 'static> std::fmt::Debug for RunnableLambda<I, O> {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.debug_struct("RunnableLambda")
37 .field("input", &std::any::type_name::<I>())
38 .field("output", &std::any::type_name::<O>())
39 .finish()
40 }
41}
42
43impl<I: Send + Sync + 'static, O: Send + Sync + 'static> RunnableLambda<I, O> {
44 pub fn new_sync<F>(func: F) -> Self
54 where
55 F: Fn(I) -> O + Send + Sync + 'static,
56 {
57 let func = Arc::new(move |input: I| {
58 let result = func(input);
59 Box::pin(async move { Ok(result) })
60 as Pin<Box<dyn Future<Output = Result<O, LcelError>> + Send>>
61 });
62 Self { func }
63 }
64
65 pub fn new_sync_fallible<F>(func: F) -> Self
69 where
70 F: Fn(I) -> Result<O, LcelError> + Send + Sync + 'static,
71 {
72 let func = Arc::new(move |input: I| {
73 let result = func(input);
74 Box::pin(async move { result })
75 as Pin<Box<dyn Future<Output = Result<O, LcelError>> + Send>>
76 });
77 Self { func }
78 }
79
80 pub fn new_async<F, Fut>(func: F) -> Self
90 where
91 F: Fn(I) -> Fut + Send + Sync + 'static,
92 Fut: Future<Output = Result<O, LcelError>> + Send + 'static,
93 {
94 let func = Arc::new(move |input: I| {
95 let fut = func(input);
96 Box::pin(fut) as Pin<Box<dyn Future<Output = Result<O, LcelError>> + Send>>
97 });
98 Self { func }
99 }
100}
101
102#[async_trait]
103impl<I: Send + Sync + 'static, O: Send + Sync + 'static> Runnable<I, O> for RunnableLambda<I, O> {
104 type Error = LcelError;
105
106 async fn invoke(
107 &self,
108 input: I,
109 _config: Option<RunnableConfig>,
110 ) -> Result<O, LcelError> {
111 (self.func)(input).await
112 }
113
114 }
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use futures_util::StreamExt;
122
123 #[tokio::test]
124 async fn sync_closure_works() {
125 let lambda = RunnableLambda::new_sync(|x: i32| x * 3);
126 let result = lambda.invoke(7, None).await.unwrap();
127 assert_eq!(result, 21);
128 }
129
130 #[tokio::test]
131 async fn sync_fallible_closure_ok() {
132 let lambda = RunnableLambda::new_sync_fallible(|x: i32| {
133 if x > 0 {
134 Ok(x * 2)
135 } else {
136 Err(LcelError::Other("must be positive".to_string()))
137 }
138 });
139 assert_eq!(lambda.invoke(5, None).await.unwrap(), 10);
140 }
141
142 #[tokio::test]
143 async fn sync_fallible_closure_err() {
144 let lambda = RunnableLambda::new_sync_fallible(|x: i32| {
145 if x > 0 {
146 Ok(x * 2)
147 } else {
148 Err(LcelError::Other("must be positive".to_string()))
149 }
150 });
151 let result = lambda.invoke(-1, None).await;
152 assert!(result.is_err());
153 }
154
155 #[tokio::test]
156 async fn async_closure_works() {
157 let lambda = RunnableLambda::new_async(|x: i32| async move {
158 tokio::task::spawn_blocking(move || x + 100).await
159 .map_err(|e| LcelError::Other(e.to_string()))
160 });
161 let result = lambda.invoke(5, None).await.unwrap();
162 assert_eq!(result, 105);
163 }
164
165 #[tokio::test]
166 async fn stream_uses_default() {
167 let lambda = RunnableLambda::new_sync(|x: i32| x + 1);
168 let mut stream = lambda.stream(9, None).await.unwrap();
169 let result = stream.next().await.unwrap().unwrap();
170 assert_eq!(result, 10);
171 assert!(stream.next().await.is_none());
172 }
173
174 #[tokio::test]
175 async fn batch_uses_default() {
176 let lambda = RunnableLambda::new_sync(|x: i32| x * 10);
177 let results = lambda.batch(vec![1, 2, 3], None).await.unwrap();
178 assert_eq!(results, vec![10, 20, 30]);
179 }
180}