1use async_trait::async_trait;
5use futures_util::Stream;
6use serde_json::Value;
7use std::collections::HashMap;
8use std::pin::Pin;
9
10#[derive(Debug, thiserror::Error)]
12pub enum ChainError {
13 #[error("Missing input: {0}")]
15 MissingInput(String),
16
17 #[error("Output error: {0}")]
19 OutputError(String),
20
21 #[error("Execution error: {0}")]
23 ExecutionError(String),
24
25 #[error("Stream error: {0}")]
27 StreamError(String),
28
29 #[error("Chain error: {0}")]
31 Other(String),
32}
33
34pub type ChainResult = HashMap<String, Value>;
36
37#[derive(Debug, Clone)]
39pub struct StreamToken {
40 pub token: String,
42 pub is_final: bool,
44}
45
46pub type ChainStream = Pin<Box<dyn Stream<Item = Result<StreamToken, ChainError>> + Send>>;
48
49#[async_trait]
53pub trait BaseChain: Send + Sync {
54 fn input_keys(&self) -> Vec<&str>;
56
57 fn output_keys(&self) -> Vec<&str>;
59
60 async fn invoke(&self, inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError>;
68
69 async fn stream(&self, inputs: HashMap<String, Value>) -> Result<ChainStream, ChainError> {
81 let result = self.invoke(inputs).await?;
83 let output_text = result
84 .values()
85 .next()
86 .and_then(|v| v.as_str())
87 .unwrap_or("")
88 .to_string();
89 let stream = futures_util::stream::once(async move {
90 Ok(StreamToken {
91 token: output_text,
92 is_final: true,
93 })
94 });
95 Ok(Box::pin(stream))
96 }
97
98 fn validate_inputs(&self, inputs: &HashMap<String, Value>) -> Result<(), ChainError> {
100 for key in self.input_keys() {
101 if !inputs.contains_key(key) {
102 return Err(ChainError::MissingInput(key.to_string()));
103 }
104 }
105 Ok(())
106 }
107
108 fn name(&self) -> &str {
110 "chain"
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_chain_error_display() {
120 let error = ChainError::MissingInput("test".to_string());
121 assert!(error.to_string().contains("Missing input"));
122
123 let error = ChainError::ExecutionError("test".to_string());
124 assert!(error.to_string().contains("Execution error"));
125 }
126}