Class: Asimov::ApiV1::ApiErrorTranslator

Inherits:
Object
  • Object
show all
Defined in:
lib/asimov/api_v1/api_error_translator.rb

Overview

Translates errors which are generated by OpenAI and provided in responses to raised exceptions. Because OpenAI doesn’t currently provide any error-specific codes above and beyond the HTTP return code, this matching process is done by matching against message text which makes it somewhat fragile.

Constant Summary collapse

INVALID_API_KEY_PREFIX =

rubocop:disable Naming/VariableNumber rubocop:disable Metrics/MethodLength

"Incorrect API key provided: ".freeze
INVALID_ORGANIZATION_PREFIX =
"No such organization: ".freeze
INVALID_TRAINING_EXAMPLE_PREFIX =
"Expected file to have JSONL format with " \
"prompt/completion keys. Missing".freeze
ADDITIONAL_PROPERTIES_ERROR_PREFIX =
"Additional properties are not allowed".freeze
INVALID_PARAMETER_VALUE_STRING =
"' is not one of [".freeze
INVALID_PARAMETER_VALUE_PREFIX_2 =
"Invalid value for ".freeze
BELOW_MINIMUM_STRING =
" is less than the minimum of ".freeze
ABOVE_MAXIMUM_STRING =
" is greater than the maximum of ".freeze

Class Method Summary collapse

Class Method Details

.error_message(resp) ⇒ Object

Extracts the error message from the API response



106
107
108
109
110
111
112
# File 'lib/asimov/api_v1/api_error_translator.rb', line 106

def self.error_message(resp)
  # This handles fragments which can occur in a streamed download
  pr = resp.respond_to?(:parsed_response) ? resp.parsed_response : JSON.parse(resp)
  return "" unless pr.is_a?(Hash) && pr["error"].is_a?(Hash)

  pr["error"]["message"] || ""
end

.match_400(resp) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/asimov/api_v1/api_error_translator.rb', line 38

def self.match_400(resp)
  return unless resp.code == 400

  msg = error_message(resp)
  # 400
  # {"error"=>{"message"=>"'moose' is not one of ['fine-tune', 'answers', 'search', "    #                       "'classifications'] - 'purpose'", "type"=>"invalid_request_error",
  #                       "param"=>nil, "code"=>nil}}
  # {"error"=>{"code"=>nil, "message"=>"'8x8' is not one of ['256x256', '512x512', "    #                                    "'1024x1024'] - 'size'", "param"=>nil,
  #            "type"=>"invalid_request_error"}}
  # {"error"=>{"message"=>"Incorrect format for purpose=classifications. Please check "    #                       "the openai documentation and try again",
  #            "type"=>"invalid_request_error", "param"=>nil, "code"=>nil}}
  # {"error"=>{"message"=>"Expected file to have the JSONL format with 'text' key "    #                       "and (optional) 'metadata' key.",
  #            "type"=>"invalid_request_error", "param"=>nil, "code"=>nil}}
  # {"error"=>{"message"=>"Additional properties are not allowed ('moose' was unexpected)",
  #            "type"=>"invalid_request_error", "param"=>nil, "code"=>nil}}
  # {"error"=>{"message"=>"Additional properties are not allowed ('moose', 'squirrel' were "    #                       "unexpected)",
  #            "type"=>"invalid_request_error", "param"=>nil, "code"=>nil}}
  # {"error"=>{"code"=>nil, "message"=>"-1 is less than the minimum of 1 - 'n'",
  #  "param"=>nil, "type"=>"invalid_request_error"}}
  # {"error"=>{"code"=>nil, "message"=>"20 is greater than the maximum of 10 - 'n'",
  #  "param"=>nil, "type"=>"invalid_request_error"}}

  if msg.start_with?(INVALID_TRAINING_EXAMPLE_PREFIX)
    raise Asimov::InvalidTrainingExampleError,
          msg
  end
  if msg.start_with?(ADDITIONAL_PROPERTIES_ERROR_PREFIX)
    raise Asimov::UnsupportedParameterError,
          msg
  end

  if match_invalid_parameter_value?(msg)
    raise Asimov::InvalidParameterValueError,
          msg
  end

  raise Asimov::RequestError, msg
end

.match_401(resp) ⇒ Object

Raises:

  • (Asimov::InvalidApiKeyError)


21
22
23
24
25
26
27
28
29
# File 'lib/asimov/api_v1/api_error_translator.rb', line 21

def self.match_401(resp)
  return unless resp.code == 401

  msg = error_message(resp)
  raise Asimov::InvalidApiKeyError, msg if msg.start_with?(INVALID_API_KEY_PREFIX)
  raise Asimov::InvalidOrganizationError, msg if msg.start_with?(INVALID_API_KEY_PREFIX)

  raise Asimov::UnauthorizedError
end

.match_404(resp) ⇒ Object



89
90
91
92
93
94
95
96
97
98
# File 'lib/asimov/api_v1/api_error_translator.rb', line 89

def self.match_404(resp)
  return unless resp.code == 404

  msg = error_message(resp)
  # {"error"=>{"message"=>"That model does not exist", "type"=>"invalid_request_error",
  #  "param"=>"model", "code"=>nil}}
  # {"error"=>{"message"=>"No such File object: file-BWp1k9EVJRq5Ybjr3Mb0tDXW",
  #  "type"=>"invalid_request_error", "param"=>"id", "code"=>nil}}
  raise Asimov::NotFoundError, msg
end

.match_invalid_parameter_value?(msg) ⇒ Boolean

Returns:

  • (Boolean)


82
83
84
85
86
87
# File 'lib/asimov/api_v1/api_error_translator.rb', line 82

def self.match_invalid_parameter_value?(msg)
  msg.include?(INVALID_PARAMETER_VALUE_STRING) ||
    msg.include?(BELOW_MINIMUM_STRING) ||
    msg.include?(ABOVE_MAXIMUM_STRING) ||
    msg.start_with?(INVALID_PARAMETER_VALUE_PREFIX_2)
end

.translate(resp) ⇒ Object



11
12
13
14
15
# File 'lib/asimov/api_v1/api_error_translator.rb', line 11

def self.translate(resp)
  match_400(resp)
  match_401(resp)
  match_404(resp)
end