asimov 0.1.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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