1use super::any::{into_runnable_any, RunnableAny};
11use super::config::RunnableConfig;
12use super::error::LcelError;
13use super::runnable_trait::Runnable;
14use async_trait::async_trait;
15use futures_util::{Stream, StreamExt};
16use serde_json::Value;
17use std::any::Any;
18use std::marker::PhantomData;
19use std::pin::Pin;
20
21pub struct RunnableConfigurable<I: Send + Sync + 'static, O: Send + Sync + 'static> {
37 default: Box<dyn RunnableAny>,
38 alternatives: Vec<(String, Box<dyn RunnableAny>)>,
39 which: String,
40 default_key: String,
41 _marker: PhantomData<(I, O)>,
42}
43
44impl<I: Send + Sync + 'static, O: Send + Sync + 'static> std::fmt::Debug
45 for RunnableConfigurable<I, O>
46{
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 let names: Vec<&str> = self.alternatives.iter().map(|(k, _)| k.as_str()).collect();
49 f.debug_struct("RunnableConfigurable")
50 .field("which", &self.which)
51 .field("default_key", &self.default_key)
52 .field("alternatives", &names)
53 .field("input", &std::any::type_name::<I>())
54 .field("output", &std::any::type_name::<O>())
55 .finish()
56 }
57}
58
59impl<I: Send + Sync + 'static, O: Send + Sync + 'static> RunnableConfigurable<I, O> {
60 pub fn new<R>(default: R, which: impl Into<String>, default_key: impl Into<String>) -> Self
69 where
70 R: Runnable<I, O> + 'static,
71 R::Error: Into<LcelError>,
72 {
73 Self {
74 default: into_runnable_any(default),
75 alternatives: Vec::new(),
76 which: which.into(),
77 default_key: default_key.into(),
78 _marker: PhantomData,
79 }
80 }
81
82 pub fn with_alternative<R>(mut self, name: impl Into<String>, runnable: R) -> Self
84 where
85 R: Runnable<I, O> + 'static,
86 R::Error: Into<LcelError>,
87 {
88 self.alternatives.push((name.into(), into_runnable_any(runnable)));
89 self
90 }
91
92 fn resolve(&self, config: &Option<RunnableConfig>) -> &dyn RunnableAny {
94 let selected = config
95 .as_ref()
96 .and_then(|c| c.configurable_value(&self.which))
97 .and_then(|v| v.as_str());
98 match selected {
99 Some(name) if name != self.default_key => self
100 .alternatives
101 .iter()
102 .find(|(k, _)| k == name)
103 .map(|(_, r)| r.as_ref())
104 .unwrap_or(&*self.default),
105 _ => &*self.default,
106 }
107 }
108}
109
110#[async_trait]
111impl<I: Send + Sync + 'static, O: Send + Sync + 'static> Runnable<I, O>
112 for RunnableConfigurable<I, O>
113{
114 type Error = LcelError;
115
116 async fn invoke(&self, input: I, config: Option<RunnableConfig>) -> Result<O, LcelError> {
117 let target = self.resolve(&config);
118 let boxed = target.invoke_any(Box::new(input) as Box<dyn Any + Send>, config).await?;
119 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
120 LcelError::TypeMismatch(format!(
121 "configurable invoke downcast: expected {}",
122 std::any::type_name::<O>()
123 ))
124 })
125 }
126
127 async fn batch(
128 &self,
129 inputs: Vec<I>,
130 config: Option<RunnableConfig>,
131 ) -> Result<Vec<O>, LcelError> {
132 let target = self.resolve(&config);
133 let boxed_inputs: Vec<Box<dyn Any + Send>> = inputs
134 .into_iter()
135 .map(|i| Box::new(i) as Box<dyn Any + Send>)
136 .collect();
137 let results = target.batch_any(boxed_inputs, config).await?;
138 results
139 .into_iter()
140 .map(|boxed| {
141 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
142 LcelError::TypeMismatch(format!(
143 "configurable batch downcast: expected {}",
144 std::any::type_name::<O>()
145 ))
146 })
147 })
148 .collect()
149 }
150
151 async fn stream(
152 &self,
153 input: I,
154 config: Option<RunnableConfig>,
155 ) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
156 let target = self.resolve(&config);
157 let stream = target
158 .stream_any(Box::new(input) as Box<dyn Any + Send>, config)
159 .await?;
160 let output = stream.map(|result| {
161 result.and_then(|boxed| {
162 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
163 LcelError::TypeMismatch(format!(
164 "configurable stream downcast: expected {}",
165 std::any::type_name::<O>()
166 ))
167 })
168 })
169 });
170 Ok(Box::pin(output))
171 }
172
173 async fn transform(
174 &self,
175 input: Pin<Box<dyn Stream<Item = Result<I, LcelError>> + Send>>,
176 config: Option<RunnableConfig>,
177 ) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
178 let target = self.resolve(&config);
179 let any_input: Pin<Box<dyn Stream<Item = Result<Box<dyn Any + Send>, LcelError>> + Send>> =
180 Box::pin(input.map(|result| result.map(|item| Box::new(item) as Box<dyn Any + Send>)));
181
182 let stream = target.transform_any(any_input, config).await?;
183 let output = stream.map(|result| {
184 result.and_then(|boxed| {
185 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
186 LcelError::TypeMismatch(format!(
187 "configurable transform downcast: expected {}",
188 std::any::type_name::<O>()
189 ))
190 })
191 })
192 });
193 Ok(Box::pin(output))
194 }
195}
196
197pub struct RunnableConfigurableFields<I: Send + Sync + 'static, O: Send + Sync + 'static> {
213 inner: Box<dyn RunnableAny>,
214 _marker: PhantomData<(I, O)>,
215}
216
217impl<I: Send + Sync + 'static, O: Send + Sync + 'static> RunnableConfigurableFields<I, O> {
218 pub fn new<R>(runnable: R) -> Self
220 where
221 R: Runnable<I, O> + 'static,
222 R::Error: Into<LcelError>,
223 {
224 Self {
225 inner: into_runnable_any(runnable),
226 _marker: PhantomData,
227 }
228 }
229
230 fn effective_config(&self, config: &Option<RunnableConfig>) -> RunnableConfig {
233 let mut effective = config.clone().unwrap_or_default();
234 let configurables = effective.configurable.clone();
236 for (key, value) in configurables {
237 match (key.as_str(), &value) {
238 ("temperature", Value::Number(n)) if n.as_f64().is_some() => {
239 effective = effective.with_temperature(n.as_f64().unwrap() as f32);
240 }
241 ("max_tokens", Value::Number(n)) if n.as_u64().is_some() => {
242 effective = effective.with_max_tokens(n.as_u64().unwrap() as usize);
243 }
244 (k, v) => effective = effective.with_metadata(k.to_string(), v.clone()),
245 }
246 }
247 effective
248 }
249}
250
251#[async_trait]
252impl<I: Send + Sync + 'static, O: Send + Sync + 'static> Runnable<I, O>
253 for RunnableConfigurableFields<I, O>
254{
255 type Error = LcelError;
256
257 async fn invoke(&self, input: I, config: Option<RunnableConfig>) -> Result<O, LcelError> {
258 let effective = self.effective_config(&config);
259 let boxed = self
260 .inner
261 .invoke_any(Box::new(input) as Box<dyn Any + Send>, Some(effective))
262 .await?;
263 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
264 LcelError::TypeMismatch(format!(
265 "configurable_fields invoke downcast: expected {}",
266 std::any::type_name::<O>()
267 ))
268 })
269 }
270
271 async fn batch(
272 &self,
273 inputs: Vec<I>,
274 config: Option<RunnableConfig>,
275 ) -> Result<Vec<O>, LcelError> {
276 let effective = self.effective_config(&config);
277 let boxed_inputs: Vec<Box<dyn Any + Send>> = inputs
278 .into_iter()
279 .map(|i| Box::new(i) as Box<dyn Any + Send>)
280 .collect();
281 let results = self.inner.batch_any(boxed_inputs, Some(effective)).await?;
282 results
283 .into_iter()
284 .map(|boxed| {
285 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
286 LcelError::TypeMismatch(format!(
287 "configurable_fields batch downcast: expected {}",
288 std::any::type_name::<O>()
289 ))
290 })
291 })
292 .collect()
293 }
294
295 async fn stream(
296 &self,
297 input: I,
298 config: Option<RunnableConfig>,
299 ) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
300 let effective = self.effective_config(&config);
301 let stream = self
302 .inner
303 .stream_any(Box::new(input) as Box<dyn Any + Send>, Some(effective))
304 .await?;
305 let output = stream.map(|result| {
306 result.and_then(|boxed| {
307 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
308 LcelError::TypeMismatch(format!(
309 "configurable_fields stream downcast: expected {}",
310 std::any::type_name::<O>()
311 ))
312 })
313 })
314 });
315 Ok(Box::pin(output))
316 }
317
318 async fn transform(
319 &self,
320 input: Pin<Box<dyn Stream<Item = Result<I, LcelError>> + Send>>,
321 config: Option<RunnableConfig>,
322 ) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
323 let effective = self.effective_config(&config);
324 let any_input: Pin<Box<dyn Stream<Item = Result<Box<dyn Any + Send>, LcelError>> + Send>> =
325 Box::pin(input.map(|result| result.map(|item| Box::new(item) as Box<dyn Any + Send>)));
326
327 let stream = self.inner.transform_any(any_input, Some(effective)).await?;
328 let output = stream.map(|result| {
329 result.and_then(|boxed| {
330 boxed.downcast::<O>().map(|b| *b).map_err(|_| {
331 LcelError::TypeMismatch(format!(
332 "configurable_fields transform downcast: expected {}",
333 std::any::type_name::<O>()
334 ))
335 })
336 })
337 });
338 Ok(Box::pin(output))
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345 use crate::RunnableExt;
346 use crate::RunnableLambda;
347
348 #[tokio::test]
349 async fn routes_to_alternative() {
350 let chain = RunnableLambda::new_sync(|s: String| format!("default:{s}"))
351 .configurable_alternatives(
352 "which",
353 "default",
354 vec![("alt", RunnableLambda::new_sync(|s: String| format!("alt:{s}")))],
355 );
356 let cfg = RunnableConfig::new().with_configurable("which", serde_json::json!("alt"));
357 assert_eq!(chain.invoke("x".to_string(), Some(cfg)).await.unwrap(), "alt:x");
358 }
359
360 #[tokio::test]
361 async fn defaults_when_no_selector() {
362 let chain = RunnableLambda::new_sync(|s: String| format!("default:{s}"))
363 .configurable_alternatives(
364 "which",
365 "default",
366 vec![(
367 "alt",
368 RunnableLambda::new_sync(|s: String| format!("alt:{s}")),
369 )],
370 );
371 assert_eq!(chain.invoke("x".to_string(), None).await.unwrap(), "default:x");
373 let cfg = RunnableConfig::new().with_configurable("which", serde_json::json!("default"));
374 assert_eq!(chain.invoke("x".to_string(), Some(cfg)).await.unwrap(), "default:x");
375 let cfg = RunnableConfig::new().with_configurable("which", serde_json::json!("nope"));
377 assert_eq!(chain.invoke("x".to_string(), Some(cfg)).await.unwrap(), "default:x");
378 }
379
380 struct TemperatureProbe;
382
383 #[async_trait]
384 impl Runnable<(), String> for TemperatureProbe {
385 type Error = LcelError;
386
387 async fn invoke(
388 &self,
389 _input: (),
390 config: Option<RunnableConfig>,
391 ) -> Result<String, LcelError> {
392 Ok(format!(
393 "temp={:?}",
394 config.as_ref().and_then(|c| c.temperature)
395 ))
396 }
397 }
398
399 #[tokio::test]
400 async fn configurable_fields_promotes_temperature() {
401 let wrapped = RunnableConfigurableFields::<(), String>::new(TemperatureProbe);
402 let cfg = RunnableConfig::new().with_configurable("temperature", serde_json::json!(0.5));
403 let out = wrapped.invoke((), Some(cfg)).await.unwrap();
404 assert_eq!(out, "temp=Some(0.5)", "temperature 应被提升为 typed config 字段");
405 }
406
407 #[tokio::test]
408 async fn configurable_fields_promotes_max_tokens() {
409 let wrapped = RunnableConfigurableFields::<(), String>::new(TemperatureProbe);
410 let cfg = RunnableConfig::new().with_configurable("max_tokens", serde_json::json!(128));
411 let out = wrapped.invoke((), Some(cfg)).await.unwrap();
412 assert_eq!(out, "temp=None"); }
414
415 #[tokio::test]
416 async fn configurable_fields_other_keys_go_to_metadata() {
417 struct MetadataProbe;
418
419 #[async_trait]
420 impl Runnable<(), String> for MetadataProbe {
421 type Error = LcelError;
422
423 async fn invoke(
424 &self,
425 _input: (),
426 config: Option<RunnableConfig>,
427 ) -> Result<String, LcelError> {
428 let cfg = config.unwrap_or_default();
429 Ok(cfg
430 .metadata
431 .get("provider")
432 .cloned()
433 .unwrap_or_default()
434 .to_string())
435 }
436 }
437
438 let wrapped = RunnableConfigurableFields::<(), String>::new(MetadataProbe);
439 let cfg = RunnableConfig::new().with_configurable("provider", serde_json::json!("x"));
440 let out = wrapped.invoke((), Some(cfg)).await.unwrap();
441 assert_eq!(out, "\"x\"", "未知 configurable 键应进 metadata");
442 }
443}