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