1use super::any::{into_runnable_any, RunnableAny};
18use super::config::RunnableConfig;
19use super::error::LcelError;
20use super::runnable_trait::Runnable;
21use async_trait::async_trait;
22use futures_util::{Stream, StreamExt};
23use std::any::Any;
24use std::marker::PhantomData;
25use std::pin::Pin;
26
27pub struct RunnableWithFallbacks<I: Send + Sync + 'static, O: Send + Sync + 'static> {
37 primary: Box<dyn RunnableAny>,
38 fallbacks: Vec<Box<dyn RunnableAny>>,
39 _marker: PhantomData<(I, O)>,
40}
41
42impl<I: Send + Sync + 'static, O: Send + Sync + 'static> std::fmt::Debug
43 for RunnableWithFallbacks<I, O>
44{
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.debug_struct("RunnableWithFallbacks")
47 .field("fallbacks", &self.fallbacks.len())
48 .field("input", &std::any::type_name::<I>())
49 .field("output", &std::any::type_name::<O>())
50 .finish()
51 }
52}
53
54impl<I: Clone + Send + Sync + 'static, O: Send + Sync + 'static> RunnableWithFallbacks<I, O> {
55 pub fn new<R>(primary: R, fallbacks: Vec<Box<dyn RunnableAny>>) -> Self
57 where
58 R: Runnable<I, O> + 'static,
59 R::Error: Into<LcelError>,
60 {
61 Self {
62 primary: into_runnable_any(primary),
63 fallbacks,
64 _marker: PhantomData,
65 }
66 }
67
68 pub fn fallback_count(&self) -> usize {
70 self.fallbacks.len()
71 }
72
73 async fn try_all(
76 &self,
77 input: I,
78 config: Option<RunnableConfig>,
79 ) -> Result<O, LcelError> {
80 let boxed_input = Box::new(input.clone()) as Box<dyn Any + Send>;
82 match self.primary.invoke_any(boxed_input, config.clone()).await {
83 Ok(result) => {
84 result
85 .downcast::<O>()
86 .map(|b| *b)
87 .map_err(|_| LcelError::TypeMismatch(format!(
88 "fallback primary output downcast: expected {}",
89 std::any::type_name::<O>()
90 )))
91 }
92 Err(primary_error) => {
93 for fallback in &self.fallbacks {
95 let boxed_input = Box::new(input.clone()) as Box<dyn Any + Send>;
96 match fallback.invoke_any(boxed_input, config.clone()).await {
97 Ok(result) => {
98 return result
99 .downcast::<O>()
100 .map(|b| *b)
101 .map_err(|_| LcelError::TypeMismatch(format!(
102 "fallback output downcast: expected {}",
103 std::any::type_name::<O>()
104 )));
105 }
106 Err(_) => continue,
107 }
108 }
109 Err(primary_error)
111 }
112 }
113 }
114}
115
116#[async_trait]
117impl<I: Clone + Send + Sync + 'static, O: Send + Sync + 'static> Runnable<I, O>
118 for RunnableWithFallbacks<I, O>
119{
120 type Error = LcelError;
121
122 async fn invoke(
124 &self,
125 input: I,
126 config: Option<RunnableConfig>,
127 ) -> Result<O, LcelError> {
128 self.try_all(input, config).await
129 }
130
131 async fn stream(
134 &self,
135 input: I,
136 config: Option<RunnableConfig>,
137 ) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
138 let boxed_input = Box::new(input.clone()) as Box<dyn Any + Send>;
140 match self.primary.stream_any(boxed_input, config.clone()).await {
141 Ok(stream) => {
142 let output_stream = stream.map(|result| {
143 result.and_then(|boxed| {
144 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
145 LcelError::TypeMismatch(format!(
146 "fallback stream downcast: expected {}",
147 std::any::type_name::<O>()
148 ))
149 })
150 })
151 });
152 return Ok(Box::pin(output_stream));
153 }
154 Err(primary_error) => {
155 for fallback in &self.fallbacks {
157 let boxed_input = Box::new(input.clone()) as Box<dyn Any + Send>;
158 match fallback.stream_any(boxed_input, config.clone()).await {
159 Ok(stream) => {
160 let output_stream = stream.map(|result| {
161 result.and_then(|boxed| {
162 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
163 LcelError::TypeMismatch(format!(
164 "fallback stream downcast: expected {}",
165 std::any::type_name::<O>()
166 ))
167 })
168 })
169 });
170 return Ok(Box::pin(output_stream));
171 }
172 Err(_) => continue,
173 }
174 }
175 Err(primary_error)
176 }
177 }
178 }
179
180 async fn batch(
182 &self,
183 inputs: Vec<I>,
184 config: Option<RunnableConfig>,
185 ) -> Result<Vec<O>, LcelError> {
186 let mut results = Vec::with_capacity(inputs.len());
187 for input in inputs {
188 results.push(self.invoke(input, config.clone()).await?);
189 }
190 Ok(results)
191 }
192
193 async fn transform(
195 &self,
196 input: Pin<Box<dyn Stream<Item = Result<I, LcelError>> + Send>>,
197 config: Option<RunnableConfig>,
198 ) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
199 let mut items = Vec::new();
201 let mut input = input;
202 while let Some(item) = input.next().await {
203 items.push(item?);
204 }
205
206 if let Some(last) = items.into_iter().last() {
207 let result = self.invoke(last, config).await?;
208 Ok(Box::pin(futures_util::stream::once(async move { Ok(result) })))
209 } else {
210 Ok(Box::pin(futures_util::stream::empty()))
211 }
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::runnables::{RunnableExt, RunnableLambda};
219 use futures_util::StreamExt;
220
221 #[tokio::test]
222 async fn primary_succeeds_no_fallback() {
223 let primary = RunnableLambda::new_sync(|x: i32| x * 2);
224 let fallback = RunnableLambda::new_sync(|x: i32| x * 3);
225
226 let with_fallbacks = primary.with_fallbacks(vec![fallback]);
227 let result = with_fallbacks.invoke(5, None).await.unwrap();
228 assert_eq!(result, 10); }
230
231 #[tokio::test]
232 async fn primary_fails_fallback_succeeds() {
233 let primary = RunnableLambda::new_sync_fallible(|x: i32| -> Result<i32, LcelError> {
234 Err(LcelError::Other("primary failed".to_string()))
235 });
236 let fallback = RunnableLambda::new_sync(|x: i32| x * 3);
237
238 let with_fallbacks = primary.with_fallbacks(vec![fallback]);
239 let result = with_fallbacks.invoke(5, None).await.unwrap();
240 assert_eq!(result, 15); }
242
243 #[tokio::test]
244 async fn all_fail_returns_primary_error() {
245 let primary = RunnableLambda::new_sync_fallible(|x: i32| -> Result<i32, LcelError> {
246 Err(LcelError::Provider("openai timeout".to_string()))
247 });
248 let fallback = RunnableLambda::new_sync_fallible(|x: i32| -> Result<i32, LcelError> {
249 Err(LcelError::Provider("anthropic timeout".to_string()))
250 });
251
252 let with_fallbacks = primary.with_fallbacks(vec![fallback]);
253 let err = with_fallbacks.invoke(5, None).await.unwrap_err();
254 assert!(matches!(err, LcelError::Provider(msg) if msg.contains("openai")));
256 }
257
258 #[tokio::test]
259 async fn multiple_fallbacks_first_wins() {
260 let primary = RunnableLambda::new_sync_fallible(|x: i32| -> Result<i32, LcelError> {
261 Err(LcelError::Other("primary failed".to_string()))
262 });
263 let fb1 = RunnableLambda::new_sync(|x: i32| x + 100);
264 let fb2 = RunnableLambda::new_sync(|x: i32| x + 200);
265
266 let with_fallbacks = primary.with_fallbacks(vec![fb1, fb2]);
267 let result = with_fallbacks.invoke(5, None).await.unwrap();
268 assert_eq!(result, 105); }
270
271 #[tokio::test]
272 async fn first_fallback_fails_second_succeeds() {
273 let primary = RunnableLambda::new_sync_fallible(|x: i32| -> Result<i32, LcelError> {
274 Err(LcelError::Other("primary failed".to_string()))
275 });
276 let fb1 = RunnableLambda::new_sync_fallible(|x: i32| -> Result<i32, LcelError> {
277 Err(LcelError::Other("fb1 failed".to_string()))
278 });
279 let fb2 = RunnableLambda::new_sync(|x: i32| x + 200);
280
281 let with_fallbacks = primary.with_fallbacks(vec![fb1, fb2]);
282 let result = with_fallbacks.invoke(5, None).await.unwrap();
283 assert_eq!(result, 205); }
285
286 #[tokio::test]
287 async fn stream_primary_succeeds() {
288 let primary = RunnableLambda::new_sync(|x: i32| x * 2);
289 let with_fallbacks = primary.with_fallbacks(Vec::<RunnableLambda<i32, i32>>::new());
290
291 let mut stream = with_fallbacks.stream(5, None).await.unwrap();
292 let result = stream.next().await.unwrap().unwrap();
293 assert_eq!(result, 10);
294 }
295
296 #[tokio::test]
297 async fn stream_primary_fails_fallback_succeeds() {
298 let primary = RunnableLambda::new_sync_fallible(|x: i32| -> Result<i32, LcelError> {
299 Err(LcelError::Other("primary failed".to_string()))
300 });
301 let fallback = RunnableLambda::new_sync(|x: i32| x * 3);
302
303 let with_fallbacks = primary.with_fallbacks(vec![fallback]);
304 let mut stream = with_fallbacks.stream(5, None).await.unwrap();
305 let result = stream.next().await.unwrap().unwrap();
306 assert_eq!(result, 15);
307 }
308
309 #[tokio::test]
310 async fn batch_works() {
311 let primary = RunnableLambda::new_sync(|x: i32| x * 2);
312 let fallback = RunnableLambda::new_sync(|x: i32| x * 3);
313
314 let with_fallbacks = primary.with_fallbacks(vec![fallback]);
315 let results = with_fallbacks.batch(vec![1, 2, 3], None).await.unwrap();
316 assert_eq!(results, vec![2, 4, 6]); }
318
319 #[tokio::test]
320 async fn transform_works() {
321 let primary = RunnableLambda::new_sync(|x: i32| x * 2);
322 let with_fallbacks = primary.with_fallbacks(Vec::<RunnableLambda<i32, i32>>::new());
323
324 let input = Box::pin(futures_util::stream::iter(vec![
325 Ok(1i32),
326 Ok(2i32),
327 Ok(3i32),
328 ])) as Pin<Box<dyn Stream<Item = Result<i32, LcelError>> + Send>>;
329
330 let mut output = with_fallbacks.transform(input, None).await.unwrap();
331 let result = output.next().await.unwrap().unwrap();
333 assert_eq!(result, 6);
334 }
335
336 #[tokio::test]
337 async fn debug_format() {
338 let primary = RunnableLambda::new_sync(|x: i32| x);
339 let with_fallbacks = primary.with_fallbacks(Vec::<RunnableLambda<i32, i32>>::new());
340 let debug_str = format!("{:?}", with_fallbacks);
341 assert!(debug_str.contains("RunnableWithFallbacks"));
342 }
343
344 #[tokio::test]
345 async fn pipe_with_fallbacks() {
346 let primary = RunnableLambda::new_sync_fallible(|x: i32| -> Result<i32, LcelError> {
347 Err(LcelError::Other("fail".to_string()))
348 });
349 let fallback = RunnableLambda::new_sync(|x: i32| x + 10);
350
351 let double = RunnableLambda::new_sync(|x: i32| x * 2);
353 let chain = primary.with_fallbacks(vec![fallback]).pipe(double);
354
355 let result = chain.invoke(5, None).await.unwrap();
356 assert_eq!(result, 30); }
358}