Skip to main content

lc_core/runnables/
runnable_trait.rs

1// src/core/runnables/runnable_trait.rs
2//! Runnable trait - foundation of LCEL (LangChain Expression Language).
3//!
4//! Every LangChain component implements Runnable, enabling
5//! chaining, composition, and interoperability.
6
7use super::RunnableConfig;
8use async_trait::async_trait;
9use futures_util::Stream;
10use std::pin::Pin;
11
12/// Base trait for all LangChain components.
13///
14/// This trait defines the core interface every component must implement:
15/// - Single execution via `invoke`
16/// - Batch processing via `batch`
17/// - Streaming output via `stream`
18/// - Stream-to-stream transformation via `transform`
19///
20/// # Example
21/// ```no_run
22/// use lc_core::runnables::Runnable;
23/// use lc_core::runnables::RunnableConfig;
24/// use async_trait::async_trait;
25///
26/// // Define a simple Runnable: add one
27/// struct AddOne;
28///
29/// #[async_trait]
30/// impl Runnable<i32, i32> for AddOne {
31///     type Error = std::convert::Infallible;
32///
33///     async fn invoke(&self, input: i32, _config: Option<RunnableConfig>) -> Result<i32, Self::Error> {
34///         Ok(input + 1)
35///     }
36/// }
37/// ```
38#[async_trait]
39pub trait Runnable<Input: Send + Sync + 'static, Output: Send + Sync + 'static>:
40    Send + Sync
41{
42    /// Error type.
43    type Error: std::error::Error + Send + Sync + 'static;
44
45    /// Transforms single input to output.
46    ///
47    /// This is the primary method for single execution.
48    ///
49    /// # Arguments
50    /// * `input` - Input to process.
51    /// * `config` - Optional execution configuration.
52    ///
53    /// # Returns
54    /// Execution result.
55    async fn invoke(
56        &self,
57        input: Input,
58        config: Option<RunnableConfig>,
59    ) -> Result<Output, Self::Error>;
60
61    /// Batch processing - transforms multiple inputs to outputs.
62    ///
63    /// Default implementation processes inputs concurrently with a bounded
64    /// concurrency: `config.max_concurrency` items run at once (defaults to
65    /// all inputs), and results are returned in input order regardless of
66    /// completion order (`buffered`, not `buffer_unordered`). Override for
67    /// provider-level batch optimization.
68    ///
69    /// # Arguments
70    /// * `inputs` - Input vector.
71    /// * `config` - Optional batch configuration.
72    ///
73    /// # Returns
74    /// Result vector, ordered as the inputs.
75    async fn batch(
76        &self,
77        inputs: Vec<Input>,
78        config: Option<RunnableConfig>,
79    ) -> Result<Vec<Output>, Self::Error> {
80        use futures_util::StreamExt;
81
82        // Concurrency cap: `max_concurrency`, clamped to at least 1 so an
83        // explicit `Some(0)` (or an empty input list) cannot panic `buffered`.
84        let limit = config
85            .as_ref()
86            .and_then(|c| c.max_concurrency)
87            .unwrap_or(inputs.len())
88            .max(1);
89
90        let results = futures_util::stream::iter(inputs)
91            .map(|input| {
92                let config = config.clone();
93                async move { self.invoke(input, config).await }
94            })
95            .buffered(limit)
96            .collect::<Vec<Result<_, _>>>()
97            .await;
98
99        // Short-circuit on the first error, preserving input order otherwise.
100        results.into_iter().collect()
101    }
102
103    /// Batch processing that returns results in *completion* order.
104    ///
105    /// Rust counterpart of Python LCEL's `batch_as_completed`: each input is
106    /// driven through the full chain via `invoke` independently, with
107    /// concurrency bounded by `config.max_concurrency` (defaults to all
108    /// inputs). The result is a `Vec<(usize, Output)>` ordered by *completion*
109    /// time, where the `usize` is the original index in `inputs`.
110    ///
111    /// Short-circuits on the first error (like `batch`): if any input fails,
112    /// the error is returned immediately and the remaining results are
113    /// dropped.
114    ///
115    /// # Example
116    ///
117    /// ```rust,ignore
118    /// let results = chain.batch_as_completed(inputs, None).await?;
119    /// // 最快完成的那项在 results[0],其下标标识它在 inputs 里的位置
120    /// for (index, output) in results {
121    ///     println!("inputs[{index}] -> {output}");
122    /// }
123    /// ```
124    async fn batch_as_completed(
125        &self,
126        inputs: Vec<Input>,
127        config: Option<RunnableConfig>,
128    ) -> Result<Vec<(usize, Output)>, Self::Error> {
129        use futures_util::StreamExt;
130
131        let limit = config
132            .as_ref()
133            .and_then(|c| c.max_concurrency)
134            .unwrap_or(inputs.len())
135            .max(1);
136
137        let results = futures_util::stream::iter(inputs.into_iter().enumerate())
138            .map(|(index, input)| {
139                let config = config.clone();
140                async move {
141                    self.invoke(input, config)
142                        .await
143                        .map(|output| (index, output))
144                }
145            })
146            .buffer_unordered(limit)
147            .collect::<Vec<Result<(usize, Output), _>>>()
148            .await;
149
150        // Short-circuit on the first error (in completion order).
151        results.into_iter().collect()
152    }
153
154    /// Streaming output - for real-time responses (LLM, etc).
155    ///
156    /// Enables real-time stream processing of output,
157    /// suitable for chat models, token generation, etc.
158    ///
159    /// # Arguments
160    /// * `input` - Input to process.
161    /// * `config` - Optional configuration.
162    ///
163    /// # Returns
164    /// Output stream.
165    ///
166    /// # Default Implementation
167    /// Wraps invoke result as single-element stream.
168    /// Types supporting true streaming should override.
169    async fn stream(
170        &self,
171        input: Input,
172        config: Option<RunnableConfig>,
173    ) -> Result<Pin<Box<dyn Stream<Item = Result<Output, Self::Error>> + Send>>, Self::Error> {
174        // Default: wrap invoke result as single-element stream
175        // All Runables automatically get stream capability
176        // Types with true streaming (like LLM) should override
177        let result = self.invoke(input, config).await?;
178        let stream = futures_util::stream::once(async move { Ok(result) });
179        Ok(Box::pin(stream))
180    }
181
182    /// Stream-to-stream transformation - the core of LCEL streaming.
183    ///
184    /// Takes an input stream and produces an output stream, enabling
185    /// pipeline streaming without buffering intermediate results.
186    ///
187    /// # Default Implementation
188    /// Drives each input item through `stream` **lazily**: as soon as an input
189    /// item arrives it is immediately run through `stream` and its output
190    /// yielded, before pulling the next input item. This is the LangChain
191    /// default `transform` semantics — downstream receives output incrementally
192    /// instead of waiting for the entire input stream to finish, and an
193    /// infinite/long-lived upstream never accumulates unboundedly in memory.
194    /// A step that overrides `stream` (e.g. an LLM) yields a real token stream
195    /// per item; a step using the default `stream` maps elementwise via
196    /// `invoke`. Components that want aggregation (e.g. incremental parsers)
197    /// should override this method.
198    ///
199    /// # Arguments
200    /// * `input` - Input stream to transform.
201    /// * `config` - Optional execution configuration.
202    ///
203    /// # Returns
204    /// Output stream.
205    async fn transform(
206        &self,
207        input: Pin<Box<dyn Stream<Item = Result<Input, Self::Error>> + Send>>,
208        config: Option<RunnableConfig>,
209    ) -> Result<Pin<Box<dyn Stream<Item = Result<Output, Self::Error>> + Send + '_>>, Self::Error>
210    {
211        // Note: the `+ '_` on the returned `dyn Stream` allows the default lazy
212        // implementation to borrow `&self` across the stream's lifetime. Existing
213        // implementations that return `'static` streams remain valid (they simply
214        // outlive the required bound).
215        use futures_util::StreamExt;
216
217        let config = config.clone();
218        let stream = async_stream::stream! {
219            let mut input = input;
220            loop {
221                let item = match input.next().await {
222                    Some(Ok(item)) => item,
223                    Some(Err(e)) => {
224                        yield Err(e);
225                        return;
226                    }
227                    None => return,
228                };
229                // Run the current item through `stream` and drain it to the
230                // output before pulling the next input item (lazy elementwise).
231                let inner = match self.stream(item, config.clone()).await {
232                    Ok(s) => s,
233                    Err(e) => {
234                        yield Err(e);
235                        return;
236                    }
237                };
238                futures_util::pin_mut!(inner);
239                while let Some(res) = inner.next().await {
240                    yield res;
241                }
242            }
243        };
244        Ok(Box::pin(stream))
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use futures_util::StreamExt;
252
253    struct TestRunnable;
254
255    #[async_trait]
256    impl Runnable<String, String> for TestRunnable {
257        type Error = std::convert::Infallible;
258
259        async fn invoke(
260            &self,
261            input: String,
262            _config: Option<RunnableConfig>,
263        ) -> Result<String, Self::Error> {
264            Ok(format!("processed: {}", input))
265        }
266    }
267
268    #[tokio::test]
269    async fn test_default_stream_returns_single_element() {
270        let runnable = TestRunnable;
271        let mut stream = runnable.stream("test".to_string(), None).await.unwrap();
272
273        let first = stream.next().await;
274        assert!(first.is_some());
275        assert_eq!(first.unwrap().unwrap(), "processed: test");
276
277        let second = stream.next().await;
278        assert!(second.is_none());
279    }
280
281    #[tokio::test]
282    async fn test_invoke_matches_stream_result() {
283        let runnable = TestRunnable;
284
285        let invoke_result = runnable.invoke("hello".to_string(), None).await.unwrap();
286        let mut stream = runnable.stream("hello".to_string(), None).await.unwrap();
287        let stream_result = stream.next().await.unwrap().unwrap();
288
289        assert_eq!(invoke_result, stream_result);
290    }
291
292    #[tokio::test]
293    async fn test_default_transform_maps_elementwise() {
294        let runnable = TestRunnable;
295        let input_stream = Box::pin(futures_util::stream::iter(vec![
296            Ok("first".to_string()),
297            Ok("second".to_string()),
298            Ok("third".to_string()),
299        ]))
300            as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
301
302        let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
303
304        // Default transform maps each item through invoke (elementwise).
305        let mut results = Vec::new();
306        while let Some(item) = output_stream.next().await {
307            results.push(item.unwrap());
308        }
309        assert_eq!(
310            results,
311            vec![
312                "processed: first".to_string(),
313                "processed: second".to_string(),
314                "processed: third".to_string(),
315            ]
316        );
317    }
318
319    #[tokio::test]
320    async fn test_default_transform_empty_input() {
321        let runnable = TestRunnable;
322        let input_stream = Box::pin(futures_util::stream::empty::<
323            Result<String, std::convert::Infallible>,
324        >())
325            as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
326
327        let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
328
329        // Empty input → empty output
330        assert!(output_stream.next().await.is_none());
331    }
332
333    #[tokio::test]
334    async fn test_default_batch_preserves_order() {
335        let runnable = TestRunnable;
336        let results = runnable
337            .batch(
338                vec!["a".to_string(), "b".to_string(), "c".to_string()],
339                None,
340            )
341            .await
342            .unwrap();
343        assert_eq!(
344            results,
345            vec![
346                "processed: a".to_string(),
347                "processed: b".to_string(),
348                "processed: c".to_string(),
349            ]
350        );
351    }
352
353    #[tokio::test]
354    async fn test_default_batch_respects_max_concurrency() {
355        let runnable = TestRunnable;
356        let config = RunnableConfig::new().with_max_concurrency(1);
357        let results = runnable
358            .batch(
359                vec!["x".to_string(), "y".to_string(), "z".to_string()],
360                Some(config),
361            )
362            .await
363            .unwrap();
364        assert_eq!(
365            results,
366            vec![
367                "processed: x".to_string(),
368                "processed: y".to_string(),
369                "processed: z".to_string(),
370            ]
371        );
372    }
373
374    #[tokio::test]
375    async fn test_default_batch_empty_input() {
376        let runnable = TestRunnable;
377        let results = runnable.batch(vec![], None).await.unwrap();
378        assert!(results.is_empty());
379    }
380
381    /// 带延迟的 Runnable:"slow" 明显慢于其他,用于验证完成顺序 ≠ 输入顺序。
382    struct Delayed;
383
384    #[async_trait]
385    impl Runnable<&'static str, usize> for Delayed {
386        type Error = std::convert::Infallible;
387
388        async fn invoke(
389            &self,
390            input: &'static str,
391            _config: Option<RunnableConfig>,
392        ) -> Result<usize, Self::Error> {
393            match input {
394                "slow" => {
395                    tokio::time::sleep(std::time::Duration::from_millis(40)).await;
396                    Ok(10)
397                }
398                _ => {
399                    tokio::time::sleep(std::time::Duration::from_millis(1)).await;
400                    Ok(1)
401                }
402            }
403        }
404    }
405
406    #[tokio::test]
407    async fn test_batch_as_completed_returns_completion_order() {
408        let results = Delayed
409            .batch_as_completed(vec!["slow", "fast"], None)
410            .await
411            .unwrap();
412        // 快的那项先完成 → results[0] 的下标应是 1("fast")
413        assert_eq!(results.len(), 2);
414        assert_eq!(results[0].0, 1, "完成最快的应带原始下标 1");
415        assert_eq!(results[0].1, 1);
416        assert_eq!(results[1].0, 0);
417        assert_eq!(results[1].1, 10);
418    }
419
420    #[tokio::test]
421    async fn test_batch_as_completed_respects_max_concurrency() {
422        let config = RunnableConfig::new().with_max_concurrency(1);
423        let results = Delayed
424            .batch_as_completed(vec!["fast", "slow"], Some(config))
425            .await
426            .unwrap();
427        // 并发上限 1 → 串行,完成顺序 = 输入顺序
428        assert_eq!(results, vec![(0, 1), (1, 10)]);
429    }
430
431    #[tokio::test]
432    async fn test_batch_as_completed_empty_input() {
433        let runnable = TestRunnable;
434        let results = runnable.batch_as_completed(vec![], None).await.unwrap();
435        assert!(results.is_empty());
436    }
437
438    /// 默认 `transform` 必须惰性:下游收到第一条输出时,上游流尚未产完。
439    /// 若仍按旧的"攒齐整条流"实现,本测试会在 `assert!(!produced_last..)` 处失败。
440    #[tokio::test]
441    async fn default_transform_is_lazy_incremental() {
442        use std::sync::atomic::{AtomicBool, Ordering};
443        use std::sync::Arc;
444
445        let produced_last = Arc::new(AtomicBool::new(false));
446        let flag = Arc::clone(&produced_last);
447
448        // 上游:产出 3 条,产完最后一条才置位 flag。
449        let src = async_stream::stream! {
450            yield Ok("first".to_string());
451            yield Ok("second".to_string());
452            yield Ok("third".to_string());
453            flag.store(true, Ordering::SeqCst);
454        };
455        let input_stream = Box::pin(src)
456            as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
457
458        let runnable = TestRunnable;
459        let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
460
461        // 首条输出到达时,上游尚未产完(惰性逐条拼接,而非攒齐整条流)。
462        let first = output_stream.next().await.unwrap().unwrap();
463        assert_eq!(first, "processed: first");
464        assert!(
465            !produced_last.load(Ordering::SeqCst),
466            "transform 不应在上游流结束前就攒齐整条输入"
467        );
468
469        // 消费剩余全部,此时上游 flag 应已置位(输出序列完整、无丢失)。
470        while output_stream.next().await.is_some() {}
471        assert!(produced_last.load(Ordering::SeqCst));
472    }
473}