1use super::any::{into_runnable_any, RunnableAny};
9use super::config::RunnableConfig;
10use super::error::LcelError;
11use super::lambda::RunnableLambda;
12use super::runnable_trait::Runnable;
13use async_trait::async_trait;
14use futures_util::Stream;
15use std::any::Any;
16use std::pin::Pin;
17
18pub struct RunnableBranch<I: Send + Sync + 'static, O: Send + Sync + 'static> {
34 branches: Vec<(Box<dyn RunnableAny>, Box<dyn RunnableAny>)>,
35 default: Box<dyn RunnableAny>,
36 _marker: std::marker::PhantomData<(I, O)>,
37}
38
39impl<I: Send + Sync + 'static, O: Send + Sync + 'static> std::fmt::Debug for RunnableBranch<I, O> {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.debug_struct("RunnableBranch")
42 .field("branches", &self.branches.len())
43 .field("input", &std::any::type_name::<I>())
44 .field("output", &std::any::type_name::<O>())
45 .finish()
46 }
47}
48
49impl<I: Clone + Send + Sync + 'static, O: Send + Sync + 'static> RunnableBranch<I, O> {
50 pub fn new<R>(default: R) -> Self
52 where
53 R: Runnable<I, O> + 'static,
54 R::Error: Into<LcelError>,
55 {
56 Self {
57 branches: Vec::new(),
58 default: into_runnable_any(default),
59 _marker: std::marker::PhantomData,
60 }
61 }
62
63 pub fn when<R1, R2>(mut self, condition: R1, branch: R2) -> Self
68 where
69 R1: Runnable<I, bool> + 'static,
70 R1::Error: Into<LcelError>,
71 R2: Runnable<I, O> + 'static,
72 R2::Error: Into<LcelError>,
73 {
74 self.branches
75 .push((into_runnable_any(condition), into_runnable_any(branch)));
76 self
77 }
78
79 pub fn when_fn<F, R2>(self, condition: F, branch: R2) -> Self
83 where
84 F: Fn(&I) -> bool + Send + Sync + 'static,
85 R2: Runnable<I, O> + 'static,
86 R2::Error: Into<LcelError>,
87 {
88 let condition_lambda = RunnableLambda::new_sync(move |input: I| condition(&input));
89 self.when(condition_lambda, branch)
90 }
91
92 pub fn len(&self) -> usize {
94 self.branches.len()
95 }
96
97 pub fn is_empty(&self) -> bool {
99 self.branches.is_empty()
100 }
101}
102
103#[async_trait]
104impl<I: Clone + Send + Sync + 'static, O: Send + Sync + 'static> Runnable<I, O>
105 for RunnableBranch<I, O>
106{
107 type Error = LcelError;
108
109 async fn invoke(&self, input: I, config: Option<RunnableConfig>) -> Result<O, LcelError> {
112 for (condition, branch) in &self.branches {
113 let cond_input: Box<dyn Any + Send> = Box::new(input.clone());
115 let cond_result = condition.invoke_any(cond_input, config.clone()).await?;
116 let matches: bool = cond_result.downcast::<bool>().map(|b| *b).map_err(|_| {
117 LcelError::TypeMismatch("branch condition must return bool".to_string())
118 })?;
119
120 if matches {
121 let branch_input: Box<dyn Any + Send> = Box::new(input);
122 let result = branch.invoke_any(branch_input, config).await?;
123 return result.downcast::<O>().map(|b| *b).map_err(|_| {
124 LcelError::TypeMismatch(format!(
125 "branch output downcast: expected {}",
126 std::any::type_name::<O>()
127 ))
128 });
129 }
130 }
131
132 let default_input: Box<dyn Any + Send> = Box::new(input);
134 let result = self.default.invoke_any(default_input, config).await?;
135 result.downcast::<O>().map(|b| *b).map_err(|_| {
136 LcelError::TypeMismatch(format!(
137 "branch default output downcast: expected {}",
138 std::any::type_name::<O>()
139 ))
140 })
141 }
142
143 async fn stream(
145 &self,
146 input: I,
147 config: Option<RunnableConfig>,
148 ) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
149 let result = self.invoke(input, config).await?;
150 Ok(Box::pin(futures_util::stream::once(
151 async move { Ok(result) },
152 )))
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use crate::RunnableConfig;
160 use async_trait::async_trait;
161 use futures_util::StreamExt;
162
163 struct EchoDefault;
164
165 #[async_trait]
166 impl Runnable<String, String> for EchoDefault {
167 type Error = std::convert::Infallible;
168
169 async fn invoke(
170 &self,
171 input: String,
172 _config: Option<RunnableConfig>,
173 ) -> Result<String, Self::Error> {
174 Ok(format!("default: {}", input))
175 }
176 }
177
178 struct LongHandler;
179
180 #[async_trait]
181 impl Runnable<String, String> for LongHandler {
182 type Error = std::convert::Infallible;
183
184 async fn invoke(
185 &self,
186 input: String,
187 _config: Option<RunnableConfig>,
188 ) -> Result<String, Self::Error> {
189 Ok(format!("long: {}", input))
190 }
191 }
192
193 #[tokio::test]
194 async fn branch_matches_condition() {
195 let branch =
196 RunnableBranch::new(EchoDefault).when_fn(|input: &String| input.len() > 5, LongHandler);
197
198 let result = branch
199 .invoke("hello world".to_string(), None)
200 .await
201 .unwrap();
202 assert_eq!(result, "long: hello world");
203 }
204
205 #[tokio::test]
206 async fn branch_falls_to_default() {
207 let branch = RunnableBranch::new(EchoDefault)
208 .when_fn(|input: &String| input.len() > 100, LongHandler);
209
210 let result = branch.invoke("hi".to_string(), None).await.unwrap();
211 assert_eq!(result, "default: hi");
212 }
213
214 #[tokio::test]
215 async fn branch_first_match_wins() {
216 let branch = RunnableBranch::new(EchoDefault)
217 .when_fn(
218 |input: &String| input.starts_with('h'),
219 RunnableLambda::new_sync(|s: String| format!("starts-h: {}", s)),
220 )
221 .when_fn(
222 |input: &String| input.len() > 3,
223 RunnableLambda::new_sync(|s: String| format!("long: {}", s)),
224 );
225
226 let result = branch.invoke("hello".to_string(), None).await.unwrap();
228 assert_eq!(result, "starts-h: hello");
229 }
230
231 #[tokio::test]
232 async fn branch_stream_works() {
233 let branch =
234 RunnableBranch::new(EchoDefault).when_fn(|input: &String| input.len() > 5, LongHandler);
235
236 let mut stream = branch
237 .stream("hello world".to_string(), None)
238 .await
239 .unwrap();
240 let result = stream.next().await.unwrap().unwrap();
241 assert_eq!(result, "long: hello world");
242 }
243}