asimov 0.1.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +27 -1
- data/Gemfile +10 -1
- data/README.md +1 -0
- data/lib/asimov/api_v1/api_error_translator.rb +70 -28
- data/lib/asimov/api_v1/base.rb +39 -5
- data/lib/asimov/api_v1/completions.rb +10 -3
- data/lib/asimov/api_v1/edits.rb +11 -4
- data/lib/asimov/api_v1/embeddings.rb +9 -4
- data/lib/asimov/api_v1/files.rb +30 -5
- data/lib/asimov/api_v1/finetunes.rb +29 -4
- data/lib/asimov/api_v1/images.rb +35 -11
- data/lib/asimov/api_v1/models.rb +17 -0
- data/lib/asimov/api_v1/moderations.rb +9 -3
- data/lib/asimov/configuration.rb +10 -0
- data/lib/asimov/error.rb +83 -1
- data/lib/asimov/utils/classifications_file_validator.rb +8 -0
- data/lib/asimov/utils/file_manager.rb +15 -3
- data/lib/asimov/utils/jsonl_validator.rb +12 -2
- data/lib/asimov/utils/request_options_validator.rb +13 -0
- data/lib/asimov/utils/text_entry_file_validator.rb +2 -0
- data/lib/asimov/utils/training_file_validator.rb +2 -0
- data/lib/asimov/version.rb +2 -1
- data/lib/asimov.rb +1 -1
- metadata +7 -103
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b71726c0c67788a649dc1c01ca5763ab13d41f31118d6f8b093795a3a92e0ca8
|
4
|
+
data.tar.gz: 5d5eb6dd51099aadf9418ff1c11c7ea6688a06a5e07b5a39bd376a48538ac79a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b20449b89964c2b2b77de68726aef47bc09e65871ba04c80ad2f607e58bc5b6322ff6106c801f01ef68377056a55010d74e6481f883ad57793eca5366a438acd
|
7
|
+
data.tar.gz: fdfe13acb265341e7c568874a8696ce520b6da8fbdc77ae694b3ba56deb97d44a65aac82935a3ad3f356258aa0375358349540c17cd54bfd0f9a3a746d40b807
|
data/CHANGELOG.md
CHANGED
@@ -5,9 +5,35 @@ 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.0.0] - 2023-01-24
|
11
|
+
|
12
|
+
This version has complete coverage of the OpenAI API (except for stream: true behavior), has
|
13
|
+
no known errors and has full test coverage. At this point there are no anticipated changes
|
14
|
+
to existing endpoints.
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
|
18
|
+
- Fixed handling of authentication errors
|
19
|
+
|
20
|
+
### Changed
|
21
|
+
|
22
|
+
- Properly distinguished public and private methods to ensure proper documentation.
|
23
|
+
- Renamed events to list_events for consistency
|
24
|
+
- Updated file arguments to take path strings or File-like objects
|
25
|
+
- Adjusted endpoints to make required parameters more explicit
|
26
|
+
|
27
|
+
### Added
|
28
|
+
|
29
|
+
- Code level documentation for all public classes and methods.
|
30
|
+
- Error for the unsupported stream: true case
|
31
|
+
- Error mapping for 409 and 429 errors
|
32
|
+
- Specs for authentication
|
33
|
+
|
8
34
|
## [0.1.0] - 2023-01-14
|
9
35
|
|
10
|
-
This initial version of the gem is now suitable for use. API endpoint method naming may shift slightly
|
36
|
+
This initial version of the gem is now suitable for use. API endpoint method naming may shift slightly.
|
11
37
|
|
12
38
|
### Changed
|
13
39
|
|
data/Gemfile
CHANGED
@@ -2,4 +2,13 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
|
5
|
+
group :development, :test do
|
6
|
+
gem "rake", "~> 13.0"
|
7
|
+
gem "rspec", "~> 3.12"
|
8
|
+
gem "rubocop", "~> 1.44"
|
9
|
+
gem "rubocop-rake"
|
10
|
+
gem "rubocop-rspec"
|
11
|
+
gem "simplecov", require: false
|
12
|
+
gem "vcr", "~> 6.1.0"
|
13
|
+
gem "webmock", "~> 3.18.1"
|
14
|
+
end
|
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Asimov
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/asimov)
|
3
4
|
[](https://github.com/petergoldstein/asimov/blob/main/LICENSE.txt)
|
4
5
|
[](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?(
|
43
|
+
raise Asimov::InvalidOrganizationError, msg if msg.start_with?(INVALID_ORGANIZATION_PREFIX)
|
27
44
|
|
28
|
-
raise Asimov::
|
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
|
data/lib/asimov/api_v1/base.rb
CHANGED
@@ -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
|
@@ -18,6 +22,12 @@ module Asimov
|
|
18
22
|
end
|
19
23
|
def_delegators :@client, :headers, :request_options
|
20
24
|
|
25
|
+
##
|
26
|
+
# Executes an HTTP DELETE on the specified path.
|
27
|
+
#
|
28
|
+
# @param [String] path the URI (when combined with the
|
29
|
+
# base_uri) against which the DELETE is executed.
|
30
|
+
##
|
21
31
|
def http_delete(path:)
|
22
32
|
wrap_response_with_error_handling do
|
23
33
|
self.class.delete(
|
@@ -27,6 +37,12 @@ module Asimov
|
|
27
37
|
end
|
28
38
|
end
|
29
39
|
|
40
|
+
##
|
41
|
+
# Executes an HTTP GET on the specified path.
|
42
|
+
#
|
43
|
+
# @param [String] path the URI (when combined with the
|
44
|
+
# base_uri) against which the GET is executed.
|
45
|
+
##
|
30
46
|
def http_get(path:)
|
31
47
|
wrap_response_with_error_handling do
|
32
48
|
self.class.get(
|
@@ -36,6 +52,12 @@ module Asimov
|
|
36
52
|
end
|
37
53
|
end
|
38
54
|
|
55
|
+
##
|
56
|
+
# Executes an HTTP POST with JSON-encoded parameters on the specified path.
|
57
|
+
#
|
58
|
+
# @param [String] path the URI (when combined with the
|
59
|
+
# base_uri) against which the POST is executed.
|
60
|
+
##
|
39
61
|
def json_post(path:, parameters:)
|
40
62
|
wrap_response_with_error_handling do
|
41
63
|
self.class.post(
|
@@ -46,6 +68,12 @@ module Asimov
|
|
46
68
|
end
|
47
69
|
end
|
48
70
|
|
71
|
+
##
|
72
|
+
# Executes an HTTP POST with multipart encoded parameters on the specified path.
|
73
|
+
#
|
74
|
+
# @param [String] path the URI (when combined with the
|
75
|
+
# base_uri) against which the POST is executed.
|
76
|
+
##
|
49
77
|
def multipart_post(path:, parameters: nil)
|
50
78
|
wrap_response_with_error_handling do
|
51
79
|
self.class.post(
|
@@ -56,20 +84,27 @@ module Asimov
|
|
56
84
|
end
|
57
85
|
end
|
58
86
|
|
87
|
+
##
|
88
|
+
# Executes an HTTP GET on the specified path, streaming the resulting body
|
89
|
+
# to the writer in case of success.
|
90
|
+
#
|
91
|
+
# @param [String] path the URI (when combined with the
|
92
|
+
# base_uri) against which the POST is executed.
|
93
|
+
# @param [Writer] writer an object, typically a File, that responds to a `write` method
|
94
|
+
##
|
59
95
|
def http_streamed_download(path:, writer:)
|
60
96
|
self.class.get(path,
|
61
97
|
{ headers: headers,
|
62
98
|
stream_body: true }.merge!(request_options)) do |fragment|
|
63
99
|
fragment.code == 200 ? writer.write(fragment) : check_for_api_error(fragment)
|
64
100
|
end
|
65
|
-
rescue Asimov::RequestError => e
|
66
|
-
# Raise any translated API errors
|
67
|
-
raise e
|
68
101
|
rescue StandardError => e
|
69
|
-
#
|
102
|
+
# Any error raised by the HTTP call is a network error
|
70
103
|
NetworkErrorTranslator.translate(e)
|
71
104
|
end
|
72
105
|
|
106
|
+
private
|
107
|
+
|
73
108
|
def wrap_response_with_error_handling
|
74
109
|
resp = begin
|
75
110
|
yield
|
@@ -84,7 +119,6 @@ module Asimov
|
|
84
119
|
return if resp.code == 200
|
85
120
|
|
86
121
|
ApiErrorTranslator.translate(resp)
|
87
|
-
resp
|
88
122
|
end
|
89
123
|
end
|
90
124
|
end
|
@@ -4,10 +4,17 @@ module Asimov
|
|
4
4
|
# Class interface for API methods in the "/completions" URI subspace.
|
5
5
|
##
|
6
6
|
class Completions < Base
|
7
|
-
|
8
|
-
|
7
|
+
##
|
8
|
+
# Calls the /completions POST endpoint 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
|
+
json_post(path: "/completions", parameters: parameters.merge({ model: model }))
|
11
18
|
end
|
12
19
|
end
|
13
20
|
end
|
data/lib/asimov/api_v1/edits.rb
CHANGED
@@ -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
|
-
|
8
|
-
|
9
|
-
|
7
|
+
##
|
8
|
+
# Calls the /edits POST endpoint 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",
|
16
|
+
json_post(path: "/edits",
|
17
|
+
parameters: parameters.merge({ model: model,
|
18
|
+
instruction: instruction }))
|
12
19
|
end
|
13
20
|
end
|
14
21
|
end
|
@@ -4,11 +4,16 @@ module Asimov
|
|
4
4
|
# Class interface for API methods in the "/embeddings" URI subspace.
|
5
5
|
##
|
6
6
|
class Embeddings < Base
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
##
|
8
|
+
# Calls the /embeddings POST endpoint with the specified parameters.
|
9
|
+
#
|
10
|
+
# @param [Hash] parameters the set of parameters being passed to the API
|
11
|
+
##
|
12
|
+
def create(model:, input:, parameters: {})
|
13
|
+
raise MissingRequiredParameterError.new(:model) unless model
|
14
|
+
raise MissingRequiredParameterError.new(:input) unless input
|
10
15
|
|
11
|
-
json_post(path: "/embeddings", parameters: parameters)
|
16
|
+
json_post(path: "/embeddings", parameters: parameters.merge({ model: model, input: input }))
|
12
17
|
end
|
13
18
|
end
|
14
19
|
end
|
data/lib/asimov/api_v1/files.rb
CHANGED
@@ -11,6 +11,7 @@ module Asimov
|
|
11
11
|
##
|
12
12
|
class Files < Base
|
13
13
|
URI_PREFIX = "/files".freeze
|
14
|
+
private_constant :URI_PREFIX
|
14
15
|
|
15
16
|
##
|
16
17
|
# Lists files that have been uploaded to OpenAI
|
@@ -19,26 +20,50 @@ module Asimov
|
|
19
20
|
http_get(path: URI_PREFIX)
|
20
21
|
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
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(
|
33
|
+
validate(file, purpose)
|
27
34
|
|
28
35
|
multipart_post(
|
29
36
|
path: URI_PREFIX,
|
30
|
-
parameters: parameters.merge(file: Utils::FileManager.open(
|
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
47
|
http_get(path: "#{URI_PREFIX}/#{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
56
|
http_delete(path: "#{URI_PREFIX}/#{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
68
|
http_streamed_download(path: "#{URI_PREFIX}/#{file_id}/content", writer: writer)
|
44
69
|
end
|
@@ -5,26 +5,51 @@ module Asimov
|
|
5
5
|
##
|
6
6
|
class Finetunes < Base
|
7
7
|
URI_PREFIX = "/fine-tunes".freeze
|
8
|
+
private_constant :URI_PREFIX
|
8
9
|
|
10
|
+
##
|
11
|
+
# Lists the set of fine-tuning jobs for this API key and (optionally) organization.
|
12
|
+
##
|
9
13
|
def list
|
10
14
|
http_get(path: URI_PREFIX)
|
11
15
|
end
|
12
16
|
|
13
|
-
|
14
|
-
|
17
|
+
##
|
18
|
+
# Creates a new fine-tuning job with the specified parameters.
|
19
|
+
#
|
20
|
+
# @param [String] training_file the id of the training file to use for fine tuning
|
21
|
+
# @param [Hash] parameters the parameters passed with the fine tuning job
|
22
|
+
##
|
23
|
+
def create(training_file:, parameters: {})
|
24
|
+
raise MissingRequiredParameterError.new(:training_file) unless training_file
|
15
25
|
|
16
|
-
json_post(path: URI_PREFIX, parameters: parameters)
|
26
|
+
json_post(path: URI_PREFIX, parameters: parameters.merge(training_file: training_file))
|
17
27
|
end
|
18
28
|
|
29
|
+
##
|
30
|
+
# Retrieves the details of a fine-tuning job with the specified id.
|
31
|
+
#
|
32
|
+
# @param [String] fine_tune_id the id of fine tuning job
|
33
|
+
##
|
19
34
|
def retrieve(fine_tune_id:)
|
20
35
|
http_get(path: "#{URI_PREFIX}/#{fine_tune_id}")
|
21
36
|
end
|
22
37
|
|
38
|
+
##
|
39
|
+
# Cancels the details of a fine-tuning job with the specified id.
|
40
|
+
#
|
41
|
+
# @param [String] fine_tune_id the id of fine tuning job
|
42
|
+
##
|
23
43
|
def cancel(fine_tune_id:)
|
24
44
|
multipart_post(path: "#{URI_PREFIX}/#{fine_tune_id}/cancel")
|
25
45
|
end
|
26
46
|
|
27
|
-
|
47
|
+
##
|
48
|
+
# Lists the events associated with a fine-tuning job with the specified id.
|
49
|
+
#
|
50
|
+
# @param [String] fine_tune_id the id of fine tuning job
|
51
|
+
##
|
52
|
+
def list_events(fine_tune_id:)
|
28
53
|
http_get(path: "#{URI_PREFIX}/#{fine_tune_id}/events")
|
29
54
|
end
|
30
55
|
end
|
data/lib/asimov/api_v1/images.rb
CHANGED
@@ -7,21 +7,45 @@ module Asimov
|
|
7
7
|
##
|
8
8
|
class Images < Base
|
9
9
|
URI_PREFIX = "/images".freeze
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
private_constant :URI_PREFIX
|
11
|
+
|
12
|
+
##
|
13
|
+
# Creates an image using the specified prompt.
|
14
|
+
#
|
15
|
+
# @param [String] prompt the prompt used to create the image
|
16
|
+
# @param [Hash] parameters additional parameters passed to the API
|
17
|
+
##
|
18
|
+
def create(prompt:, parameters: {})
|
19
|
+
raise MissingRequiredParameterError.new(:prompt) unless prompt
|
20
|
+
|
21
|
+
json_post(path: "#{URI_PREFIX}/generations",
|
22
|
+
parameters: parameters.merge({ prompt: prompt }))
|
15
23
|
end
|
16
24
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
25
|
+
##
|
26
|
+
# Creates edits of the specified image based on the prompt.
|
27
|
+
#
|
28
|
+
# @param [String] image file name or a File-like object of the base image
|
29
|
+
# @param [String] prompt the prompt used to guide the edit
|
30
|
+
# @param [Hash] parameters additional parameters passed to the API
|
31
|
+
##
|
32
|
+
def create_edit(image:, prompt:, parameters: {})
|
33
|
+
raise MissingRequiredParameterError.new(:prompt) unless prompt
|
34
|
+
|
35
|
+
multipart_post(path: "#{URI_PREFIX}/edits",
|
36
|
+
parameters: open_files(parameters.merge({ image: image, prompt: prompt })))
|
21
37
|
end
|
22
38
|
|
23
|
-
|
24
|
-
|
39
|
+
##
|
40
|
+
# Creates variations of the specified image.
|
41
|
+
#
|
42
|
+
# @param [String] image file name or a File-like object of the base image
|
43
|
+
# @param [Hash] parameters additional parameters passed to the API
|
44
|
+
# @option parameters [String] :mask mask file name or a File-like object
|
45
|
+
##
|
46
|
+
def create_variation(image:, parameters: {})
|
47
|
+
multipart_post(path: "#{URI_PREFIX}/variations",
|
48
|
+
parameters: open_files(parameters.merge({ image: image })))
|
25
49
|
end
|
26
50
|
|
27
51
|
private
|
data/lib/asimov/api_v1/models.rb
CHANGED
@@ -5,15 +5,32 @@ module Asimov
|
|
5
5
|
##
|
6
6
|
class Models < Base
|
7
7
|
URI_PREFIX = "/models".freeze
|
8
|
+
private_constant :URI_PREFIX
|
8
9
|
|
10
|
+
##
|
11
|
+
# Lists the models accessible to this combination of OpenAI API
|
12
|
+
# key and organization id.
|
13
|
+
##
|
9
14
|
def list
|
10
15
|
http_get(path: URI_PREFIX)
|
11
16
|
end
|
12
17
|
|
18
|
+
##
|
19
|
+
# Retrieve information about the model with the specified
|
20
|
+
# model_id.
|
21
|
+
#
|
22
|
+
# @param [String] model_id the id of the model to be retrieved
|
23
|
+
##
|
13
24
|
def retrieve(model_id:)
|
14
25
|
http_get(path: "#{URI_PREFIX}/#{model_id}")
|
15
26
|
end
|
16
27
|
|
28
|
+
##
|
29
|
+
# Deletes the model with the specified model_id. Only
|
30
|
+
# works on models created via fine tuning.
|
31
|
+
#
|
32
|
+
# @param [String] model_id the id of the model to be deleted
|
33
|
+
##
|
17
34
|
def delete(model_id:)
|
18
35
|
http_delete(path: "#{URI_PREFIX}/#{model_id}")
|
19
36
|
end
|
@@ -4,10 +4,16 @@ module Asimov
|
|
4
4
|
# Class interface for API methods in the "/moderations" URI subspace.
|
5
5
|
##
|
6
6
|
class Moderations < Base
|
7
|
-
|
8
|
-
|
7
|
+
##
|
8
|
+
# Calls the /moderations POST endpoint with the specified parameters.
|
9
|
+
#
|
10
|
+
# @param [String] input the text being evaluated by the API
|
11
|
+
# @param [Hash] parameters the set of parameters being passed to the API
|
12
|
+
##
|
13
|
+
def create(input:, parameters: {})
|
14
|
+
raise MissingRequiredParameterError.new(:input) unless input
|
9
15
|
|
10
|
-
json_post(path: "/moderations", parameters: parameters)
|
16
|
+
json_post(path: "/moderations", parameters: parameters.merge({ input: input }))
|
11
17
|
end
|
12
18
|
end
|
13
19
|
end
|
data/lib/asimov/configuration.rb
CHANGED
@@ -11,16 +11,26 @@ module Asimov
|
|
11
11
|
|
12
12
|
attr_reader :request_options
|
13
13
|
|
14
|
+
##
|
15
|
+
# Initializes the Configuration object and resets it to default values.
|
16
|
+
##
|
14
17
|
def initialize
|
15
18
|
reset
|
16
19
|
end
|
17
20
|
|
21
|
+
##
|
22
|
+
# Reset the configuration to default values. Mostly used for testing.
|
23
|
+
##
|
18
24
|
def reset
|
19
25
|
@api_key = nil
|
20
26
|
@organization_id = nil
|
21
27
|
@request_options = {}
|
22
28
|
end
|
23
29
|
|
30
|
+
##
|
31
|
+
# Sets the request_options on the Configuration. Typically not invoked
|
32
|
+
# directly, but rather through use of `Asimov.configure`.
|
33
|
+
##
|
24
34
|
def request_options=(val)
|
25
35
|
@request_options = Utils::RequestOptionsValidator.validate(val)
|
26
36
|
end
|
data/lib/asimov/error.rb
CHANGED
@@ -9,6 +9,10 @@ module Asimov
|
|
9
9
|
##
|
10
10
|
class ConfigurationError < Error; end
|
11
11
|
|
12
|
+
##
|
13
|
+
# Error that occurs when there is no configured
|
14
|
+
# API key for a newly created Asimov::Client.
|
15
|
+
##
|
12
16
|
class MissingApiKeyError < ConfigurationError; end
|
13
17
|
|
14
18
|
##
|
@@ -56,6 +60,18 @@ module Asimov
|
|
56
60
|
##
|
57
61
|
class AuthorizationError < RequestError; end
|
58
62
|
|
63
|
+
##
|
64
|
+
# Error that occurs because the provided API key is not
|
65
|
+
# valid.
|
66
|
+
##
|
67
|
+
class InvalidApiKeyError < AuthorizationError; end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Error that occurs because the provided API key is not
|
71
|
+
# valid.
|
72
|
+
##
|
73
|
+
class InvalidOrganizationError < AuthorizationError; end
|
74
|
+
|
59
75
|
##
|
60
76
|
# Errors that occur because of issues with the
|
61
77
|
# parameters of a request. Typically these correspond
|
@@ -87,26 +103,54 @@ module Asimov
|
|
87
103
|
@system_message = system_message
|
88
104
|
end
|
89
105
|
|
106
|
+
##
|
107
|
+
# Returns the error message based on the file name and the wrapped error.
|
108
|
+
##
|
90
109
|
def message
|
91
110
|
"The file #{@file_name} could not be opened for upload because of the " \
|
92
111
|
"following error - #{@system_message}."
|
93
112
|
end
|
94
113
|
end
|
95
114
|
|
115
|
+
##
|
116
|
+
# Error that occurs when a JSONL file is expected
|
117
|
+
# and it cannot be parsed.
|
118
|
+
##
|
96
119
|
class JsonlFileCannotBeParsedError < FileDataError; end
|
97
120
|
|
121
|
+
##
|
122
|
+
# Error that occurs when an invalid training example
|
123
|
+
# is found in a training file.
|
124
|
+
##
|
98
125
|
class InvalidTrainingExampleError < FileDataError; end
|
99
126
|
|
127
|
+
##
|
128
|
+
# Error that occurs when an invalid text entry
|
129
|
+
# is found in a text entry file.
|
130
|
+
##
|
100
131
|
class InvalidTextEntryError < FileDataError; end
|
101
132
|
|
133
|
+
##
|
134
|
+
# Error that occurs when an invalid classification
|
135
|
+
# is found in a classifications file.
|
136
|
+
##
|
102
137
|
class InvalidClassificationError < FileDataError; end
|
103
138
|
|
139
|
+
##
|
140
|
+
# Error that occurs when an invalid value is provided
|
141
|
+
# for an expected parameter in a request.
|
142
|
+
##
|
104
143
|
class InvalidParameterValueError < RequestError; end
|
105
144
|
|
145
|
+
##
|
146
|
+
# Error that occurs when an unexpected parameter is
|
147
|
+
# provided in a request.
|
148
|
+
##
|
106
149
|
class UnsupportedParameterError < RequestError; end
|
107
150
|
|
108
151
|
##
|
109
|
-
#
|
152
|
+
# Error raised when a required parameter is not included
|
153
|
+
# in the request.
|
110
154
|
##
|
111
155
|
class MissingRequiredParameterError < RequestError
|
112
156
|
def initialize(parameter_name)
|
@@ -114,6 +158,9 @@ module Asimov
|
|
114
158
|
@parameter_name = parameter_name
|
115
159
|
end
|
116
160
|
|
161
|
+
##
|
162
|
+
# Returns the error message based on the missing parameter name.
|
163
|
+
##
|
117
164
|
def message
|
118
165
|
"The parameter #{@parameter_name} is required."
|
119
166
|
end
|
@@ -126,4 +173,39 @@ module Asimov
|
|
126
173
|
# an object in the OpenAI system.
|
127
174
|
##
|
128
175
|
class NotFoundError < RequestError; end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Errors that occur because the OpenAI API returned
|
179
|
+
# an HTTP code 429. This typically occurs because
|
180
|
+
# you have hit a rate limit or quota limit, or
|
181
|
+
# because the engine is overloaded.
|
182
|
+
##
|
183
|
+
class TooManyRequestsError < RequestError; end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Error that occurs when the quota for an API key
|
187
|
+
# is exceeded.
|
188
|
+
##
|
189
|
+
class QuotaExceededError < TooManyRequestsError; end
|
190
|
+
|
191
|
+
##
|
192
|
+
# Error that occurs when the rate limit for requests
|
193
|
+
# is exceeded.
|
194
|
+
##
|
195
|
+
class RateLimitError < TooManyRequestsError; end
|
196
|
+
|
197
|
+
##
|
198
|
+
# Error that occurs when the API itself is
|
199
|
+
# overloaded and temporarily cannot accept additional
|
200
|
+
# requests.
|
201
|
+
##
|
202
|
+
class ApiOverloadedError < TooManyRequestsError; end
|
203
|
+
|
204
|
+
##
|
205
|
+
# Raised when a non-false stream parameter is passed
|
206
|
+
# to certain API methods. Processing of server-side
|
207
|
+
# events using the stream parameter is currently not
|
208
|
+
# supported.
|
209
|
+
##
|
210
|
+
class StreamingResponseNotSupportedError < RequestError; end
|
129
211
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
require "json"
|
2
2
|
|
3
3
|
module Asimov
|
4
|
+
##
|
5
|
+
# Set of utilities, primarily intended for internal library use.
|
6
|
+
##
|
4
7
|
module Utils
|
5
8
|
##
|
6
9
|
# Validates that a file is in the "classifications" format
|
@@ -8,8 +11,13 @@ module Asimov
|
|
8
11
|
# "text" and "label" keys for each line that have string
|
9
12
|
# values and an optional "metadata" key that can have
|
10
13
|
# any value. No other keys are permitted.
|
14
|
+
#
|
15
|
+
# The only method that clients should call on instances
|
16
|
+
# of this class is `validate`
|
11
17
|
##
|
12
18
|
class ClassificationsFileValidator < JsonlValidator
|
19
|
+
private
|
20
|
+
|
13
21
|
def validate_line(line, idx)
|
14
22
|
parsed = JSON.parse(line)
|
15
23
|
validate_classification(parsed, idx)
|
@@ -8,11 +8,23 @@ module Asimov
|
|
8
8
|
# Not intended for client use.
|
9
9
|
##
|
10
10
|
class FileManager
|
11
|
-
|
12
|
-
|
11
|
+
##
|
12
|
+
# Returns the file corresponding to the file_or_path. If the argument is a
|
13
|
+
# file, then just returns the argument. Otherwise calls File.open with the
|
14
|
+
# argument. Raises an error if the file cannot be opened.
|
15
|
+
#
|
16
|
+
# @param [File/String] file_or_path the path to the file to be opened
|
17
|
+
##
|
18
|
+
def self.open(file_or_path)
|
19
|
+
file?(file_or_path) ? file_or_path : File.open(file_or_path)
|
13
20
|
rescue SystemCallError => e
|
14
|
-
raise Asimov::FileCannotBeOpenedError.new(
|
21
|
+
raise Asimov::FileCannotBeOpenedError.new(file_or_path, e.message)
|
15
22
|
end
|
23
|
+
|
24
|
+
def self.file?(object)
|
25
|
+
object.respond_to?(:path) && object.respond_to?(:read)
|
26
|
+
end
|
27
|
+
private_class_method :file?
|
16
28
|
end
|
17
29
|
end
|
18
30
|
end
|
@@ -7,13 +7,23 @@ module Asimov
|
|
7
7
|
# more specific file validators.
|
8
8
|
##
|
9
9
|
class JsonlValidator
|
10
|
-
|
11
|
-
|
10
|
+
##
|
11
|
+
# Validate that the IO object (typically a File) is properly
|
12
|
+
# formatted. Entry method for this class and its subclasses.
|
13
|
+
# Required format will depend on the class.
|
14
|
+
#
|
15
|
+
# @param [IO] io IO object, typically a file, whose contents
|
16
|
+
# are to be format checked.
|
17
|
+
##
|
18
|
+
def validate(io)
|
19
|
+
io.each_line.with_index { |line, idx| validate_line(line, idx) }
|
12
20
|
true
|
13
21
|
rescue JSON::ParserError
|
14
22
|
raise JsonlFileCannotBeParsedError, "Expected file to have the JSONL format."
|
15
23
|
end
|
16
24
|
|
25
|
+
private
|
26
|
+
|
17
27
|
def validate_line(line, idx)
|
18
28
|
JSON.parse(line)
|
19
29
|
rescue JSON::ParserError
|
@@ -14,6 +14,16 @@ module Asimov
|
|
14
14
|
ALLOWED_OPTIONS = %i[timeout open_timeout read_timeout write_timeout local_host local_port
|
15
15
|
verify verify_peer ssl_ca_file ssl_ca_path ssl_version ciphers
|
16
16
|
http_proxyaddr http_proxyport http_proxyuser http_proxypass].freeze
|
17
|
+
|
18
|
+
##
|
19
|
+
# Validates that the options are allowed request
|
20
|
+
# options. Currently checks the keys - both that they are symbols and that
|
21
|
+
# they are allowed options. Does not validate values.
|
22
|
+
#
|
23
|
+
# Only entry point for this class.
|
24
|
+
#
|
25
|
+
# @param [Hash] options the set of request options to validate
|
26
|
+
##
|
17
27
|
def self.validate(options)
|
18
28
|
unless options.is_a?(Hash)
|
19
29
|
raise Asimov::ConfigurationError,
|
@@ -32,6 +42,7 @@ module Asimov
|
|
32
42
|
end
|
33
43
|
unsupported_options
|
34
44
|
end
|
45
|
+
private_class_method :generate_unsupported_options
|
35
46
|
|
36
47
|
def self.check_unsupported_options(unsupported_options)
|
37
48
|
return if unsupported_options.empty?
|
@@ -40,6 +51,7 @@ module Asimov
|
|
40
51
|
raise Asimov::ConfigurationError,
|
41
52
|
"The options #{quoted_keys.join(',')} are not supported."
|
42
53
|
end
|
54
|
+
private_class_method :check_unsupported_options
|
43
55
|
|
44
56
|
def self.supported_option?(key)
|
45
57
|
ALLOWED_OPTIONS.include?(key)
|
@@ -51,6 +63,7 @@ module Asimov
|
|
51
63
|
raise Asimov::ConfigurationError,
|
52
64
|
"Request options keys must be symbols. The key '#{key}' is not a symbol."
|
53
65
|
end
|
66
|
+
private_class_method :check_symbol
|
54
67
|
end
|
55
68
|
end
|
56
69
|
end
|
data/lib/asimov/version.rb
CHANGED
data/lib/asimov.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: asimov
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter M. Goldstein
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -30,105 +30,8 @@ dependencies:
|
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 0.22.0
|
33
|
-
|
34
|
-
|
35
|
-
requirement: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '13.0'
|
40
|
-
type: :development
|
41
|
-
prerelease: false
|
42
|
-
version_requirements: !ruby/object:Gem::Requirement
|
43
|
-
requirements:
|
44
|
-
- - "~>"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '13.0'
|
47
|
-
- !ruby/object:Gem::Dependency
|
48
|
-
name: rspec
|
49
|
-
requirement: !ruby/object:Gem::Requirement
|
50
|
-
requirements:
|
51
|
-
- - "~>"
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: '3.12'
|
54
|
-
type: :development
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
requirements:
|
58
|
-
- - "~>"
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version: '3.12'
|
61
|
-
- !ruby/object:Gem::Dependency
|
62
|
-
name: rubocop
|
63
|
-
requirement: !ruby/object:Gem::Requirement
|
64
|
-
requirements:
|
65
|
-
- - "~>"
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: 1.43.0
|
68
|
-
type: :development
|
69
|
-
prerelease: false
|
70
|
-
version_requirements: !ruby/object:Gem::Requirement
|
71
|
-
requirements:
|
72
|
-
- - "~>"
|
73
|
-
- !ruby/object:Gem::Version
|
74
|
-
version: 1.43.0
|
75
|
-
- !ruby/object:Gem::Dependency
|
76
|
-
name: rubocop-rake
|
77
|
-
requirement: !ruby/object:Gem::Requirement
|
78
|
-
requirements:
|
79
|
-
- - ">="
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
version: '0'
|
82
|
-
type: :development
|
83
|
-
prerelease: false
|
84
|
-
version_requirements: !ruby/object:Gem::Requirement
|
85
|
-
requirements:
|
86
|
-
- - ">="
|
87
|
-
- !ruby/object:Gem::Version
|
88
|
-
version: '0'
|
89
|
-
- !ruby/object:Gem::Dependency
|
90
|
-
name: rubocop-rspec
|
91
|
-
requirement: !ruby/object:Gem::Requirement
|
92
|
-
requirements:
|
93
|
-
- - ">="
|
94
|
-
- !ruby/object:Gem::Version
|
95
|
-
version: '0'
|
96
|
-
type: :development
|
97
|
-
prerelease: false
|
98
|
-
version_requirements: !ruby/object:Gem::Requirement
|
99
|
-
requirements:
|
100
|
-
- - ">="
|
101
|
-
- !ruby/object:Gem::Version
|
102
|
-
version: '0'
|
103
|
-
- !ruby/object:Gem::Dependency
|
104
|
-
name: vcr
|
105
|
-
requirement: !ruby/object:Gem::Requirement
|
106
|
-
requirements:
|
107
|
-
- - "~>"
|
108
|
-
- !ruby/object:Gem::Version
|
109
|
-
version: 6.1.0
|
110
|
-
type: :development
|
111
|
-
prerelease: false
|
112
|
-
version_requirements: !ruby/object:Gem::Requirement
|
113
|
-
requirements:
|
114
|
-
- - "~>"
|
115
|
-
- !ruby/object:Gem::Version
|
116
|
-
version: 6.1.0
|
117
|
-
- !ruby/object:Gem::Dependency
|
118
|
-
name: webmock
|
119
|
-
requirement: !ruby/object:Gem::Requirement
|
120
|
-
requirements:
|
121
|
-
- - "~>"
|
122
|
-
- !ruby/object:Gem::Version
|
123
|
-
version: 3.18.1
|
124
|
-
type: :development
|
125
|
-
prerelease: false
|
126
|
-
version_requirements: !ruby/object:Gem::Requirement
|
127
|
-
requirements:
|
128
|
-
- - "~>"
|
129
|
-
- !ruby/object:Gem::Version
|
130
|
-
version: 3.18.1
|
131
|
-
description: A Ruby client for the OpenAI API
|
33
|
+
description: A Ruby client for the OpenAI API support for multiple API configurations
|
34
|
+
in a single app, robust and simple error handling, and network-level configuration.
|
132
35
|
email:
|
133
36
|
- peter.m.goldstein@gmail.com
|
134
37
|
executables: []
|
@@ -185,8 +88,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
88
|
- !ruby/object:Gem::Version
|
186
89
|
version: '0'
|
187
90
|
requirements: []
|
188
|
-
rubygems_version: 3.4.
|
91
|
+
rubygems_version: 3.4.5
|
189
92
|
signing_key:
|
190
93
|
specification_version: 4
|
191
|
-
summary: A Ruby client for the OpenAI API
|
94
|
+
summary: A Ruby client for the OpenAI API support for multiple API configurations
|
95
|
+
in a single app, robust and simple error handling, and network-level configuration.
|
192
96
|
test_files: []
|