neuromance_client/error.rs
1//! Error types for the client library.
2
3use std::time::Duration;
4
5use serde::Deserialize;
6use thiserror::Error;
7
8/// Error response from the API.
9///
10/// Wraps the detailed error information returned by LLM providers.
11#[derive(Debug, Deserialize)]
12pub struct ErrorResponse {
13 /// The error detail object from the API.
14 pub error: ErrorDetail,
15}
16
17/// Detailed error information from the API.
18///
19/// Contains the specific error message returned by the provider.
20#[derive(Debug, Deserialize)]
21pub struct ErrorDetail {
22 /// The error message text describing what went wrong.
23 pub message: String,
24}
25
26/// Errors that can occur when interacting with LLM APIs.
27///
28/// This enum covers all error conditions from network failures to API-specific
29/// errors like rate limiting and content filtering.
30#[derive(Debug, Error)]
31#[non_exhaustive]
32pub enum ClientError {
33 /// Network or HTTP request failure.
34 ///
35 /// Indicates issues like DNS resolution, connection failures, or socket errors.
36 /// These errors are typically retryable.
37 #[error("Network error: {0}")]
38 NetworkError(#[from] reqwest::Error),
39
40 /// Middleware layer error.
41 ///
42 /// Errors from request/response middleware such as retry logic or logging.
43 #[error("Middleware error: {0}")]
44 MiddlewareError(#[from] reqwest_middleware::Error),
45
46 /// JSON serialization or deserialization error.
47 ///
48 /// Occurs when request/response JSON cannot be properly encoded or decoded.
49 #[error("Serialization error: {0}")]
50 SerializationError(#[from] serde_json::Error),
51
52 /// API authentication failure (HTTP 401).
53 ///
54 /// The API key is missing, invalid, or revoked. Check your credentials.
55 #[error("Authentication error: {0}")]
56 AuthenticationError(String),
57
58 /// use reqwest_eventsource::{Error as EventSourceError};
59 #[error("EventSource error: {0}")]
60 EventSourceError(#[from] reqwest_eventsource::Error),
61
62 /// Rate limit exceeded (HTTP 429).
63 ///
64 /// Too many requests sent in a given time period. Wait and retry.
65 #[error("Rate limit exceeded: {retry_after:?}")]
66 RateLimitError {
67 /// Suggested wait time before retrying, if provided by the API.
68 retry_after: Option<Duration>,
69 },
70
71 /// Model-specific error from the API.
72 ///
73 /// The model encountered an error during generation.
74 #[error("Model error: {0}")]
75 ModelError(String),
76
77 /// Client configuration issue.
78 ///
79 /// Invalid base URL, missing required fields, or incompatible settings.
80 #[error("Configuration error: {0}")]
81 ConfigurationError(String),
82
83 /// Request timeout.
84 ///
85 /// The request took longer than the configured timeout. Consider increasing
86 /// the timeout or reducing request complexity.
87 #[error("Timeout error")]
88 TimeoutError,
89
90 /// Malformed request.
91 ///
92 /// The request structure is invalid or missing required parameters.
93 #[error("Invalid request: {0}")]
94 InvalidRequest(String),
95
96 /// Unexpected or malformed API response.
97 ///
98 /// The API returned data that doesn't match the expected format.
99 #[error("Invalid response: {0}")]
100 InvalidResponse(String),
101
102 /// Tools requested but not supported by this model.
103 ///
104 /// The model or provider doesn't support function calling.
105 #[error("Tool execution not supported")]
106 ToolsNotSupported,
107
108 /// Streaming requested but not supported.
109 ///
110 /// The model or provider doesn't support streaming responses.
111 #[error("Streaming not supported")]
112 StreamingNotSupported,
113
114 /// Token limit exceeded for this model.
115 ///
116 /// The input plus requested output exceeds the model's context window.
117 #[error("Context length exceeded: {current_tokens} > {max_tokens}")]
118 ContextLengthExceeded {
119 /// Current number of tokens in the request.
120 current_tokens: usize,
121 /// Maximum tokens allowed by the model.
122 max_tokens: usize,
123 },
124
125 /// Content blocked by safety filter.
126 ///
127 /// The content violates the provider's usage policies.
128 #[error("Content filtered: {reason}")]
129 ContentFiltered {
130 /// Reason for filtering (e.g., "violence", "hate_speech").
131 reason: String,
132 },
133
134 /// API service unavailable (5xx errors).
135 ///
136 /// The provider's servers are experiencing issues. Retry with backoff.
137 #[error("Service unavailable: {0}")]
138 ServiceUnavailable(String),
139
140 /// Temperature parameter out of valid range.
141 ///
142 /// Temperature must be between 0.0 and 2.0.
143 #[error("Temperature must be between 0.0 & 2.0")]
144 InvalidTemperature,
145
146 /// top_p parameter out of valid range.
147 ///
148 /// top_p must be between 0.0 and 1.0.
149 #[error("TopP must be between 0.0 & 1.0")]
150 InvalidTopP,
151
152 /// frequency_penalty parameter out of valid range.
153 ///
154 /// frequency_penalty must be between -2.0 and 2.0.
155 #[error("FrequencyPenalty must be between -2.0 & 2.0")]
156 InvalidFrequencyPenalty,
157}
158
159impl ClientError {
160 /// Check if this error is potentially retryable.
161 ///
162 /// Returns `true` for network errors, timeouts, rate limits, and service unavailable errors.
163 pub fn is_retryable(&self) -> bool {
164 matches!(
165 self,
166 Self::NetworkError(_)
167 | Self::MiddlewareError(_)
168 | Self::TimeoutError
169 | Self::RateLimitError { .. }
170 | Self::ServiceUnavailable(_)
171 )
172 }
173
174 /// Check if this is an authentication error.
175 pub fn is_authentication_error(&self) -> bool {
176 matches!(self, Self::AuthenticationError(_))
177 }
178
179 /// Check if this is a rate limit error.
180 pub fn is_rate_limit_error(&self) -> bool {
181 matches!(self, Self::RateLimitError { .. })
182 }
183
184 /// Get the retry-after duration if this is a rate limit error.
185 ///
186 /// Returns the suggested wait time before retrying the request.
187 pub fn retry_after(&self) -> Option<Duration> {
188 match self {
189 Self::RateLimitError { retry_after } => *retry_after,
190 _ => None,
191 }
192 }
193}