Class: Insika::ProviderErrorClassifier
- Inherits:
-
Object
- Object
- Insika::ProviderErrorClassifier
- Defined in:
- lib/insika/provider_error_classifier.rb
Overview
Classifies provider/transport failures by ACTION (B9). Four kinds — the hermes/openclaw structural rule: NON-retryable checked first, so an error we do not recognize defaults to :fatal (a retry would hammer a poisoned credential; a fatal is retried only after the operator fixes the cause):
:fatal 401/402/403/400 (auth, billing, permanent quota,
bad request, context too long) — retrying does not help.
:retryable 5xx/529/socket/timeout — the same call may succeed
moments later.
:rate_limited_short a 429 that says "back off briefly" (RPM-scale).
:rate_limited_long a 429 with a long retry-after — quota-scale.
The classification is STRING-based (class names, no constant references):
the core loads without ruby_llm, and the smoke-shim's fake RubyLLM is a
drop-in. retry_after is read from the provider's own Retry-After header
when the error carries a response, else a per-kind default.
Defined Under Namespace
Classes: Classification
Constant Summary collapse
- KINDS =
%i[fatal retryable rate_limited_short rate_limited_long].freeze
- SHORT_RETRY_LIMIT =
Above this a 429 means quota, not RPM (this is what tells the two apart — a short 429 wants a quick retry; a long one is a billing event).
60- DEFAULTS =
seconds
{ retryable: 5, rate_limited_short: 10, rate_limited_long: 300 }.freeze
- FATAL_ERROR_NAMES =
RubyLLM's taxonomy (error.rb), matched by class name so the core stays ruby_llm-free at load time.
%w[ RubyLLM::ContextLengthExceededError RubyLLM::BadRequestError RubyLLM::UnauthorizedError RubyLLM::PaymentRequiredError RubyLLM::ForbiddenError ].freeze
- RATE_LIMITED_ERROR_NAME =
"RubyLLM::RateLimitError"- RETRYABLE_ERROR_NAMES =
%w[ RubyLLM::ServerError RubyLLM::ServiceUnavailableError RubyLLM::OverloadedError ].freeze
- RUBY_LLM_ERROR_NAMES =
( FATAL_ERROR_NAMES + [RATE_LIMITED_ERROR_NAME] + RETRYABLE_ERROR_NAMES + ["RubyLLM::Error"] ).freeze
- TRANSPORT_NAME_PATTERNS =
Transport failures while talking to the provider: connection refused/reset, DNS, TLS, timeouts (Faraday wraps its own names; the stdlib ones surface from raw sockets).
[ /\AFaraday::/, /\ASocketError\z/, /\AIOError\z/, /\AErrno::/, /\ANet::(Read|Open)Timeout\z/, /\ATimeout::Error\z/, /\AOpenSSL::SSL::SSLError\z/ ].freeze
Class Method Summary collapse
-
.classify(error) ⇒ Object
-> Classification.
-
.provider_error?(error) ⇒ Boolean
True when the error came from the provider call itself (RubyLLM family or transport) — the executor routes these to the :ruby_llm stage with a wrapped classification instead of :unknown.
-
.wrap(error) ⇒ Object
The typed ProviderError the executor stores and emits.
Class Method Details
.classify(error) ⇒ Object
-> Classification
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/insika/provider_error_classifier.rb', line 72 def classify(error) names = class_names(error) # Non-retryable first (the structural rule): a known fatal is NEVER # retried, and an unknown error defaults to fatal — never to retry. return fatal if (names & FATAL_ERROR_NAMES).any? return rate_limited(error) if names.include?(RATE_LIMITED_ERROR_NAME) return retryable if (names & RETRYABLE_ERROR_NAMES).any? return retryable if transport?(names) # A generic RubyLLM::Error (or a raw HTTP error) still carries the # status: 429 and 5xx are retryable regardless of the wrapping class. case http_status(error) when 429 then rate_limited(error) when 500..599 then retryable else fatal end end |
.provider_error?(error) ⇒ Boolean
True when the error came from the provider call itself (RubyLLM family or transport) — the executor routes these to the :ruby_llm stage with a wrapped classification instead of :unknown.
96 97 98 99 |
# File 'lib/insika/provider_error_classifier.rb', line 96 def provider_error?(error) names = class_names(error) (names & RUBY_LLM_ERROR_NAMES).any? || transport?(names) end |
.wrap(error) ⇒ Object
The typed ProviderError the executor stores and emits.
102 103 104 105 106 107 108 |
# File 'lib/insika/provider_error_classifier.rb', line 102 def wrap(error) c = classify(error) Insika::ProviderError.new( error. || error.class.name, kind: c.kind, retryable: c.retryable, retry_after: c.retry_after ) end |