asimov 0.1.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c50684e3e690496248b87a912334da4b3ace87c83dec1c05e2d8b33349c5d16d
4
- data.tar.gz: 6ab91367f32fa02d4582018fd6cad490ba4e78db92c86e231b64323a3389d88f
3
+ metadata.gz: 7d49b0bc867c3e318501548e7fcf45a3dd48bc469b05f959b5839144e3f9d472
4
+ data.tar.gz: 1193a9a5eea75c3d3b57d09ca10ccd5e50ee01d44ecce648c985ac2bcfd5cded
5
5
  SHA512:
6
- metadata.gz: 859bbbfccff90fdddac6708846bc03b2a6f175bf83fd7ad9329a3de86d8830444e34d822eeeb65d4ba054678b1cda15312b85ca52b4dea2e43dc83cc2554e908
7
- data.tar.gz: 169621c5fe064acc1ba293a01da9473c75aae65f7a650139c377319990140f2295fdae39d5b231fae7f3df7a506224d99e9f7d64acfb46af6967841ec46072c5
6
+ metadata.gz: 349259c93c833021ebfc46f7912f8c36109dc51693c3828972d263282f514adee94b5416d9e89c2dc1cc1d868d01cabce20ef1df01e83279eac80d156b1ded61
7
+ data.tar.gz: 67deac4b3306ffdc5ab36aa49b7ab66093dc4938131b3264ce2ef2cd6a628840cd04d3474f39fe7bc1f70dc5c0d9dba7bd136c28c46c58b776ff2494ae825030
data/CHANGELOG.md CHANGED
@@ -5,9 +5,42 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## Unreleased
9
+
10
+ ## [1.1.0] - 2023-03-01
11
+
12
+ ### Added
13
+
14
+ - Made API base URI configurable to support services that proxy API calls (like Helicone)
15
+ - Added support for the chat and audio endpoints
16
+
17
+ ## [1.0.0] - 2023-01-24
18
+
19
+ This version has complete coverage of the OpenAI API (except for stream: true behavior), has
20
+ no known errors and has full test coverage. At this point there are no anticipated changes
21
+ to existing endpoints.
22
+
23
+ ### Fixed
24
+
25
+ - Fixed handling of authentication errors
26
+
27
+ ### Changed
28
+
29
+ - Properly distinguished public and private methods to ensure proper documentation.
30
+ - Renamed events to list_events for consistency
31
+ - Updated file arguments to take path strings or File-like objects
32
+ - Adjusted endpoints to make required parameters more explicit
33
+
34
+ ### Added
35
+
36
+ - Code level documentation for all public classes and methods.
37
+ - Error for the unsupported stream: true case
38
+ - Error mapping for 409 and 429 errors
39
+ - Specs for authentication
40
+
8
41
  ## [0.1.0] - 2023-01-14
9
42
 
10
- This initial version of the gem is now suitable for use. API endpoint method naming may shift slightly,
43
+ This initial version of the gem is now suitable for use. API endpoint method naming may shift slightly.
11
44
 
12
45
  ### Changed
13
46
 
