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).await.map(|output| (index, output))
142                }
143            })
144            .buffer_unordered(limit)
145            .collect::<Vec<Result<(usize, Output), _>>>()
146            .await;
147
148        // Short-circuit on the first error (in completion order).
149        results.into_iter().collect()
150    }
151
152    /// Streaming output - for real-time responses (LLM, etc).
153    ///
154    /// Enables real-time stream processing of output,
155    /// suitable for chat models, token generation, etc.
156    ///
157    /// # Arguments
158    /// * `input` - Input to process.
159    /// * `config` - Optional configuration.
160    ///
161    /// # Returns
162    /// Output stream.
163    ///
164    /// # Default Implementation
165    /// Wraps invoke result as single-element stream.
166    /// Types supporting true streaming should override.
167    async fn stream(
168        &self,
169        input: Input,
170        config: Option<RunnableConfig>,
171    ) -> Result<Pin<Box<dyn Stream<Item = Result<Output, Self::Error>> + Send>>, Self::Error> {
172        // Default: wrap invoke result as single-element stream
173        // All Runables automatically get stream capability
174        // Types with true streaming (like LLM) should override
175        let result = self.invoke(input, config).await?;
176        let stream = futures_util::stream::once(async move { Ok(result) });
177        Ok(Box::pin(stream))
178    }
179
180    /// Stream-to-stream transformation - the core of LCEL streaming.
181    ///
182    /// Takes an input stream and produces an output stream, enabling
183    /// pipeline streaming without buffering intermediate results.
184    ///
185    /// # Default Implementation
186    /// Drives each input item through `stream` and concatenates the per-item
187    /// streams in order — the LangChain default `transform` semantics. A step
188    /// that overrides `stream` (e.g. an LLM) yields a real token stream per
189    /// item; a step using the default `stream` maps elementwise via `invoke`.
190    /// Components that want aggregation (e.g. incremental parsers) should
191    /// override this method.
192    ///
193    /// # Arguments
194    /// * `input` - Input stream to transform.
195    /// * `config` - Optional execution configuration.
196    ///
197    /// # Returns
198    /// Output stream.
199    async fn transform(
200        &self,
201        input: Pin<Box<dyn Stream<Item = Result<Input, Self::Error>> + Send>>,
202        config: Option<RunnableConfig>,
203    ) -> Result<Pin<Box<dyn Stream<Item = Result<Output, Self::Error>> + Send>>, Self::Error> {
204        use futures_util::StreamExt;
205
206        // Buffer the input items, then run each one through `stream` and
207        // concatenate the per-item streams (elementwise semantics).
208        let mut items = Vec::new();
209        let mut input = input;
210        while let Some(item) = input.next().await {
211            items.push(item?);
212        }
213
214        let mut per_item_streams = Vec::with_capacity(items.len());
215        for item in items {
216            let stream = self.stream(item, config.clone()).await?;
217            per_item_streams.push(stream);
218        }
219
220        let flattened = futures_util::stream::iter(per_item_streams).flatten();
221        Ok(Box::pin(flattened))
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use futures_util::StreamExt;
229
230    struct TestRunnable;
231
232    #[async_trait]
233    impl Runnable<String, String> for TestRunnable {
234        type Error = std::convert::Infallible;
235
236        async fn invoke(
237            &self,
238            input: String,
239            _config: Option<RunnableConfig>,
240        ) -> Result<String, Self::Error> {
241            Ok(format!("processed: {}", input))
242        }
243    }
244
245    #[tokio::test]
246    async fn test_default_stream_returns_single_element() {
247        let runnable = TestRunnable;
248        let mut stream = runnable.stream("test".to_string(), None).await.unwrap();
249
250        let first = stream.next().await;
251        assert!(first.is_some());
252        assert_eq!(first.unwrap().unwrap(), "processed: test");
253
254        let second = stream.next().await;
255        assert!(second.is_none());
256    }
257
258    #[tokio::test]
259    async fn test_invoke_matches_stream_result() {
260        let runnable = TestRunnable;
261
262        let invoke_result = runnable.invoke("hello".to_string(), None).await.unwrap();
263        let mut stream = runnable.stream("hello".to_string(), None).await.unwrap();
264        let stream_result = stream.next().await.unwrap().unwrap();
265
266        assert_eq!(invoke_result, stream_result);
267    }
268
269    #[tokio::test]
270    async fn test_default_transform_maps_elementwise() {
271        let runnable = TestRunnable;
272        let input_stream = Box::pin(futures_util::stream::iter(vec![
273            Ok("first".to_string()),
274            Ok("second".to_string()),
275            Ok("third".to_string()),
276        ]))
277            as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
278
279        let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
280
281        // Default transform maps each item through invoke (elementwise).
282        let mut results = Vec::new();
283        while let Some(item) = output_stream.next().await {
284            results.push(item.unwrap());
285        }
286        assert_eq!(
287            results,
288            vec![
289                "processed: first".to_string(),
290                "processed: second".to_string(),
291                "processed: third".to_string(),
292            ]
293        );
294    }
295
296    #[tokio::test]
297    async fn test_default_transform_empty_input() {
298        let runnable = TestRunnable;
299        let input_stream = Box::pin(futures_util::stream::empty::<
300            Result<String, std::convert::Infallible>,
301        >())
302            as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
303
304        let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
305
306        // Empty input → empty output
307        assert!(output_stream.next().await.is_none());
308    }
309
310    #[tokio::test]
311    async fn test_default_batch_preserves_order() {
312        let runnable = TestRunnable;
313        let results = runnable
314            .batch(
315                vec!["a".to_string(), "b".to_string(), "c".to_string()],
316                None,
317            )
318            .await
319            .unwrap();
320        assert_eq!(
321            results,
322            vec![
323                "processed: a".to_string(),
324                "processed: b".to_string(),
325                "processed: c".to_string(),
326            ]
327        );
328    }
329
330    #[tokio::test]
331    async fn test_default_batch_respects_max_concurrency() {
332        let runnable = TestRunnable;
333        let config = RunnableConfig::new().with_max_concurrency(1);
334        let results = runnable
335            .batch(
336                vec!["x".to_string(), "y".to_string(), "z".to_string()],
337                Some(config),
338            )
339            .await
340            .unwrap();
341        assert_eq!(
342            results,
343            vec![
344                "processed: x".to_string(),
345                "processed: y".to_string(),
346                "processed: z".to_string(),
347            ]
348        );
349    }
350
351    #[tokio::test]
352    async fn test_default_batch_empty_input() {
353        let runnable = TestRunnable;
354        let results = runnable.batch(vec![], None).await.unwrap();
355        assert!(results.is_empty());
356    }
357
358    /// 带延迟的 Runnable:"slow" 明显慢于其他,用于验证完成顺序 ≠ 输入顺序。
359    struct Delayed;
360
361    #[async_trait]
362    impl Runnable<&'static str, usize> for Delayed {
363        type Error = std::convert::Infallible;
364
365        async fn invoke(
366            &self,
367            input: &'static str,
368            _config: Option<RunnableConfig>,
369        ) -> Result<usize, Self::Error> {
370            match input {
371                "slow" => {
372                    tokio::time::sleep(std::time::Duration::from_millis(40)).await;
373                    Ok(10)
374                }
375                _ => {
376                    tokio::time::sleep(std::time::Duration::from_millis(1)).await;
377                    Ok(1)
378                }
379            }
380        }
381    }
382
383    #[tokio::test]
384    async fn test_batch_as_completed_returns_completion_order() {
385        let results = Delayed
386            .batch_as_completed(vec!["slow", "fast"], None)
387            .await
388            .unwrap();
389        // 快的那项先完成 → results[0] 的下标应是 1("fast")
390        assert_eq!(results.len(), 2);
391        assert_eq!(results[0].0, 1, "完成最快的应带原始下标 1");
392        assert_eq!(results[0].1, 1);
393        assert_eq!(results[1].0, 0);
394        assert_eq!(results[1].1, 10);
395    }
396
397    #[tokio::test]
398    async fn test_batch_as_completed_respects_max_concurrency() {
399        let config = RunnableConfig::new().with_max_concurrency(1);
400        let results = Delayed
401            .batch_as_completed(vec!["fast", "slow"], Some(config))
402            .await
403            .unwrap();
404        // 并发上限 1 → 串行,完成顺序 = 输入顺序
405        assert_eq!(results, vec![(0, 1), (1, 10)]);
406    }
407
408    #[tokio::test]
409    async fn test_batch_as_completed_empty_input() {
410        let runnable = TestRunnable;
411        let results = runnable.batch_as_completed(vec![], None).await.unwrap();
412        assert!(results.is_empty());
413    }
414}