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/// ```rust
22/// use langchainrust::core::runnables::Runnable;
23/// use langchainrust::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 sequentially.
64    /// Override for concurrent execution or batch optimization.
65    ///
66    /// # Arguments
67    /// * `inputs` - Input vector.
68    /// * `config` - Optional batch configuration.
69    ///
70    /// # Returns
71    /// Result vector.
72    async fn batch(
73        &self,
74        inputs: Vec<Input>,
75        config: Option<RunnableConfig>,
76    ) -> Result<Vec<Output>, Self::Error> {
77        let mut results = Vec::with_capacity(inputs.len());
78
79        // Process each input sequentially
80        for input in inputs {
81            let result = self.invoke(input, config.clone()).await?;
82            results.push(result);
83        }
84
85        Ok(results)
86    }
87
88    /// Streaming output - for real-time responses (LLM, etc).
89    ///
90    /// Enables real-time stream processing of output,
91    /// suitable for chat models, token generation, etc.
92    ///
93    /// # Arguments
94    /// * `input` - Input to process.
95    /// * `config` - Optional configuration.
96    ///
97    /// # Returns
98    /// Output stream.
99    ///
100    /// # Default Implementation
101    /// Wraps invoke result as single-element stream.
102    /// Types supporting true streaming should override.
103    async fn stream(
104        &self,
105        input: Input,
106        config: Option<RunnableConfig>,
107    ) -> Result<Pin<Box<dyn Stream<Item = Result<Output, Self::Error>> + Send>>, Self::Error> {
108        // Default: wrap invoke result as single-element stream
109        // All Runables automatically get stream capability
110        // Types with true streaming (like LLM) should override
111        let result = self.invoke(input, config).await?;
112        let stream = futures_util::stream::once(async move { Ok(result) });
113        Ok(Box::pin(stream))
114    }
115
116    /// Stream-to-stream transformation - the core of LCEL streaming.
117    ///
118    /// Takes an input stream and produces an output stream, enabling
119    /// pipeline streaming without buffering intermediate results.
120    ///
121    /// # Default Implementation
122    /// Buffers all input items, takes the last one (stream accumulation
123    /// semantics), and calls `invoke` on it. Components that support
124    /// true streaming (e.g. LLMs) should override this method.
125    ///
126    /// # Arguments
127    /// * `input` - Input stream to transform.
128    /// * `config` - Optional execution configuration.
129    ///
130    /// # Returns
131    /// Output stream.
132    async fn transform(
133        &self,
134        input: Pin<Box<dyn Stream<Item = Result<Input, Self::Error>> + Send>>,
135        config: Option<RunnableConfig>,
136    ) -> Result<Pin<Box<dyn Stream<Item = Result<Output, Self::Error>> + Send>>, Self::Error> {
137        use futures_util::StreamExt;
138
139        // Default: buffer all input, take the last item, invoke on it
140        let mut items = Vec::new();
141        let mut input = input;
142        while let Some(item) = input.next().await {
143            items.push(item?);
144        }
145
146        // Use the last item (stream accumulation semantics)
147        if let Some(last) = items.into_iter().last() {
148            let result = self.invoke(last, config).await?;
149            Ok(Box::pin(futures_util::stream::once(async move { Ok(result) })))
150        } else {
151            Ok(Box::pin(futures_util::stream::empty()))
152        }
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use futures_util::StreamExt;
160
161    struct TestRunnable;
162
163    #[async_trait]
164    impl Runnable<String, String> for TestRunnable {
165        type Error = std::convert::Infallible;
166
167        async fn invoke(
168            &self,
169            input: String,
170            _config: Option<RunnableConfig>,
171        ) -> Result<String, Self::Error> {
172            Ok(format!("processed: {}", input))
173        }
174    }
175
176    #[tokio::test]
177    async fn test_default_stream_returns_single_element() {
178        let runnable = TestRunnable;
179        let mut stream = runnable.stream("test".to_string(), None).await.unwrap();
180
181        let first = stream.next().await;
182        assert!(first.is_some());
183        assert_eq!(first.unwrap().unwrap(), "processed: test");
184
185        let second = stream.next().await;
186        assert!(second.is_none());
187    }
188
189    #[tokio::test]
190    async fn test_invoke_matches_stream_result() {
191        let runnable = TestRunnable;
192
193        let invoke_result = runnable.invoke("hello".to_string(), None).await.unwrap();
194        let mut stream = runnable.stream("hello".to_string(), None).await.unwrap();
195        let stream_result = stream.next().await.unwrap().unwrap();
196
197        assert_eq!(invoke_result, stream_result);
198    }
199
200    #[tokio::test]
201    async fn test_default_transform_buffers_and_invokes() {
202        let runnable = TestRunnable;
203        let input_stream = Box::pin(futures_util::stream::iter(vec![
204            Ok("first".to_string()),
205            Ok("second".to_string()),
206            Ok("third".to_string()),
207        ])) as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
208
209        let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
210
211        // Default transform takes the last item and invokes on it
212        let result = output_stream.next().await.unwrap().unwrap();
213        assert_eq!(result, "processed: third");
214
215        // Stream should be exhausted
216        assert!(output_stream.next().await.is_none());
217    }
218
219    #[tokio::test]
220    async fn test_default_transform_empty_input() {
221        let runnable = TestRunnable;
222        let input_stream = Box::pin(futures_util::stream::empty::<Result<String, std::convert::Infallible>>())
223            as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
224
225        let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
226
227        // Empty input → empty output
228        assert!(output_stream.next().await.is_none());
229    }
230}