Skip to main content

ai_lib_rust/client/
error_classification.rs

1//! 错误分类逻辑:将 HTTP 状态码和提供商错误映射到 V2 标准错误码。
2//!
3//! Error classification logic.
4
5use crate::error_code::StandardErrorCode;
6use serde_yaml::Value;
7
8/// Maps an error class name string to the corresponding AI-Protocol V2 standard error code.
9///
10/// The class name should match the standard names (e.g., `"invalid_request"`).
11/// Aliases such as `"authorized_error"` (→ authentication) are supported.
12/// Unknown class names map to `StandardErrorCode::Unknown`.
13pub fn classify_to_standard_code(error_class: &str) -> StandardErrorCode {
14    StandardErrorCode::from_error_class(error_class)
15}
16
17/// Classify an error from HTTP status and optional response body.
18///
19/// Provider-specific error codes in the response body (e.g., OpenAI `context_length_exceeded`,
20/// Anthropic `overloaded_error`) override the HTTP status mapping when available.
21pub fn classify_error_from_response(
22    http_status: u16,
23    response_body: Option<&Value>,
24) -> StandardErrorCode {
25    if let Some(body) = response_body {
26        if let Some(code) = extract_provider_error_code(body) {
27            if let Some(std_code) = StandardErrorCode::from_provider_code(&code) {
28                return std_code;
29            }
30        }
31    }
32    StandardErrorCode::from_http_status(http_status)
33}
34
35/// Extract provider error code from response body.
36///
37/// Handles OpenAI format: `{"error": {"code": "...", "type": "..."}}`
38/// and Anthropic format: `{"error": {"type": "..."}}`.
39fn extract_provider_error_code(body: &Value) -> Option<String> {
40    let mapping = body.as_mapping()?;
41    // OpenAI: error.code or error.type
42    if let Some(err) = mapping.get("error") {
43        if let Some(err_map) = err.as_mapping() {
44            if let Some(code) = err_map.get("code").and_then(|v| v.as_str()) {
45                return Some(code.to_string());
46            }
47            if let Some(ty) = err_map.get("type").and_then(|v| v.as_str()) {
48                return Some(ty.to_string());
49            }
50        }
51    }
52    None
53}
54
55/// Determine if an error class is fallbackable based on protocol specification.
56///
57/// This follows the standard error_classes from spec.yaml:
58/// - Transient errors (retryable) are typically fallbackable
59/// - Quota/authentication errors are fallbackable (per-provider; another provider may succeed)
60/// - Invalid requests are NOT fallbackable (they'll fail on any provider)
61pub(crate) fn is_fallbackable_error_class(error_class: &str) -> bool {
62    classify_to_standard_code(error_class).fallbackable()
63}