1use super::assign::RunnableAssign;
9use super::config::RunnableConfig;
10use super::error::LcelError;
11use super::runnable_trait::Runnable;
12use async_trait::async_trait;
13use futures_util::Stream;
14use serde_json::Value;
15use std::collections::HashMap;
16use std::pin::Pin;
17use std::sync::Arc;
18
19pub struct RunnableParallel<I: Send + Sync + 'static> {
36 steps: Vec<(String, Arc<dyn ParallelStep<I>>)>,
37}
38
39impl<I: Send + Sync + 'static> std::fmt::Debug for RunnableParallel<I> {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 let keys: Vec<&str> = self.steps.iter().map(|(k, _)| k.as_str()).collect();
42 f.debug_struct("RunnableParallel")
43 .field("steps", &keys)
44 .field("input", &std::any::type_name::<I>())
45 .finish()
46 }
47}
48
49impl<I: Clone + Send + Sync + 'static> Default for RunnableParallel<I> {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl<I: Clone + Send + Sync + 'static> RunnableParallel<I> {
56 pub fn new() -> Self {
58 Self { steps: Vec::new() }
59 }
60
61 pub fn with<O, R>(mut self, key: &str, runnable: R) -> Self
66 where
67 O: serde::Serialize + Send + Sync + 'static,
68 R: Runnable<I, O> + Send + Sync + 'static,
69 R::Error: Into<LcelError>,
70 {
71 self.steps.push((
72 key.to_string(),
73 Arc::new(ParallelStepImpl {
74 inner: runnable,
75 serialize: |output: &O| serde_json::to_value(output),
76 _marker: std::marker::PhantomData,
77 }),
78 ));
79 self
80 }
81
82 pub fn len(&self) -> usize {
84 self.steps.len()
85 }
86
87 pub fn is_empty(&self) -> bool {
89 self.steps.is_empty()
90 }
91
92 pub fn assign<O, R>(self, key: &str, runnable: R) -> RunnableSequence<I, HashMap<String, Value>>
117 where
118 I: 'static,
119 O: serde::Serialize + Send + Sync + 'static,
120 R: Runnable<HashMap<String, Value>, O> + Send + Sync + 'static,
121 R::Error: Into<LcelError>,
122 {
123 use super::ext::RunnableExt;
124
125 let assign = RunnableAssign::new().with(key, runnable);
126 self.pipe(assign)
127 }
128}
129
130use super::sequence::RunnableSequence;
131
132#[async_trait]
134trait ParallelStep<I: Send + Sync + 'static>: Send + Sync {
135 async fn invoke(&self, input: I, config: Option<RunnableConfig>) -> Result<Value, LcelError>;
136}
137
138struct ParallelStepImpl<I, O, R>
140where
141 I: Send + Sync + 'static,
142 O: serde::Serialize + Send + Sync + 'static,
143 R: Runnable<I, O>,
144{
145 inner: R,
146 serialize: fn(&O) -> Result<Value, serde_json::Error>,
147 _marker: std::marker::PhantomData<I>,
148}
149
150#[async_trait]
151impl<I, O, R> ParallelStep<I> for ParallelStepImpl<I, O, R>
152where
153 I: Clone + Send + Sync + 'static,
154 O: serde::Serialize + Send + Sync + 'static,
155 R: Runnable<I, O>,
156 R::Error: Into<LcelError>,
157{
158 async fn invoke(&self, input: I, config: Option<RunnableConfig>) -> Result<Value, LcelError> {
159 let result = self.inner.invoke(input, config).await.map_err(Into::into)?;
160 (self.serialize)(&result)
161 .map_err(|e| LcelError::Other(format!("parallel serialization: {}", e)))
162 }
163}
164
165#[async_trait]
166impl<I: Clone + Send + Sync + 'static> Runnable<I, HashMap<String, Value>> for RunnableParallel<I> {
167 type Error = LcelError;
168
169 async fn invoke(
171 &self,
172 input: I,
173 config: Option<RunnableConfig>,
174 ) -> Result<HashMap<String, Value>, LcelError> {
175 let mut handles = Vec::with_capacity(self.steps.len());
176
177 for (key, step) in &self.steps {
178 let key = key.clone();
179 let step = step.clone();
180 let input = input.clone();
181 let config = config.clone();
182
183 let handle = tokio::spawn(async move {
184 let value = step.invoke(input, config).await?;
185 Ok::<(String, Value), LcelError>((key, value))
186 });
187
188 handles.push(handle);
189 }
190
191 let mut results = HashMap::new();
192 for handle in handles {
193 let (k, v) = handle
194 .await
195 .map_err(|e| LcelError::Other(format!("parallel task join error: {}", e)))??;
196 results.insert(k, v);
197 }
198
199 Ok(results)
200 }
201
202 async fn batch(
204 &self,
205 inputs: Vec<I>,
206 config: Option<RunnableConfig>,
207 ) -> Result<Vec<HashMap<String, Value>>, LcelError> {
208 let mut results = Vec::with_capacity(inputs.len());
209 for input in inputs {
210 results.push(self.invoke(input, config.clone()).await?);
211 }
212 Ok(results)
213 }
214
215 async fn stream(
217 &self,
218 input: I,
219 config: Option<RunnableConfig>,
220 ) -> Result<
221 Pin<Box<dyn Stream<Item = Result<HashMap<String, Value>, LcelError>> + Send>>,
222 LcelError,
223 > {
224 let result = self.invoke(input, config).await?;
225 Ok(Box::pin(futures_util::stream::once(
226 async move { Ok(result) },
227 )))
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use crate::RunnableLambda;
235
236 #[tokio::test]
237 async fn parallel_invoke() {
238 let parallel = RunnableParallel::<String>::new()
239 .with("len", RunnableLambda::new_sync(|s: String| s.len() as i64))
240 .with(
241 "upper",
242 RunnableLambda::new_sync(|s: String| s.to_uppercase()),
243 );
244
245 let result = parallel.invoke("hello".to_string(), None).await.unwrap();
246 assert_eq!(
247 result.get("len").unwrap(),
248 &Value::Number(serde_json::Number::from(5))
249 );
250 assert_eq!(
251 result.get("upper").unwrap(),
252 &Value::String("HELLO".to_string())
253 );
254 }
255
256 #[tokio::test]
257 async fn parallel_empty() {
258 let parallel = RunnableParallel::<i32>::new();
259 let result = parallel.invoke(42, None).await.unwrap();
260 assert!(result.is_empty());
261 }
262
263 #[tokio::test]
264 async fn parallel_batch() {
265 let parallel = RunnableParallel::<String>::new()
266 .with("len", RunnableLambda::new_sync(|s: String| s.len() as i64));
267
268 let results = parallel
269 .batch(vec!["hi".to_string(), "hello".to_string()], None)
270 .await
271 .unwrap();
272 assert_eq!(results.len(), 2);
273 assert_eq!(
274 results[0].get("len").unwrap(),
275 &Value::Number(serde_json::Number::from(2))
276 );
277 assert_eq!(
278 results[1].get("len").unwrap(),
279 &Value::Number(serde_json::Number::from(5))
280 );
281 }
282
283 #[tokio::test]
284 async fn parallel_assign_adds_field() {
285 let chain = RunnableParallel::<String>::new()
286 .with("len", RunnableLambda::new_sync(|s: String| s.len() as i64))
287 .assign(
288 "upper",
289 RunnableLambda::new_sync(|m: HashMap<String, Value>| {
290 m.get("len")
292 .and_then(|v| v.as_i64())
293 .map(|n| format!("length={}", n))
294 .unwrap_or_default()
295 }),
296 );
297
298 let result = chain.invoke("hello".to_string(), None).await.unwrap();
299 assert_eq!(
301 result.get("len").unwrap(),
302 &Value::Number(serde_json::Number::from(5))
303 );
304 assert_eq!(
306 result.get("upper").unwrap(),
307 &Value::String("length=5".to_string())
308 );
309 }
310}