lc_core/runnables/
binding.rs1use super::any::{into_runnable_any, RunnableAny};
9use super::config::RunnableConfig;
10use super::error::LcelError;
11use super::runnable_trait::Runnable;
12use async_trait::async_trait;
13use futures_util::{Stream, StreamExt};
14use serde_json::Value;
15use std::any::Any;
16use std::collections::HashMap;
17use std::pin::Pin;
18
19pub struct RunnableBinding<I: Send + Sync + 'static, O: Send + Sync + 'static> {
30 bound: Box<dyn RunnableAny>,
31 kwargs: HashMap<String, Value>,
32 config: RunnableConfig,
33 _marker: std::marker::PhantomData<(I, O)>,
34}
35
36impl<I: Send + Sync + 'static, O: Send + Sync + 'static> std::fmt::Debug for RunnableBinding<I, O> {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("RunnableBinding")
39 .field("kwargs", &self.kwargs)
40 .field("input", &std::any::type_name::<I>())
41 .field("output", &std::any::type_name::<O>())
42 .finish()
43 }
44}
45
46impl<I: Send + Sync + 'static, O: Send + Sync + 'static> RunnableBinding<I, O> {
47 pub fn new<R>(runnable: R) -> Self
49 where
50 R: Runnable<I, O> + 'static,
51 R::Error: Into<LcelError>,
52 {
53 Self {
54 bound: into_runnable_any(runnable),
55 kwargs: HashMap::new(),
56 config: RunnableConfig::default(),
57 _marker: std::marker::PhantomData,
58 }
59 }
60
61 pub fn bind(mut self, key: impl Into<String>, value: Value) -> Self {
66 self.kwargs.insert(key.into(), value);
67 self
68 }
69
70 pub fn with_config(mut self, config: RunnableConfig) -> Self {
74 self.config = config;
75 self
76 }
77
78 fn merged_config(&self, invocation_config: Option<RunnableConfig>) -> RunnableConfig {
80 let mut base = self.config.clone();
81
82 for (key, value) in &self.kwargs {
84 base = base.with_metadata(key.clone(), value.clone());
85 }
86
87 if let Some(inv) = invocation_config {
89 base.merge(inv)
90 } else {
91 base
92 }
93 }
94}
95
96#[async_trait]
97impl<I: Send + Sync + 'static, O: Send + Sync + 'static> Runnable<I, O> for RunnableBinding<I, O> {
98 type Error = LcelError;
99
100 async fn invoke(
101 &self,
102 input: I,
103 config: Option<RunnableConfig>,
104 ) -> Result<O, LcelError> {
105 let merged = self.merged_config(config);
106 let result = self
107 .bound
108 .invoke_any(Box::new(input) as Box<dyn Any + Send>, Some(merged))
109 .await?;
110 result
111 .downcast::<O>()
112 .map(|b| *b)
113 .map_err(|_| LcelError::TypeMismatch(format!(
114 "binding output downcast: expected {}",
115 std::any::type_name::<O>()
116 )))
117 }
118
119 async fn batch(
120 &self,
121 inputs: Vec<I>,
122 config: Option<RunnableConfig>,
123 ) -> Result<Vec<O>, LcelError> {
124 let merged = self.merged_config(config);
125 let boxed_inputs: Vec<Box<dyn Any + Send>> =
126 inputs.into_iter().map(|i| Box::new(i) as Box<dyn Any + Send>).collect();
127 let results = self.bound.batch_any(boxed_inputs, Some(merged)).await?;
128 results
129 .into_iter()
130 .map(|boxed| {
131 boxed
132 .downcast::<O>()
133 .map(|b| *b)
134 .map_err(|_| LcelError::TypeMismatch(format!(
135 "binding batch downcast: expected {}",
136 std::any::type_name::<O>()
137 )))
138 })
139 .collect()
140 }
141
142 async fn stream(
143 &self,
144 input: I,
145 config: Option<RunnableConfig>,
146 ) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
147 let merged = self.merged_config(config);
148 let stream = self
149 .bound
150 .stream_any(Box::new(input) as Box<dyn Any + Send>, Some(merged))
151 .await?;
152 let output_stream = stream.map(|result| {
153 result.and_then(|boxed| {
154 boxed
155 .downcast::<O>()
156 .map(|b| *b)
157 .map_err(|_| LcelError::TypeMismatch(format!(
158 "binding stream downcast: expected {}",
159 std::any::type_name::<O>()
160 )))
161 })
162 });
163 Ok(Box::pin(output_stream))
164 }
165
166 async fn transform(
167 &self,
168 input: Pin<Box<dyn Stream<Item = Result<I, LcelError>> + Send>>,
169 config: Option<RunnableConfig>,
170 ) -> Result<Pin<Box<dyn Stream<Item = Result<O, LcelError>> + Send>>, LcelError> {
171 let merged = self.merged_config(config);
172 let any_input: Pin<Box<dyn Stream<Item = Result<Box<dyn Any + Send>, LcelError>> + Send>> =
173 Box::pin(input.map(|result| result.map(|item| Box::new(item) as Box<dyn Any + Send>)));
174
175 let output_stream = self.bound.transform_any(any_input, Some(merged)).await?;
176
177 let typed_output = output_stream.map(|result| {
178 result.and_then(|boxed| {
179 boxed
180 .downcast::<O>()
181 .map(|b| *b)
182 .map_err(|_| LcelError::TypeMismatch(format!(
183 "binding transform downcast: expected {}",
184 std::any::type_name::<O>()
185 )))
186 })
187 });
188 Ok(Box::pin(typed_output))
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use futures_util::StreamExt;
196
197 struct EchoRunnable;
198
199 #[async_trait]
200 impl Runnable<String, String> for EchoRunnable {
201 type Error = std::convert::Infallible;
202
203 async fn invoke(
204 &self,
205 input: String,
206 config: Option<RunnableConfig>,
207 ) -> Result<String, Self::Error> {
208 let tags = config
209 .map(|c| c.tags.join(","))
210 .unwrap_or_default();
211 if tags.is_empty() {
212 Ok(input)
213 } else {
214 Ok(format!("[{}] {}", tags, input))
215 }
216 }
217 }
218
219 #[tokio::test]
220 async fn binding_with_config() {
221 let binding = RunnableBinding::new(EchoRunnable)
222 .with_config(RunnableConfig::default().with_tag("prod"));
223
224 let result = binding.invoke("hello".to_string(), None).await.unwrap();
225 assert_eq!(result, "[prod] hello");
226 }
227
228 #[tokio::test]
229 async fn binding_with_kwargs() {
230 let binding = RunnableBinding::new(EchoRunnable)
231 .bind("stop", Value::String("\n".to_string()));
232
233 let result = binding.invoke("test".to_string(), None).await.unwrap();
235 assert_eq!(result, "test"); }
237
238 #[tokio::test]
239 async fn binding_stream_works() {
240 let binding = RunnableBinding::new(EchoRunnable)
241 .with_config(RunnableConfig::default().with_tag("stream"));
242
243 let mut stream = binding.stream("hello".to_string(), None).await.unwrap();
244 let result = stream.next().await.unwrap().unwrap();
245 assert_eq!(result, "[stream] hello");
246 }
247}