Skip to main content

autoagents_llm/
error.rs

1use std::fmt;
2
3/// Error types that can occur when interacting with LLM providers.
4#[derive(Debug)]
5pub enum LLMError {
6    /// HTTP request/response errors
7    HttpError(String),
8    /// Authentication and authorization errors
9    AuthError(String),
10    /// Invalid request parameters or format
11    InvalidRequest(String),
12    /// Errors returned by the LLM provider
13    ProviderError(String),
14    /// API response parsing or format error
15    ResponseFormatError {
16        message: String,
17        raw_response: String,
18    },
19    /// Generic error
20    Generic(String),
21    /// JSON serialization/deserialization errors
22    JsonError(String),
23    /// Tool configuration error
24    ToolConfigError(String),
25    /// No Tool Support
26    NoToolSupport(String),
27}
28
29impl fmt::Display for LLMError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            LLMError::HttpError(e) => write!(f, "HTTP Error: {e}"),
33            LLMError::AuthError(e) => write!(f, "Auth Error: {e}"),
34            LLMError::InvalidRequest(e) => write!(f, "Invalid Request: {e}"),
35            LLMError::ProviderError(e) => write!(f, "Provider Error: {e}"),
36            LLMError::Generic(e) => write!(f, "Generic Error : {e}"),
37            LLMError::ResponseFormatError {
38                message,
39                raw_response,
40            } => {
41                write!(
42                    f,
43                    "Response Format Error: {message}. Raw response: {raw_response}"
44                )
45            }
46            LLMError::JsonError(e) => write!(f, "JSON Parse Error: {e}"),
47            LLMError::ToolConfigError(e) => write!(f, "Tool Configuration Error: {e}"),
48            LLMError::NoToolSupport(e) => write!(f, "No Tool Support: {e}"),
49        }
50    }
51}
52
53impl std::error::Error for LLMError {}
54
55/// Converts reqwest HTTP errors into LLMErrors
56#[cfg(not(target_arch = "wasm32"))]
57impl From<reqwest::Error> for LLMError {
58    fn from(err: reqwest::Error) -> Self {
59        LLMError::HttpError(err.to_string())
60    }
61}
62
63impl From<serde_json::Error> for LLMError {
64    fn from(err: serde_json::Error) -> Self {
65        LLMError::JsonError(format!(
66            "{} at line {} column {}",
67            err,
68            err.line(),
69            err.column()
70        ))
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use serde_json::Error as JsonError;
78    use std::error::Error;
79
80    #[test]
81    fn test_llm_error_display_http_error() {
82        let error = LLMError::HttpError("Connection failed".to_string());
83        assert_eq!(error.to_string(), "HTTP Error: Connection failed");
84    }
85
86    #[test]
87    fn test_llm_error_display_auth_error() {
88        let error = LLMError::AuthError("Invalid API key".to_string());
89        assert_eq!(error.to_string(), "Auth Error: Invalid API key");
90    }
91
92    #[test]
93    fn test_llm_error_display_invalid_request() {
94        let error = LLMError::InvalidRequest("Missing required parameter".to_string());
95        assert_eq!(
96            error.to_string(),
97            "Invalid Request: Missing required parameter"
98        );
99    }
100
101    #[test]
102    fn test_llm_error_display_provider_error() {
103        let error = LLMError::ProviderError("Model not found".to_string());
104        assert_eq!(error.to_string(), "Provider Error: Model not found");
105    }
106
107    #[test]
108    fn test_llm_error_display_generic_error() {
109        let error = LLMError::Generic("Something went wrong".to_string());
110        assert_eq!(error.to_string(), "Generic Error : Something went wrong");
111    }
112
113    #[test]
114    fn test_llm_error_display_response_format_error() {
115        let error = LLMError::ResponseFormatError {
116            message: "Invalid JSON".to_string(),
117            raw_response: "{invalid json}".to_string(),
118        };
119        assert_eq!(
120            error.to_string(),
121            "Response Format Error: Invalid JSON. Raw response: {invalid json}"
122        );
123    }
124
125    #[test]
126    fn test_llm_error_display_json_error() {
127        let error = LLMError::JsonError("Parse error at line 5 column 10".to_string());
128        assert_eq!(
129            error.to_string(),
130            "JSON Parse Error: Parse error at line 5 column 10"
131        );
132    }
133
134    #[test]
135    fn test_llm_error_display_tool_config_error() {
136        let error = LLMError::ToolConfigError("Invalid tool configuration".to_string());
137        assert_eq!(
138            error.to_string(),
139            "Tool Configuration Error: Invalid tool configuration"
140        );
141    }
142
143    #[test]
144    fn test_llm_error_is_error_trait() {
145        let error = LLMError::Generic("test error".to_string());
146        assert!(error.source().is_none());
147    }
148
149    #[test]
150    fn test_llm_error_debug_format() {
151        let error = LLMError::HttpError("test".to_string());
152        let debug_str = format!("{error:?}");
153        assert!(debug_str.contains("HttpError"));
154        assert!(debug_str.contains("test"));
155    }
156
157    #[test]
158    fn test_from_reqwest_error() {
159        // Create a mock reqwest error by trying to make a request to an invalid URL
160        let client = reqwest::Client::new();
161        let rt = tokio::runtime::Runtime::new().unwrap();
162        let reqwest_error = rt
163            .block_on(async {
164                client
165                    .get("https://invalid-url-that-does-not-exist-12345.com/")
166                    .timeout(std::time::Duration::from_millis(100))
167                    .send()
168                    .await
169            })
170            .unwrap_err();
171
172        let llm_error: LLMError = reqwest_error.into();
173
174        match llm_error {
175            LLMError::HttpError(msg) => {
176                assert!(!msg.is_empty());
177            }
178            _ => panic!("Expected HttpError"),
179        }
180    }
181
182    #[test]
183    fn test_from_serde_json_error() {
184        let json_str = r#"{"invalid": json}"#;
185        let json_error: JsonError =
186            serde_json::from_str::<serde_json::Value>(json_str).unwrap_err();
187
188        let llm_error: LLMError = json_error.into();
189
190        match llm_error {
191            LLMError::JsonError(msg) => {
192                assert!(msg.contains("line"));
193                assert!(msg.contains("column"));
194            }
195            _ => panic!("Expected JsonError"),
196        }
197    }
198
199    #[test]
200    fn test_error_variants_equality() {
201        let error1 = LLMError::HttpError("test".to_string());
202        let error2 = LLMError::HttpError("test".to_string());
203        let error3 = LLMError::HttpError("different".to_string());
204        let error4 = LLMError::AuthError("test".to_string());
205
206        // Note: LLMError doesn't implement PartialEq, so we test via string representation
207        assert_eq!(error1.to_string(), error2.to_string());
208        assert_ne!(error1.to_string(), error3.to_string());
209        assert_ne!(error1.to_string(), error4.to_string());
210    }
211
212    #[test]
213    fn test_response_format_error_fields() {
214        let error = LLMError::ResponseFormatError {
215            message: "Parse failed".to_string(),
216            raw_response: "raw content".to_string(),
217        };
218
219        let display_str = error.to_string();
220        assert!(display_str.contains("Parse failed"));
221        assert!(display_str.contains("raw content"));
222    }
223
224    #[test]
225    fn test_all_error_variants_have_display() {
226        let errors = vec![
227            LLMError::HttpError("http".to_string()),
228            LLMError::AuthError("auth".to_string()),
229            LLMError::InvalidRequest("invalid".to_string()),
230            LLMError::ProviderError("provider".to_string()),
231            LLMError::Generic("generic".to_string()),
232            LLMError::ResponseFormatError {
233                message: "format".to_string(),
234                raw_response: "raw".to_string(),
235            },
236            LLMError::JsonError("json".to_string()),
237            LLMError::ToolConfigError("tool".to_string()),
238        ];
239
240        for error in errors {
241            let display_str = error.to_string();
242            assert!(!display_str.is_empty());
243        }
244    }
245
246    #[test]
247    fn test_error_type_classification() {
248        // Test that we can pattern match on different error types
249        let http_error = LLMError::HttpError("test".to_string());
250        match http_error {
251            LLMError::HttpError(_) => {}
252            _ => panic!("Expected HttpError"),
253        }
254
255        let auth_error = LLMError::AuthError("test".to_string());
256        match auth_error {
257            LLMError::AuthError(_) => {}
258            _ => panic!("Expected AuthError"),
259        }
260
261        let response_error = LLMError::ResponseFormatError {
262            message: "test".to_string(),
263            raw_response: "test".to_string(),
264        };
265        match response_error {
266            LLMError::ResponseFormatError { .. } => {}
267            _ => panic!("Expected ResponseFormatError"),
268        }
269    }
270}