1use async_trait::async_trait;
5use futures_util::Stream;
6use lc_core::runnables::RunnableConfig;
7use serde_json::Value;
8use std::collections::HashMap;
9use std::pin::Pin;
10
11#[derive(Debug, thiserror::Error)]
13pub enum ChainError {
14 #[error("Missing input: {0}")]
16 MissingInput(String),
17
18 #[error("Output error: {0}")]
20 OutputError(String),
21
22 #[error("Execution error: {0}")]
24 ExecutionError(String),
25
26 #[error("Stream error: {0}")]
28 StreamError(String),
29
30 #[error("Chain error: {0}")]
32 Other(String),
33}
34
35pub type ChainResult = HashMap<String, Value>;
37
38#[derive(Debug, Clone)]
40pub struct StreamToken {
41 pub token: String,
43 pub is_final: bool,
45}
46
47pub type ChainStream = Pin<Box<dyn Stream<Item = Result<StreamToken, ChainError>> + Send>>;
49
50#[async_trait]
54pub trait BaseChain: Send + Sync {
55 fn input_keys(&self) -> Vec<&str>;
57
58 fn output_keys(&self) -> Vec<&str>;
60
61 async fn invoke(&self, inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError>;
69
70 async fn invoke_with_config(
79 &self,
80 inputs: HashMap<String, Value>,
81 config: Option<RunnableConfig>,
82 ) -> Result<ChainResult, ChainError> {
83 let _ = config; self.invoke(inputs).await
86 }
87
88 async fn stream(&self, inputs: HashMap<String, Value>) -> Result<ChainStream, ChainError> {
100 let result = self.invoke(inputs).await?;
102 let output_text = result
103 .values()
104 .next()
105 .and_then(|v| v.as_str())
106 .unwrap_or("")
107 .to_string();
108 let stream = futures_util::stream::once(async move {
109 Ok(StreamToken {
110 token: output_text,
111 is_final: true,
112 })
113 });
114 Ok(Box::pin(stream))
115 }
116
117 async fn stream_with_config(
121 &self,
122 inputs: HashMap<String, Value>,
123 config: Option<RunnableConfig>,
124 ) -> Result<ChainStream, ChainError> {
125 let _ = config;
126 self.stream(inputs).await
127 }
128
129 fn validate_inputs(&self, inputs: &HashMap<String, Value>) -> Result<(), ChainError> {
131 for key in self.input_keys() {
132 if !inputs.contains_key(key) {
133 return Err(ChainError::MissingInput(key.to_string()));
134 }
135 }
136 Ok(())
137 }
138
139 fn name(&self) -> &str {
141 "chain"
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_chain_error_display() {
151 let error = ChainError::MissingInput("test".to_string());
152 assert!(error.to_string().contains("Missing input"));
153
154 let error = ChainError::ExecutionError("test".to_string());
155 assert!(error.to_string().contains("Execution error"));
156 }
157
158 #[test]
159 fn test_chain_error_all_variants() {
160 let err = ChainError::MissingInput("key".to_string());
161 assert!(err.to_string().contains("key"));
162
163 let err = ChainError::OutputError("bad".to_string());
164 assert!(err.to_string().contains("bad"));
165
166 let err = ChainError::ExecutionError("fail".to_string());
167 assert!(err.to_string().contains("fail"));
168
169 let err = ChainError::StreamError("broken".to_string());
170 assert!(err.to_string().contains("broken"));
171
172 let err = ChainError::Other("misc".to_string());
173 assert!(err.to_string().contains("misc"));
174 }
175
176 #[test]
177 fn test_stream_token_debug() {
178 let token = StreamToken {
179 token: "hello".to_string(),
180 is_final: false,
181 };
182 assert!(format!("{:?}", token).contains("hello"));
183 }
184
185 #[test]
186 fn test_validate_inputs_pass() {
187 struct PassthroughChain;
188 #[async_trait]
189 impl BaseChain for PassthroughChain {
190 fn input_keys(&self) -> Vec<&str> { vec!["input"] }
191 fn output_keys(&self) -> Vec<&str> { vec!["output"] }
192 async fn invoke(&self, inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError> {
193 Ok(inputs)
194 }
195 }
196
197 let chain = PassthroughChain;
198 let mut inputs = HashMap::new();
199 inputs.insert("input".to_string(), Value::String("test".to_string()));
200 assert!(chain.validate_inputs(&inputs).is_ok());
201 }
202
203 #[test]
204 fn test_validate_inputs_missing_key() {
205 struct PassthroughChain;
206 #[async_trait]
207 impl BaseChain for PassthroughChain {
208 fn input_keys(&self) -> Vec<&str> { vec!["input"] }
209 fn output_keys(&self) -> Vec<&str> { vec!["output"] }
210 async fn invoke(&self, _inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError> {
211 Ok(HashMap::new())
212 }
213 }
214
215 let chain = PassthroughChain;
216 let inputs = HashMap::new();
217 assert!(chain.validate_inputs(&inputs).is_err());
218 }
219
220 #[test]
221 fn test_default_chain_name() {
222 struct MyChain;
223 #[async_trait]
224 impl BaseChain for MyChain {
225 fn input_keys(&self) -> Vec<&str> { vec![] }
226 fn output_keys(&self) -> Vec<&str> { vec![] }
227 async fn invoke(&self, _inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError> {
228 Ok(HashMap::new())
229 }
230 }
231 let chain = MyChain;
232 assert_eq!(chain.name(), "chain");
233 }
234}