Skip to main content

lc_providers/
error.rs

1// lc-providers/src/error.rs
2//! Unified error type for the lc-providers crate.
3//!
4//! Aggregates all provider-specific error types so the `?` operator works
5//! seamlessly across provider boundaries.
6
7pub use crate::ollama::OllamaError;
8pub use crate::openai::responses::types::ResponsesError;
9pub use crate::openai::AssistantError;
10pub use crate::openai::OpenAIError;
11pub use crate::providers::anthropic::error::AnthropicError;
12pub use crate::providers::azure::AzureOpenAIError;
13pub use crate::providers::cohere::CohereError;
14pub use crate::providers::gemini::GeminiError;
15
16/// Unified error type that aggregates all LLM provider errors.
17///
18/// This allows using `?` across provider boundaries without manually
19/// mapping error types. Each variant wraps the original provider
20/// error, preserving full context.
21#[derive(Debug)]
22#[non_exhaustive]
23pub enum ProviderError {
24    /// OpenAI API error.
25    OpenAI(OpenAIError),
26    /// Anthropic API error.
27    Anthropic(AnthropicError),
28    /// Gemini API error.
29    Gemini(GeminiError),
30    /// Azure OpenAI API error.
31    Azure(AzureOpenAIError),
32    /// Cohere API error.
33    Cohere(CohereError),
34    /// Ollama API error.
35    Ollama(OllamaError),
36    /// OpenAI Assistants API error.
37    Assistant(AssistantError),
38    /// OpenAI Responses API error.
39    Responses(ResponsesError),
40    /// DeepSeek API error (OpenAI-compatible endpoint).
41    DeepSeek(OpenAIError),
42    /// Qwen (Alibaba) API error (OpenAI-compatible endpoint).
43    Qwen(OpenAIError),
44    /// Moonshot (Kimi) API error (OpenAI-compatible endpoint).
45    Moonshot(OpenAIError),
46    /// Zhipu (ChatGLM) API error (OpenAI-compatible endpoint).
47    Zhipu(OpenAIError),
48    /// Mistral API error (OpenAI-compatible endpoint).
49    Mistral(OpenAIError),
50    /// Configuration error (missing/malformed environment variables, etc.).
51    Config(String),
52    /// Testkit harness error (recording/replay failures from `lc-testkit`).
53    Testkit(String),
54}
55
56impl std::fmt::Display for ProviderError {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        match self {
59            ProviderError::OpenAI(e) => write!(f, "OpenAI error: {e}"),
60            ProviderError::Anthropic(e) => write!(f, "Anthropic error: {e}"),
61            ProviderError::Gemini(e) => write!(f, "Gemini error: {e}"),
62            ProviderError::Azure(e) => write!(f, "Azure OpenAI error: {e}"),
63            ProviderError::Cohere(e) => write!(f, "Cohere error: {e}"),
64            ProviderError::Ollama(e) => write!(f, "Ollama error: {e}"),
65            ProviderError::Assistant(e) => write!(f, "Assistant error: {e}"),
66            ProviderError::Responses(e) => write!(f, "Responses error: {e}"),
67            ProviderError::DeepSeek(e) => write!(f, "DeepSeek error: {e}"),
68            ProviderError::Qwen(e) => write!(f, "Qwen error: {e}"),
69            ProviderError::Moonshot(e) => write!(f, "Moonshot error: {e}"),
70            ProviderError::Zhipu(e) => write!(f, "Zhipu error: {e}"),
71            ProviderError::Mistral(e) => write!(f, "Mistral error: {e}"),
72            ProviderError::Config(msg) => write!(f, "Configuration error: {msg}"),
73            ProviderError::Testkit(msg) => write!(f, "Testkit error: {msg}"),
74        }
75    }
76}
77
78impl std::error::Error for ProviderError {
79    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
80        match self {
81            ProviderError::OpenAI(e) => Some(e),
82            ProviderError::Anthropic(e) => Some(e),
83            ProviderError::Gemini(e) => Some(e),
84            ProviderError::Azure(e) => Some(e),
85            ProviderError::Cohere(e) => Some(e),
86            ProviderError::Ollama(e) => Some(e),
87            ProviderError::Assistant(e) => Some(e),
88            ProviderError::Responses(e) => Some(e),
89            ProviderError::DeepSeek(e) => Some(e),
90            ProviderError::Qwen(e) => Some(e),
91            ProviderError::Moonshot(e) => Some(e),
92            ProviderError::Zhipu(e) => Some(e),
93            ProviderError::Mistral(e) => Some(e),
94            ProviderError::Config(_) => None,
95            ProviderError::Testkit(_) => None,
96        }
97    }
98}
99
100// ---- From impls for all provider error types ----
101
102impl From<OpenAIError> for ProviderError {
103    fn from(e: OpenAIError) -> Self {
104        ProviderError::OpenAI(e)
105    }
106}
107impl From<AnthropicError> for ProviderError {
108    fn from(e: AnthropicError) -> Self {
109        ProviderError::Anthropic(e)
110    }
111}
112impl From<GeminiError> for ProviderError {
113    fn from(e: GeminiError) -> Self {
114        ProviderError::Gemini(e)
115    }
116}
117impl From<OllamaError> for ProviderError {
118    fn from(e: OllamaError) -> Self {
119        ProviderError::Ollama(e)
120    }
121}
122impl From<AssistantError> for ProviderError {
123    fn from(e: AssistantError) -> Self {
124        ProviderError::Assistant(e)
125    }
126}
127impl From<ResponsesError> for ProviderError {
128    fn from(e: ResponsesError) -> Self {
129        ProviderError::Responses(e)
130    }
131}
132impl From<AzureOpenAIError> for ProviderError {
133    fn from(e: AzureOpenAIError) -> Self {
134        ProviderError::Azure(e)
135    }
136}
137impl From<CohereError> for ProviderError {
138    fn from(e: CohereError) -> Self {
139        ProviderError::Cohere(e)
140    }
141}
142
143/// Allow testkit harness errors (recording/replay) to surface through the
144/// provider error chain. `lc-testkit` is an external crate and cannot
145/// construct `#[non_exhaustive]` variants, so this is the sole entry point.
146impl From<String> for ProviderError {
147    fn from(msg: String) -> Self {
148        ProviderError::Testkit(msg)
149    }
150}
151
152// ---- LCEL Error conversion ----
153
154/// Allow `ProviderError` to convert into `LcelError` for LCEL pipeline compatibility.
155/// This enables LLM providers to participate in `pipe()` chains.
156impl From<ProviderError> for lc_core::LcelError {
157    fn from(err: ProviderError) -> Self {
158        lc_core::LcelError::Provider(err.to_string())
159    }
160}
161
162/// Allow `OpenAIError` to convert into `LcelError` for LCEL pipeline compatibility.
163///
164/// `OpenAIChat`'s `Runnable` uses `OpenAIError` directly as its `Error` type; without the
165/// bridge, `OpenAIChat` cannot enter a `pipe()` chain (which needs `R2::Error: Into<LcelError>`).
166/// Qwen / DeepSeek go through OpenAI-compatible endpoints, but they wrap `OpenAIError` into
167/// `ProviderError` and bridge from there, already covered by the impl above.
168impl From<OpenAIError> for lc_core::LcelError {
169    fn from(err: OpenAIError) -> Self {
170        lc_core::LcelError::Provider(err.to_string())
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    /// Native OpenAIChat errors convert cleanly into LcelError, so `openai.pipe(...)` compiles as the second pipe step.
179    #[test]
180    fn openai_error_into_lcel_error() {
181        let e = OpenAIError::Api("rate limited".to_string());
182        let lcel: lc_core::LcelError = e.into();
183        assert!(matches!(
184            lcel,
185            lc_core::LcelError::Provider(ref msg) if msg.contains("API error: rate limited")
186        ));
187    }
188
189    /// Qwen/DeepSeek reuse OpenAIError, but bridge through ProviderError, equally into LcelError.
190    #[test]
191    fn qwen_provider_error_into_lcel_error() {
192        let e = ProviderError::Qwen(OpenAIError::Http("timeout".to_string()));
193        let lcel: lc_core::LcelError = e.into();
194        assert!(matches!(
195            lcel,
196            lc_core::LcelError::Provider(ref msg) if msg.contains("Qwen error") && msg.contains("timeout")
197        ));
198    }
199
200    #[test]
201    fn deepseek_provider_error_into_lcel_error() {
202        let e = ProviderError::DeepSeek(OpenAIError::Parse("bad json".to_string()));
203        let lcel: lc_core::LcelError = e.into();
204        assert!(matches!(
205            lcel,
206            lc_core::LcelError::Provider(ref msg) if msg.contains("DeepSeek error")
207        ));
208    }
209}