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(&self, input: I, config: Option<RunnableConfig>) -> Result<O, LcelError> {
76 let boxed_input = Box::new(input.clone()) as Box<dyn Any + Send>;
78 match self.primary.invoke_any(boxed_input, config.clone()).await {
79 Ok(result) => result.downcast::<O>().map(|b| *b).map_err(|_| {
80 LcelError::TypeMismatch(format!(
81 "fallback primary output downcast: expected {}",
82 std::any::type_name::<O>()
83 ))
84 }),
85 Err(primary_error) => {
86 for fallback in &self.fallbacks {
88 let boxed_input = Box::new(input.clone()) as Box<dyn Any + Send>;
89 match fallback.invoke_any(boxed_input, config.clone()).await {
90 Ok(result) => {
91 return result.downcast::<O>().map(|b| *b).map_err(|_| {
92 LcelError::TypeMismatch(format!(
93 "fallback output downcast: expected {}",
94 std::any::type_name::<O>()
95 ))
96 });
97 }
98 Err(e) => {
99 log::warn!("fallback 调用失败(继续尝试下一个): {e}");
100 continue;
101 }
102 }
103 }
104 log::warn!("primary 与全部 fallback 均失败,返回 primary 的错误");
106 Err(primary_error)
107 }
108 }
109 }
110}
111
112#[async_trait]
113impl<I: Clone + Send + Sync + 'static, O: Send + Sync + 'static> Runnable<I, O>
114 for RunnableWithFallbacks<I, O>
115{
116 type Error = LcelError;
117
118 async fn invoke(&self, input: I, config: Option<RunnableConfig>) -> Result<O, LcelError> {
120 self.try_all(input, config).await
121 }
122
123 async fn stream(
126 &self,
127 input: I,
128 config: Option<RunnableConfig>,
129 ) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
130 let boxed_input = Box::new(input.clone()) as Box<dyn Any + Send>;
132 match self.primary.stream_any(boxed_input, config.clone()).await {
133 Ok(stream) => {
134 let output_stream = stream.map(|result| {
135 result.and_then(|boxed| {
136 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
137 LcelError::TypeMismatch(format!(
138 "fallback stream downcast: expected {}",
139 std::any::type_name::<O>()
140 ))
141 })
142 })
143 });
144 return Ok(Box::pin(output_stream));
145 }
146 Err(primary_error) => {
147 for fallback in &self.fallbacks {
149 let boxed_input = Box::new(input.clone()) as Box<dyn Any + Send>;
150 match fallback.stream_any(boxed_input, config.clone()).await {
151 Ok(stream) => {
152 let output_stream = stream.map(|result| {
153 result.and_then(|boxed| {
154 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
155 LcelError::TypeMismatch(format!(
156 "fallback stream downcast: expected {}",
157 std::any::type_name::<O>()
158 ))
159 })
160 })
161 });
162 return Ok(Box::pin(output_stream));
163 }
164 Err(e) => {
165 log::warn!("fallback 流式调用失败(继续尝试下一个): {e}");
166 continue;
167 }
168 }
169 }
170 log::warn!("primary 与全部 fallback 流式调用均失败,返回 primary 的错误");
171 Err(primary_error)
172 }
173 }
174 }
175
176 async fn batch(
178 &self,
179 inputs: Vec<I>,
180 config: Option<RunnableConfig>,
181 ) -> Result<Vec<O>, LcelError> {
182 let mut results = Vec::with_capacity(inputs.len());
183 for input in inputs {
184 results.push(self.invoke(input, config.clone()).await?);
185 }
186 Ok(results)
187 }
188
189 async fn transform(
191 &self,
192 input: Pin<Box<dyn Stream<Item = Result<I, LcelError>> + Send>>,
193 config: Option<RunnableConfig>,
194 ) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
195 let mut items = Vec::new();
199 let mut input = input;
200 while let Some(item) = input.next().await {
201 items.push(item?);
202 }
203 let mut results = Vec::with_capacity(items.len());
204 for item in items {
205 results.push(self.invoke(item, config.clone()).await);
206 }
207 Ok(Box::pin(futures_util::stream::iter(results)))
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::runnables::{RunnableExt, RunnableLambda};
215 use futures_util::StreamExt;
216
217 #[tokio::test]
218 async fn primary_succeeds_no_fallback() {
219 let primary = RunnableLambda::new_sync(|x: i32| x * 2);
220 let fallback = RunnableLambda::new_sync(|x: i32| x * 3);
221
222 let with_fallbacks = primary.with_fallbacks(vec![fallback]);
223 let result = with_fallbacks.invoke(5, None).await.unwrap();
224 assert_eq!(result, 10); }
226
227 #[tokio::test]
228 async fn primary_fails_fallback_succeeds() {
229 let primary = RunnableLambda::new_sync_fallible(|_x: i32| -> Result<i32, LcelError> {
230 Err(LcelError::Other("primary failed".to_string()))
231 });
232 let fallback = RunnableLambda::new_sync(|x: i32| x * 3);
233
234 let with_fallbacks = primary.with_fallbacks(vec![fallback]);
235 let result = with_fallbacks.invoke(5, None).await.unwrap();
236 assert_eq!(result, 15); }
238
239 #[tokio::test]
240 async fn all_fail_returns_primary_error() {
241 let primary = RunnableLambda::new_sync_fallible(|_x: i32| -> Result<i32, LcelError> {
242 Err(LcelError::Provider("openai timeout".to_string()))
243 });
244 let fallback = RunnableLambda::new_sync_fallible(|_x: i32| -> Result<i32, LcelError> {
245 Err(LcelError::Provider("anthropic timeout".to_string()))
246 });
247
248 let with_fallbacks = primary.with_fallbacks(vec![fallback]);
249 let err = with_fallbacks.invoke(5, None).await.unwrap_err();
250 assert!(matches!(err, LcelError::Provider(msg) if msg.contains("openai")));
252 }
253
254 #[tokio::test]
255 async fn multiple_fallbacks_first_wins() {
256 let primary = RunnableLambda::new_sync_fallible(|_x: i32| -> Result<i32, LcelError> {
257 Err(LcelError::Other("primary failed".to_string()))
258 });
259 let fb1 = RunnableLambda::new_sync(|x: i32| x + 100);
260 let fb2 = RunnableLambda::new_sync(|x: i32| x + 200);
261
262 let with_fallbacks = primary.with_fallbacks(vec![fb1, fb2]);
263 let result = with_fallbacks.invoke(5, None).await.unwrap();
264 assert_eq!(result, 105); }
266
267 #[tokio::test]
268 async fn first_fallback_fails_second_succeeds() {
269 let primary = RunnableLambda::new_sync_fallible(|_x: i32| -> Result<i32, LcelError> {
270 Err(LcelError::Other("primary failed".to_string()))
271 });
272 let fb1 = RunnableLambda::new_sync_fallible(|_x: i32| -> Result<i32, LcelError> {
273 Err(LcelError::Other("fb1 failed".to_string()))
274 });
275 let fb2 = RunnableLambda::new_sync(|x: i32| x + 200);
276
277 let with_fallbacks = primary.with_fallbacks(vec![fb1, fb2]);
278 let result = with_fallbacks.invoke(5, None).await.unwrap();
279 assert_eq!(result, 205); }
281
282 #[tokio::test]
283 async fn stream_primary_succeeds() {
284 let primary = RunnableLambda::new_sync(|x: i32| x * 2);
285 let with_fallbacks = primary.with_fallbacks(Vec::<RunnableLambda<i32, i32>>::new());
286
287 let mut stream = with_fallbacks.stream(5, None).await.unwrap();
288 let result = stream.next().await.unwrap().unwrap();
289 assert_eq!(result, 10);
290 }
291
292 #[tokio::test]
293 async fn stream_primary_fails_fallback_succeeds() {
294 let primary = RunnableLambda::new_sync_fallible(|_x: i32| -> Result<i32, LcelError> {
295 Err(LcelError::Other("primary failed".to_string()))
296 });
297 let fallback = RunnableLambda::new_sync(|x: i32| x * 3);
298
299 let with_fallbacks = primary.with_fallbacks(vec![fallback]);
300 let mut stream = with_fallbacks.stream(5, None).await.unwrap();
301 let result = stream.next().await.unwrap().unwrap();
302 assert_eq!(result, 15);
303 }
304
305 #[tokio::test]
306 async fn batch_works() {
307 let primary = RunnableLambda::new_sync(|x: i32| x * 2);
308 let fallback = RunnableLambda::new_sync(|x: i32| x * 3);
309
310 let with_fallbacks = primary.with_fallbacks(vec![fallback]);
311 let results = with_fallbacks.batch(vec![1, 2, 3], None).await.unwrap();
312 assert_eq!(results, vec![2, 4, 6]); }
314
315 #[tokio::test]
316 async fn transform_works() {
317 let primary = RunnableLambda::new_sync(|x: i32| x * 2);
318 let with_fallbacks = primary.with_fallbacks(Vec::<RunnableLambda<i32, i32>>::new());
319
320 let input = Box::pin(futures_util::stream::iter(vec![
321 Ok(1i32),
322 Ok(2i32),
323 Ok(3i32),
324 ])) as Pin<Box<dyn Stream<Item = Result<i32, LcelError>> + Send>>;
325
326 let mut output = with_fallbacks.transform(input, None).await.unwrap();
327 let mut results = Vec::new();
329 while let Some(item) = output.next().await {
330 results.push(item.unwrap());
331 }
332 assert_eq!(results, vec![2, 4, 6]);
333 }
334
335 #[tokio::test]
336 async fn transform_fallback_per_item() {
337 let primary = RunnableLambda::new_sync_fallible(|_x: i32| -> Result<i32, LcelError> {
339 Err(LcelError::Other("primary failed".to_string()))
340 });
341 let fallback = RunnableLambda::new_sync(|x: i32| x * 3);
342
343 let with_fallbacks = primary.with_fallbacks(vec![fallback]);
344 let input = Box::pin(futures_util::stream::iter(vec![
345 Ok(1i32),
346 Ok(2i32),
347 ])) as Pin<Box<dyn Stream<Item = Result<i32, LcelError>> + Send>>;
348
349 let mut output = with_fallbacks.transform(input, None).await.unwrap();
350 let mut results = Vec::new();
351 while let Some(item) = output.next().await {
352 results.push(item.unwrap());
353 }
354 assert_eq!(results, vec![3, 6]);
355 }
356
357 #[tokio::test]
358 async fn debug_format() {
359 let primary = RunnableLambda::new_sync(|x: i32| x);
360 let with_fallbacks = primary.with_fallbacks(Vec::<RunnableLambda<i32, i32>>::new());
361 let debug_str = format!("{:?}", with_fallbacks);
362 assert!(debug_str.contains("RunnableWithFallbacks"));
363 }
364
365 #[tokio::test]
366 async fn pipe_with_fallbacks() {
367 let primary = RunnableLambda::new_sync_fallible(|_x: i32| -> Result<i32, LcelError> {
368 Err(LcelError::Other("fail".to_string()))
369 });
370 let fallback = RunnableLambda::new_sync(|x: i32| x + 10);
371
372 let double = RunnableLambda::new_sync(|x: i32| x * 2);
374 let chain = primary.with_fallbacks(vec![fallback]).pipe(double);
375
376 let result = chain.invoke(5, None).await.unwrap();
377 assert_eq!(result, 30); }
379}