Module: ActiveAgent::Providers::Errors::Taxonomy

Defined in:
lib/active_agent/providers/errors.rb

Overview

Classifies vendor SDK exceptions into the taxonomy. Unrecognizable exceptions (including ordinary Ruby errors) pass through untouched — only errors that look like vendor API failures are normalized.

Constant Summary collapse

NAME_MAP =

Vendor SDK class names (demodulized) → taxonomy class. Covers the official anthropic/openai gems and SDKs following their naming.

{
  "RateLimitError" => RateLimited,
  "AuthenticationError" => AuthenticationFailed,
  "PermissionDeniedError" => AuthenticationFailed,
  "ContentFilterError" => ContentFiltered,
  "InternalServerError" => ServiceUnavailable,
  "APIConnectionError" => ServiceUnavailable,
  "APIConnectionTimeoutError" => ServiceUnavailable,
  "APITimeoutError" => ServiceUnavailable,
  "OverloadedError" => ServiceUnavailable,
  "ServiceUnavailableError" => ServiceUnavailable,
  "BadRequestError" => InvalidRequest,
  "UnprocessableEntityError" => InvalidRequest
}.freeze
STATUS_MAP =
{
  400 => InvalidRequest,
  401 => AuthenticationFailed,
  403 => AuthenticationFailed,
  408 => ServiceUnavailable,
  422 => InvalidRequest,
  429 => RateLimited,
  529 => ServiceUnavailable # Anthropic "overloaded"
}.freeze
CONTEXT_LENGTH_PATTERN =
/context length|context_length|maximum context|context window|too many tokens|prompt is too long|input (?:is )?too long/i
CONTENT_FILTER_PATTERN =
/content (?:filter|policy|management)|filtered due to|blocked by|safety (?:system|filter)/i

Class Method Summary collapse

Class Method Details

.classify(exception) ⇒ Class?

Returns:

  • (Class, nil)


109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/active_agent/providers/errors.rb', line 109

def classify(exception)
  name = exception.class.name.to_s.demodulize
  status = status_of(exception)
  api_error = NAME_MAP.key?(name) || !status.nil?
  return nil unless api_error

  message = exception.message.to_s
  return ContextLengthExceeded if CONTEXT_LENGTH_PATTERN.match?(message)
  return ContentFiltered if CONTENT_FILTER_PATTERN.match?(message)

  NAME_MAP[name] || STATUS_MAP[status] || (status && status >= 500 ? ServiceUnavailable : nil)
end

.normalize(exception, provider_tag: nil) ⇒ Exception

Returns a taxonomy error, or the original exception when it doesn't classify.

Parameters:

  • exception (Exception)
  • provider_tag (String, nil) (defaults to: nil)

Returns:

  • (Exception)

    a taxonomy error, or the original exception when it doesn't classify



99
100
101
102
103
104
105
106
# File 'lib/active_agent/providers/errors.rb', line 99

def normalize(exception, provider_tag: nil)
  return exception if exception.is_a?(ProviderError)

  klass = classify(exception)
  return exception unless klass

  klass.new(exception.message, status: status_of(exception), provider_tag: provider_tag)
end

.status_of(exception) ⇒ Integer?

Returns:

  • (Integer, nil)


123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/active_agent/providers/errors.rb', line 123

def status_of(exception)
  [ :status, :status_code, :http_status, :code ].each do |reader|
    next unless exception.respond_to?(reader)

    value = begin
      exception.public_send(reader)
    rescue StandardError
      nil
    end
    return value if value.is_a?(Integer)
  end
  nil
end