asimov 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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?(
|
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: []
|