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 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(
150 async move { Ok(result) },
151 )))
152 } else {
153 Ok(Box::pin(futures_util::stream::empty()))
154 }
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use futures_util::StreamExt;
162
163 struct TestRunnable;
164
165 #[async_trait]
166 impl Runnable<String, String> for TestRunnable {
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!("processed: {}", input))
175 }
176 }
177
178 #[tokio::test]
179 async fn test_default_stream_returns_single_element() {
180 let runnable = TestRunnable;
181 let mut stream = runnable.stream("test".to_string(), None).await.unwrap();
182
183 let first = stream.next().await;
184 assert!(first.is_some());
185 assert_eq!(first.unwrap().unwrap(), "processed: test");
186
187 let second = stream.next().await;
188 assert!(second.is_none());
189 }
190
191 #[tokio::test]
192 async fn test_invoke_matches_stream_result() {
193 let runnable = TestRunnable;
194
195 let invoke_result = runnable.invoke("hello".to_string(), None).await.unwrap();
196 let mut stream = runnable.stream("hello".to_string(), None).await.unwrap();
197 let stream_result = stream.next().await.unwrap().unwrap();
198
199 assert_eq!(invoke_result, stream_result);
200 }
201
202 #[tokio::test]
203 async fn test_default_transform_buffers_and_invokes() {
204 let runnable = TestRunnable;
205 let input_stream = Box::pin(futures_util::stream::iter(vec![
206 Ok("first".to_string()),
207 Ok("second".to_string()),
208 Ok("third".to_string()),
209 ]))
210 as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
211
212 let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
213
214 // Default transform takes the last item and invokes on it
215 let result = output_stream.next().await.unwrap().unwrap();
216 assert_eq!(result, "processed: third");
217
218 // Stream should be exhausted
219 assert!(output_stream.next().await.is_none());
220 }
221
222 #[tokio::test]
223 async fn test_default_transform_empty_input() {
224 let runnable = TestRunnable;
225 let input_stream = Box::pin(futures_util::stream::empty::<
226 Result<String, std::convert::Infallible>,
227 >())
228 as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
229
230 let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
231
232 // Empty input → empty output
233 assert!(output_stream.next().await.is_none());
234 }
235}