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}