data/Gemfile CHANGED
@@ -2,4 +2,15 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem "simplecov", require: false, group: :test
5
+ group :development, :test do
6
+ gem "faker"
7
+ gem "rake", "~> 13.0"
8
+ gem "rspec", "~> 3.12"
9
+ gem "rubocop", "~> 1.44"
10
+ gem "rubocop-performance"
11
+ gem "rubocop-rake"
12
+ gem "rubocop-rspec"
13
+ gem "simplecov", require: false
14
+ gem "vcr", "~> 6.1.0"
15
+ gem "webmock", "~> 3.18.1"
16
+ end
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Asimov
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/asimov.svg)](https://badge.fury.io/rb/asimov)
3
4
  [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/petergoldstein/asimov/blob/main/LICENSE.txt)
4
5
  [![Tests](https://github.com/petergoldstein/asimov/actions/workflows/ci.yml/badge.svg)](https://github.com/petergoldstein/asimov/actions/workflows/ci.yml)
5
6
 
@@ -8,26 +8,47 @@ module Asimov
8
8
  # fragile.
9
9
  ##
10
10
  class ApiErrorTranslator
11
+ ##
12
+ # This method raises an appropriate Asimov::RequestError
13
+ # subclass if the response corresponds to an HTTP error.
14
+ #
15
+ # @param [HTTParty::Response] resp the response (or fragment) object
16
+ # encapsulating the server response.
17
+ ##
11
18
  def self.translate(resp)
19
+ return if resp.code == 200
20
+
12
21
  match_400(resp)
13
22
  match_401(resp)
14
23
  match_404(resp)
24
+ match_409(resp)
25
+ match_429(resp)
26
+
27
+ raise Asimov::RequestError, error_message(resp)
15
28
  end
16
29
 
17
30
  # rubocop:disable Naming/VariableNumber
18
31
  # rubocop:disable Metrics/MethodLength
32
+
33
+ # Prefix for OpenAI error message when an invalid key is provided.
19
34
  INVALID_API_KEY_PREFIX = "Incorrect API key provided: ".freeze
35
+
36
+ # Prefix for OpenAI error message when an organization cannot be found.
20
37
  INVALID_ORGANIZATION_PREFIX = "No such organization: ".freeze
21
38
  def self.match_401(resp)
22
39
  return unless resp.code == 401
23
40
 
24
41
  msg = error_message(resp)
25
42
  raise Asimov::InvalidApiKeyError, msg if msg.start_with?(INVALID_API_KEY_PREFIX)
26
- raise Asimov::InvalidOrganizationError, msg if msg.start_with?(INVALID_API_KEY_PREFIX)
43
+ raise Asimov::InvalidOrganizationError, msg if msg.start_with?(INVALID_ORGANIZATION_PREFIX)
27
44
 
28
- raise Asimov::UnauthorizedError
45
+ raise Asimov::AuthorizationError
29
46
  end
47
+ private_class_method :match_401
48
+ private_constant :INVALID_API_KEY_PREFIX
49
+ private_constant :INVALID_ORGANIZATION_PREFIX
30
50
 
51
+ # Prefix for OpenAI error message when training file format cannot be validated.
31
52
  INVALID_TRAINING_EXAMPLE_PREFIX = "Expected file to have JSONL format with " \
32
53
  "prompt/completion keys. Missing".freeze
33
54
  ADDITIONAL_PROPERTIES_ERROR_PREFIX = "Additional properties are not allowed".freeze
@@ -39,28 +60,6 @@ module Asimov
39
60
  return unless resp.code == 400
40
61
 
41
62
  msg = error_message(resp)
42
- # 400
43
- # {"error"=>{"message"=>"'moose' is not one of ['fine-tune', 'answers', 'search', " \
44
- # "'classifications'] - 'purpose'", "type"=>"invalid_request_error",
45
- # "param"=>nil, "code"=>nil}}
46
- # {"error"=>{"code"=>nil, "message"=>"'8x8' is not one of ['256x256', '512x512', " \
47
- # "'1024x1024'] - 'size'", "param"=>nil,
48
- # "type"=>"invalid_request_error"}}
49
- # {"error"=>{"message"=>"Incorrect format for purpose=classifications. Please check " \
50
- # "the openai documentation and try again",
51
- # "type"=>"invalid_request_error", "param"=>nil, "code"=>nil}}
52
- # {"error"=>{"message"=>"Expected file to have the JSONL format with 'text' key " \
53
- # "and (optional) 'metadata' key.",
54
- # "type"=>"invalid_request_error", "param"=>nil, "code"=>nil}}
55
- # {"error"=>{"message"=>"Additional properties are not allowed ('moose' was unexpected)",
56
- # "type"=>"invalid_request_error", "param"=>nil, "code"=>nil}}
57
- # {"error"=>{"message"=>"Additional properties are not allowed ('moose', 'squirrel' were " \
58
- # "unexpected)",
59
- # "type"=>"invalid_request_error", "param"=>nil, "code"=>nil}}
60
- # {"error"=>{"code"=>nil, "message"=>"-1 is less than the minimum of 1 - 'n'",
61
- # "param"=>nil, "type"=>"invalid_request_error"}}
62
- # {"error"=>{"code"=>nil, "message"=>"20 is greater than the maximum of 10 - 'n'",
63
- # "param"=>nil, "type"=>"invalid_request_error"}}
64
63
 
65
64
  if msg.start_with?(INVALID_TRAINING_EXAMPLE_PREFIX)
66
65
  raise Asimov::InvalidTrainingExampleError,
@@ -78,6 +77,50 @@ module Asimov
78
77
 
79
78
  raise Asimov::RequestError, msg
80
79
  end
80
+ private_class_method :match_400
81
+ private_constant :INVALID_TRAINING_EXAMPLE_PREFIX
82
+ private_constant :ADDITIONAL_PROPERTIES_ERROR_PREFIX
83
+ private_constant :INVALID_PARAMETER_VALUE_STRING
84
+ private_constant :INVALID_PARAMETER_VALUE_PREFIX_2
85
+ private_constant :BELOW_MINIMUM_STRING
86
+ private_constant :ABOVE_MAXIMUM_STRING
87
+
88
+ def self.match_409(resp)
89
+ return unless resp.code == 409
90
+
91
+ raise Asimov::RequestError, error_message(resp)
92
+ end
93
+ private_class_method :match_409
94
+
95
+ QUOTA_EXCEEDED_MESSAGE = "You exceeded your current quota".freeze
96
+ RATE_LIMIT_REACHED_MESSAGE = "Rate limit reached".freeze
97
+ ENGINE_OVERLOADED_MESSAGE = "The engine is currently overloaded".freeze
98
+ def self.match_429(resp)
99
+ return unless resp.code == 429
100
+
101
+ msg = error_message(resp)
102
+
103
+ if msg.start_with?(QUOTA_EXCEEDED_MESSAGE)
104
+ raise Asimov::QuotaExceededError,
105
+ msg
106
+ end
107
+
108
+ if msg.start_with?(RATE_LIMIT_REACHED_MESSAGE)
109
+ raise Asimov::RateLimitError,
110
+ msg
111
+ end
112
+
113
+ if msg.start_with?(ENGINE_OVERLOADED_MESSAGE)
114
+ raise Asimov::ApiOverloadedError,
115
+ msg
116
+ end
117
+
118
+ raise Asimov::TooManyRequestsError, msg
119
+ end
120
+ private_class_method :match_429
121
+ private_constant :QUOTA_EXCEEDED_MESSAGE
122
+ private_constant :RATE_LIMIT_REACHED_MESSAGE
123
+ private_constant :ENGINE_OVERLOADED_MESSAGE
81
124
 
82
125
  def self.match_invalid_parameter_value?(msg)
83
126
  msg.include?(INVALID_PARAMETER_VALUE_STRING) ||
@@ -85,17 +128,15 @@ module Asimov
85
128
  msg.include?(ABOVE_MAXIMUM_STRING) ||
86
129
  msg.start_with?(INVALID_PARAMETER_VALUE_PREFIX_2)
87
130
  end
131
+ private_class_method :match_invalid_parameter_value?
88
132
 
89
133
  def self.match_404(resp)
90
134
  return unless resp.code == 404
91
135
 
92
136
  msg = error_message(resp)
93
- # {"error"=>{"message"=>"That model does not exist", "type"=>"invalid_request_error",
94
- # "param"=>"model", "code"=>nil}}
95
- # {"error"=>{"message"=>"No such File object: file-BWp1k9EVJRq5Ybjr3Mb0tDXW",
96
- # "type"=>"invalid_request_error", "param"=>"id", "code"=>nil}}
97
137
  raise Asimov::NotFoundError, msg
98
138
  end
139
+ private_class_method :match_404
99
140
 
100
141
  # rubocop:enable Naming/VariableNumber
101
142
  # rubocop:enable Metrics/MethodLength
@@ -110,6 +151,7 @@ module Asimov
110
151
 
111
152
  pr["error"]["message"] || ""
112
153
  end
154
+ private_class_method :error_message
113
155
  end
114
156
  end
115
157
  end
@@ -0,0 +1,52 @@
1
+ require_relative "../utils/chat_messages_validator"
2
+
3
+ module Asimov
4
+ module ApiV1
5
+ ##
6
+ # Class interface for API methods in the "/audio" URI subspace.
7
+ ##
8
+ class Audio < Base
9
+ RESOURCE = "audio".freeze
10
+
11
+ ##
12
+ # Creates a transcription request with the specified parameters.
13
+ #
14
+ # @param [String] model the model to use for the completion
15
+ # @param [Hash] parameters the set of parameters being passed to the API
16
+ ##
17
+ def create_transcription(file:, model:, parameters: {})
18
+ raise MissingRequiredParameterError.new(:model) unless model
19
+
20
+ rest_create_w_multipart_params(resource: [RESOURCE, "transcriptions"],
21
+ parameters:
22
+ open_file(parameters.merge({
23
+ file: file, model: model
24
+ })))
25
+ end
26
+
27
+ ##
28
+ # Creates a transcription request with the specified parameters.
29
+ #
30
+ # @param [String] model the model to use for the completion
31
+ # @param [Hash] parameters the set of parameters being passed to the API
32
+ ##
33
+ def create_translation(file:, model:, parameters: {})
34
+ raise MissingRequiredParameterError.new(:model) unless model
35
+
36
+ rest_create_w_multipart_params(resource: [RESOURCE, "translations"],
37
+ parameters:
38
+ open_file(parameters.merge({
39
+ file: file, model: model
40
+ })))
41
+ end
42
+
43
+ private
44
+
45
+ def open_file(parameters)
46
+ raise MissingRequiredParameterError.new(:file) unless parameters[:file]
47
+
48
+ parameters.merge(file: Utils::FileManager.open(parameters[:file]))
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,6 +2,10 @@ require_relative "./api_error_translator"
2
2
  require_relative "./network_error_translator"
3
3
 
4
4
  module Asimov
5
+ ##
6
+ # Classes and method associated with the requests, responses, and
7
+ # errors associated with v1 of the OpenAI API.
8
+ ##
5
9
  module ApiV1
6
10
  ##
7
11
  # Base class for API interface implementations. Currently
@@ -11,65 +15,115 @@ module Asimov
11
15
  extend Forwardable
12
16
  include HTTParty
13
17
 
14
- base_uri "https://api.openai.com/v1"
15
-
16
18
  def initialize(client: nil)
17
19
  @client = client
18
20
  end
19
- def_delegators :@client, :headers, :request_options
21
+ def_delegators :@client, :headers, :request_options, :openai_api_base
20
22
 
21
- def http_delete(path:)
23
+ ##
24
+ # Executes a REST index for the specified resource
25
+ #
26
+ # @param [String] resource the pluralized resource name
27
+ ##
28
+ def rest_index(resource:)
29
+ wrap_response_with_error_handling do
30
+ self.class.get(
31
+ absolute_path("/#{Array(resource).join('/')}"),
32
+ { headers: headers }.merge!(request_options)
33
+ )
34
+ end
35
+ end
36
+
37
+ ##
38
+ # Executes a REST delete on the specified resource.
39
+ #
40
+ # @param [String] resource the pluralized resource name
41
+ # @param [String] id the id of the resource to delete
42
+ ##
43
+ def rest_delete(resource:, id:)
22
44
  wrap_response_with_error_handling do
23
45
  self.class.delete(
24
- path,
46
+ absolute_path("/#{resource}/#{CGI.escape(id)}"),
25
47
  { headers: headers }.merge!(request_options)
26
48
  )
27
49
  end
28
50
  end
29
51
 
30
- def http_get(path:)
52
+ ##
53
+ # Executes a REST get on the specified resource.
54
+ #
55
+ # @param [String] resource the pluralized resource name
56
+ # @param [String] id the id of the resource get
57
+ ##
58
+ def rest_get(resource:, id:)
31
59
  wrap_response_with_error_handling do
32
60
  self.class.get(
33
- path,
61
+ absolute_path("/#{resource}/#{CGI.escape(id)}"),
34
62
  { headers: headers }.merge!(request_options)
35
63
  )
36
64
  end
37
65
  end
38
66
 
39
- def json_post(path:, parameters:)
67
+ ##
68
+ # Executes a REST create with JSON-encoded parameters for the specified
69
+ # resource.
70
+ #
71
+ # @param [String] the resource to be created.
72
+ # @param [Hash] parameters the parameters to include with the request
73
+ # to create the resource
74
+ ##
75
+ def rest_create_w_json_params(resource:, parameters:)
40
76
  wrap_response_with_error_handling do
41
77
  self.class.post(
42
- path,
78
+ absolute_path("/#{Array(resource).join('/')}"),
43
79
  { headers: headers,
44
80
  body: parameters&.to_json }.merge!(request_options)
45
81
  )
46
82
  end
47
83
  end
48
84
 
49
- def multipart_post(path:, parameters: nil)
85
+ ##
86
+ # Executes a REST create with multipart-encoded parameters for the specified
87
+ # resource.
88
+ #
89
+ # @param [String] the resource to be created.
90
+ # @param [Hash] parameters the optional parameters to include with the request
91
+ # to create the resource
92
+ ##
93
+ def rest_create_w_multipart_params(resource:, parameters: nil)
50
94
  wrap_response_with_error_handling do
51
95
  self.class.post(
52
- path,
96
+ absolute_path("/#{Array(resource).join('/')}"),
53
97
  { headers: headers("multipart/form-data"),
54
98
  body: parameters }.merge!(request_options)
55
99
  )
56
100
  end
57
101
  end
58
102
 
59
- def http_streamed_download(path:, writer:)
60
- self.class.get(path,
103
+ ##
104
+ # Executes an REST get on the specified path, streaming the resulting body
105
+ # to the writer in case of success.
106
+ #
107
+ # @param [Array] resource the resource path elements as an array
108
+ # @param [Writer] writer an object, typically a File, that responds to a `write` method
109
+ ##
110
+ def rest_get_streamed_download(resource:, writer:)
111
+ self.class.get(absolute_path("/#{Array(resource).join('/')}"),
61
112
  { headers: headers,
62
113
  stream_body: true }.merge!(request_options)) do |fragment|
63
114
  fragment.code == 200 ? writer.write(fragment) : check_for_api_error(fragment)
64
115
  end
65
- rescue Asimov::RequestError => e
66
- # Raise any translated API errors
67
- raise e
68
116
  rescue StandardError => e
69
- # Otherwise translate the error to a network error
117
+ # Any error raised by the HTTP call is a network error
70
118
  NetworkErrorTranslator.translate(e)
71
119
  end
72
120
 
121
+ private
122
+
123
+ def absolute_path(path)
124
+ "#{openai_api_base}#{path}"
125
+ end
126
+
73
127
  def wrap_response_with_error_handling
74
128
  resp = begin
75
129
  yield
@@ -84,7 +138,6 @@ module Asimov
84
138
  return if resp.code == 200
85
139
 
86
140
  ApiErrorTranslator.translate(resp)
87
- resp
88
141
  end
89
142
  end
90
143
  end
@@ -0,0 +1,29 @@
1
+ require_relative "../utils/chat_messages_validator"
2
+
3
+ module Asimov
4
+ module ApiV1
5
+ ##
6
+ # Class interface for API methods in the "/chat" URI subspace.
7
+ ##
8
+ class Chat < Base
9
+ RESOURCE = "chat".freeze
10
+
11
+ ##
12
+ # Creates a completion request with the specified parameters.
13
+ #
14
+ # @param [String] model the model to use for the completion
15
+ # @param [Hash] parameters the set of parameters being passed to the API
16
+ ##
17
+ def create_completions(model:, messages:, parameters: {})
18
+ raise MissingRequiredParameterError.new(:model) unless model
19
+ raise MissingRequiredParameterError.new(:messages) unless messages
20
+ raise StreamingResponseNotSupportedError if parameters[:stream]
21
+
22
+ messages = Utils::ChatMessagesValidator.validate_and_normalize(messages)
23
+ rest_create_w_json_params(resource: [RESOURCE, "completions"],
24
+ parameters: parameters.merge({ model: model,
25
+ messages: messages }))
26
+ end
27
+ end
28
+ end
29
+ end
@@ -4,10 +4,18 @@ module Asimov
4
4
  # Class interface for API methods in the "/completions" URI subspace.
5
5
  ##
6
6
  class Completions < Base
7
- def create(parameters:)
8
- raise MissingRequiredParameterError.new(:model) unless parameters[:model]
7
+ ##
8
+ # Creates a completion request with the specified parameters.
9
+ #
10
+ # @param [String] model the model to use for the completion
11
+ # @param [Hash] parameters the set of parameters being passed to the API
12
+ ##
13
+ def create(model:, parameters: {})
14
+ raise MissingRequiredParameterError.new(:model) unless model
15
+ raise StreamingResponseNotSupportedError if parameters[:stream]
9
16
 
10
- json_post(path: "/completions", parameters: parameters)
17
+ rest_create_w_json_params(resource: "completions",
18
+ parameters: parameters.merge({ model: model }))
11
19
  end
12
20
  end
13
21
  end
@@ -4,11 +4,18 @@ module Asimov
4
4
  # Class interface for API methods in the "/edits" URI subspace.
5
5
  ##
6
6
  class Edits < Base
7
- def create(parameters:)
8
- raise MissingRequiredParameterError.new(:model) unless parameters[:model]
9
- raise MissingRequiredParameterError.new(:instruction) unless parameters[:instruction]
7
+ ##
8
+ # Creates an edit resource with the specified parameters.
9
+ #
10
+ # @param [Hash] parameters the set of parameters being passed to the API
11
+ ##
12
+ def create(model:, instruction:, parameters: {})
13
+ raise MissingRequiredParameterError.new(:model) unless model
14
+ raise MissingRequiredParameterError.new(:instruction) unless instruction
10
15
 
11
- json_post(path: "/edits", parameters: parameters)
16
+ rest_create_w_json_params(resource: "edits",
17
+ parameters: parameters.merge({ model: model,
18
+ instruction: instruction }))
12
19
  end
13
20
  end
14
21
  end
@@ -4,11 +4,22 @@ module Asimov
4
4
  # Class interface for API methods in the "/embeddings" URI subspace.
5
5
  ##
6
6
  class Embeddings < Base
7
- def create(parameters:)
8
- raise MissingRequiredParameterError.new(:model) unless parameters[:model]
9
- raise MissingRequiredParameterError.new(:input) unless parameters[:input]
7
+ ##
8
+ # Creates an embedding resource with the specified parameters.
9
+ #
10
+ # @param [String] model the id for the model used to create the embedding
11
+ # @param [String] parameters the (optional) additional parameters being
12
+ # provided to inform embedding creation.
13
+ # @param [Hash] parameters the set of parameters being passed to the API
14
+ ##
15
+ def create(model:, input:, parameters: {})
16
+ raise MissingRequiredParameterError.new(:model) unless model
17
+ raise MissingRequiredParameterError.new(:input) unless input
10
18
 
11
- json_post(path: "/embeddings", parameters: parameters)
19
+ rest_create_w_json_params(resource: "embeddings",
20
+ parameters: parameters.merge({
21
+ model: model, input: input
22
+ }))
12
23
  end
13
24
  end
14
25
  end
@@ -10,37 +10,62 @@ module Asimov
10
10
  # Class interface for API methods in the "/files" URI subspace.
11
11
  ##
12
12
  class Files < Base
13
- URI_PREFIX = "/files".freeze
13
+ RESOURCE = "files".freeze
14
+ private_constant :RESOURCE
14
15
 
15
16
  ##
16
17
  # Lists files that have been uploaded to OpenAI
17
18
  ##
18
19
  def list
19
- http_get(path: URI_PREFIX)
20
+ rest_index(resource: RESOURCE)
20
21
  end
21
22
 
22
- def upload(parameters:)
23
- raise MissingRequiredParameterError.new(:file) unless parameters[:file]
24
- raise MissingRequiredParameterError.new(:purpose) unless parameters[:purpose]
23
+ ##
24
+ # Uploads a file to the /files endpoint with the specified parameters.
25
+ #
26
+ # @param [String] file file name or a File-like object to be uploaded
27
+ # @param [Hash] parameters the set of parameters being passed to the API
28
+ ##
29
+ def upload(file:, purpose:, parameters: {})
30
+ raise MissingRequiredParameterError.new(:file) unless file
31
+ raise MissingRequiredParameterError.new(:purpose) unless purpose
25
32
 
26
- validate(parameters[:file], parameters[:purpose])
33
+ validate(file, purpose)
27
34
 
28
- multipart_post(
29
- path: URI_PREFIX,
30
- parameters: parameters.merge(file: Utils::FileManager.open(parameters[:file]))
35
+ rest_create_w_multipart_params(
36
+ resource: RESOURCE,
37
+ parameters: parameters.merge(file: Utils::FileManager.open(file), purpose: purpose)
31
38
  )
32
39
  end
33
40
 
41
+ ##
42
+ # Retrieves the file with the specified file_id from OpenAI.
43
+ #
44
+ # @param [String] file_id the id of the file to be retrieved
45
+ ##
34
46
  def retrieve(file_id:)
35
- http_get(path: "#{URI_PREFIX}/#{file_id}")
47
+ rest_get(resource: RESOURCE, id: file_id)
36
48
  end
37
49
 
50
+ ##
51
+ # Deletes the file with the specified file_id from OpenAI.
52
+ #
53
+ # @param [String] file_id the id of the file to be deleted
54
+ ##
38
55
  def delete(file_id:)
39
- http_delete(path: "#{URI_PREFIX}/#{file_id}")
56
+ rest_delete(resource: RESOURCE, id: file_id)
40
57
  end
41
58
 
59
+ ##
60
+ # Retrieves the contents of the file with the specified file_id from OpenAI
61
+ # and passes those contents to the writer in a chunked manner.
62
+ #
63
+ # @param [String] file_id the id of the file to be retrieved
64
+ # @param [Writer] writer the Writer that will process the chunked content
65
+ # as it is received from the API
66
+ ##
42
67
  def content(file_id:, writer:)
43
- http_streamed_download(path: "#{URI_PREFIX}/#{file_id}/content", writer: writer)
68
+ rest_get_streamed_download(resource: [RESOURCE, file_id, "content"], writer: writer)
44
69
  end
45
70
 
46
71
  private