Class: Ace::LLM::Atoms::ErrorClassifier
- Inherits:
-
Object
- Object
- Ace::LLM::Atoms::ErrorClassifier
- Defined in:
- lib/ace/llm/atoms/error_classifier.rb
Overview
ErrorClassifier categorizes errors to determine retry and fallback strategies This is an atom - it has no dependencies on other parts of this gem
Constant Summary collapse
- RETRYABLE_WITH_BACKOFF =
Error classification types
:retryable_with_backoff- FALLBACK_IMMEDIATELY =
:fallback_immediately- SKIP_TO_NEXT =
:skip_to_next- TERMINAL =
:terminal- STATUS_CLASSIFICATIONS =
Map HTTP status codes to classification
{ 401 => SKIP_TO_NEXT, # Unauthorized - skip to next provider 403 => SKIP_TO_NEXT, # Forbidden - skip to next provider 404 => SKIP_TO_NEXT, # Not Found - skip to next provider 429 => RETRYABLE_WITH_BACKOFF, # Rate Limited - retry with backoff 500 => RETRYABLE_WITH_BACKOFF, # Internal Server Error - retry 502 => RETRYABLE_WITH_BACKOFF, # Bad Gateway - retry 503 => RETRYABLE_WITH_BACKOFF, # Service Unavailable - retry 504 => RETRYABLE_WITH_BACKOFF # Gateway Timeout - retry }.freeze
- QUOTA_LIMIT_PATTERNS =
[ "insufficient_quota", "insufficient quota", "quota exceeded", "quota has been exceeded", "quota limit", "quota exhausted", "out of credit", "credits exhausted", "insufficient credit", "billing hard limit", "spending limit", "usage limit reached", "rate window limit", "window limit reached" ].freeze
Class Method Summary collapse
-
.classify(error) ⇒ Symbol
Classify an error for retry/fallback decisions.
-
.extract_status_code(error) ⇒ Integer?
Extract HTTP status code from various error types.
-
.fallback_immediately?(error) ⇒ Boolean
Determine if an error should trigger immediate fallback.
-
.quota_or_credit_limited?(error) ⇒ Boolean
Determine if an error indicates quota/credit/window exhaustion and should move immediately to the next provider.
-
.retry_delay(error, attempt: 1, base_delay: 1.0) ⇒ Float
Get retry delay for an error based on retry-after header or default.
-
.retryable?(error) ⇒ Boolean
Determine if an error is retryable.
-
.skip_to_next?(error) ⇒ Boolean
Determine if an error should skip to next provider without retry.
Class Method Details
.classify(error) ⇒ Symbol
Classify an error for retry/fallback decisions
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/ace/llm/atoms/error_classifier.rb', line 47 def self.classify(error) case error when Ace::LLM::AuthenticationError SKIP_TO_NEXT when Ace::LLM::ProviderError # Check if we can extract HTTP status from the error message classify_provider_error(error) when Faraday::TimeoutError FALLBACK_IMMEDIATELY when Faraday::ConnectionFailed RETRYABLE_WITH_BACKOFF when Faraday::ClientError classify_faraday_error(error) when Faraday::ServerError RETRYABLE_WITH_BACKOFF else TERMINAL end end |
.extract_status_code(error) ⇒ Integer?
Extract HTTP status code from various error types
102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/ace/llm/atoms/error_classifier.rb', line 102 def self.extract_status_code(error) if error.respond_to?(:response) && error.response error.response[:status] elsif error.respond_to?(:http_status) error.http_status elsif error.is_a?(Ace::LLM::ProviderError) # Try to parse status from error message like "error (503):" match = error..match(/\((\d{3})\)/) match[1].to_i if match end end |
.fallback_immediately?(error) ⇒ Boolean
Determine if an error should trigger immediate fallback
78 79 80 81 |
# File 'lib/ace/llm/atoms/error_classifier.rb', line 78 def self.fallback_immediately?(error) classification = classify(error) classification == FALLBACK_IMMEDIATELY end |
.quota_or_credit_limited?(error) ⇒ Boolean
Determine if an error indicates quota/credit/window exhaustion and should move immediately to the next provider.
95 96 97 |
# File 'lib/ace/llm/atoms/error_classifier.rb', line 95 def self.quota_or_credit_limited?(error) (error..to_s) end |
.retry_delay(error, attempt: 1, base_delay: 1.0) ⇒ Float
Get retry delay for an error based on retry-after header or default
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/ace/llm/atoms/error_classifier.rb', line 119 def self.retry_delay(error, attempt: 1, base_delay: 1.0) # Check for retry-after header (for 429 rate limits) if error.respond_to?(:response) && error.response headers = error.response[:headers] if headers && headers["retry-after"] return parse_retry_after(headers["retry-after"]) end end # Exponential backoff with jitter: base_delay * 2^(attempt - 1) * (1 + jitter) # Jitter is 10-30% to prevent thundering herd exponential_delay = base_delay * (2**(attempt - 1)) jitter = rand(0.1..0.3) exponential_delay * (1 + jitter) end |
.retryable?(error) ⇒ Boolean
Determine if an error is retryable
70 71 72 73 |
# File 'lib/ace/llm/atoms/error_classifier.rb', line 70 def self.retryable?(error) classification = classify(error) classification == RETRYABLE_WITH_BACKOFF end |
.skip_to_next?(error) ⇒ Boolean
Determine if an error should skip to next provider without retry
86 87 88 89 |
# File 'lib/ace/llm/atoms/error_classifier.rb', line 86 def self.skip_to_next?(error) classification = classify(error) classification == SKIP_TO_NEXT end |