asimov 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +34 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/lib/asimov/api_v1/api_error_translator.rb +115 -0
- data/lib/asimov/api_v1/base.rb +91 -0
- data/lib/asimov/api_v1/completions.rb +14 -0
- data/lib/asimov/api_v1/edits.rb +15 -0
- data/lib/asimov/api_v1/embeddings.rb +15 -0
- data/lib/asimov/api_v1/files.rb +66 -0
- data/lib/asimov/api_v1/finetunes.rb +32 -0
- data/lib/asimov/api_v1/images.rb +38 -0
- data/lib/asimov/api_v1/models.rb +22 -0
- data/lib/asimov/api_v1/moderations.rb +14 -0
- data/lib/asimov/api_v1/network_error_translator.rb +35 -0
- data/lib/asimov/client.rb +99 -0
- data/lib/asimov/configuration.rb +28 -0
- data/lib/asimov/error.rb +129 -0
- data/lib/asimov/headers_factory.rb +67 -0
- data/lib/asimov/utils/classifications_file_validator.rb +42 -0
- data/lib/asimov/utils/file_manager.rb +18 -0
- data/lib/asimov/utils/jsonl_validator.rb +25 -0
- data/lib/asimov/utils/request_options_validator.rb +56 -0
- data/lib/asimov/utils/text_entry_file_validator.rb +38 -0
- data/lib/asimov/utils/training_file_validator.rb +39 -0
- data/lib/asimov/version.rb +3 -0
- data/lib/asimov.rb +35 -0
- metadata +192 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c50684e3e690496248b87a912334da4b3ace87c83dec1c05e2d8b33349c5d16d
|
4
|
+
data.tar.gz: 6ab91367f32fa02d4582018fd6cad490ba4e78db92c86e231b64323a3389d88f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 859bbbfccff90fdddac6708846bc03b2a6f175bf83fd7ad9329a3de86d8830444e34d822eeeb65d4ba054678b1cda15312b85ca52b4dea2e43dc83cc2554e908
|
7
|
+
data.tar.gz: 169621c5fe064acc1ba293a01da9473c75aae65f7a650139c377319990140f2295fdae39d5b231fae7f3df7a506224d99e9f7d64acfb46af6967841ec46072c5
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [0.1.0] - 2023-01-14
|
9
|
+
|
10
|
+
This initial version of the gem is now suitable for use. API endpoint method naming may shift slightly,
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
|
14
|
+
- Refactored code base to better accord with SOLID principles and support better unit testing
|
15
|
+
- `access_token` is now `api_key` to better accord with OpenAI documentation
|
16
|
+
- Renamed some endpoint methods from the original codebase
|
17
|
+
- API method default argument now reflect whether a parameter is required
|
18
|
+
- For errors resulting from an exception raised in the HTTP stack, API endpoints raise subclasses of Asimov::NetworkError
|
19
|
+
- For errors returned in an OpenAI payload, API endpoint methods raise subclasses of Asimov::RequestError
|
20
|
+
- API Endpoint methods now return parsed JSON
|
21
|
+
- Repackaged original code into asimov
|
22
|
+
|
23
|
+
### Added
|
24
|
+
|
25
|
+
- Method for the files content endpoint
|
26
|
+
- Method for the models delete endpoint
|
27
|
+
- Feature test coverage for all endpoint methods
|
28
|
+
- Unit testing for all classes
|
29
|
+
- Header and body matching for all VCR specs, including multipart POST
|
30
|
+
- Support for the use of multiple OpenAI configurations within the same application
|
31
|
+
- SimpleCov execution for testing
|
32
|
+
- GitHub Actions for CI
|
33
|
+
- Forked code from alexrudall/ruby-openai
|
34
|
+
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 Peter M. Goldstein, Alex Rudall
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Asimov
|
2
|
+
|
3
|
+
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/petergoldstein/asimov/blob/main/LICENSE.txt)
|
4
|
+
[![Tests](https://github.com/petergoldstein/asimov/actions/workflows/ci.yml/badge.svg)](https://github.com/petergoldstein/asimov/actions/workflows/ci.yml)
|
5
|
+
|
6
|
+
Asimov is a gem that enables the use of OpenAI's ML capabilities within SaaS applications. With that in mind it includes:
|
7
|
+
|
8
|
+
* The ability to use multiple OpenAI configurations (API keys and organization ids) within a single application.
|
9
|
+
* Support for configuring connections with timeouts and proxies to make applications robust against failure and to support complex network environments.
|
10
|
+
* Methods for all non-streaming endpoints in the [OpenAI API](https://openai.com/blog/openai-api/). (Streaming support is under consideration).
|
11
|
+
|
12
|
+
|
13
|
+
You can use this gem to write applications that generate text with GPT-3, create images with DALL·E, or write code with Codex.
|
14
|
+
|
15
|
+
The name Asimov is a tribute to [Isaac Asimov](https://en.wikipedia.org/wiki/Isaac_Asimov), a 20th century author whose work referenced AI extensively, most famously with his ["Three Laws of Robotics"](https://en.wikipedia.org/wiki/Three_Laws_of_Robotics). Asimov's stories explored the ethical and philosophical implications of using artificial intelligence and robots in society, and he is often credited with helping to popularize the concept of AI in science fiction. His work has had a lasting impact on the genre and has influenced many other writers and thinkers.
|
16
|
+
|
17
|
+
|
18
|
+
## Documentation and Information
|
19
|
+
|
20
|
+
* [User Documentation](https://github.com/petergoldstein/asimov/wiki) - The documentation is maintained in the repository's wiki.
|
21
|
+
* [Announcements](https://github.com/petergoldstein/asimov/discussions/categories/announcements) - Announcements of interest to the Asimov community will be posted here.
|
22
|
+
* [Bug Reports](https://github.com/petergoldstein/asimov/issues) - If you discover a problem with Asimov, please submit a bug report in the tracker.
|
23
|
+
* [Client API](https://rubydoc.info/github/petergoldstein/asimov/Asimov/Client) - Ruby documentation for the `Asimov::Client` API
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/petergoldstein/asimov>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/petergoldstein/asimov/blob/main/CODE_OF_CONDUCT.md).
|
28
|
+
|
29
|
+
## License
|
30
|
+
|
31
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
32
|
+
|
33
|
+
## Code of Conduct
|
34
|
+
|
35
|
+
Everyone interacting in the ruby-openai project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/petergoldstein/asimov/blob/main/CODE_OF_CONDUCT.md).
|
36
|
+
|
37
|
+
## Appreciation
|
38
|
+
|
39
|
+
Asimov was originally created from a fork of the [ruby-openai](https://github.com/alexrudall/ruby-openai). It would not exist without the work of [alexrudall](https://github.com/alexrudall) and other contributors to that gem.
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Asimov
|
2
|
+
module ApiV1
|
3
|
+
##
|
4
|
+
# Translates errors which are generated by OpenAI and provided in responses
|
5
|
+
# to raised exceptions. Because OpenAI doesn't currently provide any
|
6
|
+
# error-specific codes above and beyond the HTTP return code, this matching
|
7
|
+
# process is done by matching against message text which makes it somewhat
|
8
|
+
# fragile.
|
9
|
+
##
|
10
|
+
class ApiErrorTranslator
|
11
|
+
def self.translate(resp)
|
12
|
+
match_400(resp)
|
13
|
+
match_401(resp)
|
14
|
+
match_404(resp)
|
15
|
+
end
|
16
|
+
|
17
|
+
# rubocop:disable Naming/VariableNumber
|
18
|
+
# rubocop:disable Metrics/MethodLength
|
19
|
+
INVALID_API_KEY_PREFIX = "Incorrect API key provided: ".freeze
|
20
|
+
INVALID_ORGANIZATION_PREFIX = "No such organization: ".freeze
|
21
|
+
def self.match_401(resp)
|
22
|
+
return unless resp.code == 401
|
23
|
+
|
24
|
+
msg = error_message(resp)
|
25
|
+
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)
|
27
|
+
|
28
|
+
raise Asimov::UnauthorizedError
|
29
|
+
end
|
30
|
+
|
31
|
+
INVALID_TRAINING_EXAMPLE_PREFIX = "Expected file to have JSONL format with " \
|
32
|
+
"prompt/completion keys. Missing".freeze
|
33
|
+
ADDITIONAL_PROPERTIES_ERROR_PREFIX = "Additional properties are not allowed".freeze
|
34
|
+
INVALID_PARAMETER_VALUE_STRING = "' is not one of [".freeze
|
35
|
+
INVALID_PARAMETER_VALUE_PREFIX_2 = "Invalid value for ".freeze
|
36
|
+
BELOW_MINIMUM_STRING = " is less than the minimum of ".freeze
|
37
|
+
ABOVE_MAXIMUM_STRING = " is greater than the maximum of ".freeze
|
38
|
+
def self.match_400(resp)
|
39
|
+
return unless resp.code == 400
|
40
|
+
|
41
|
+
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
|
+
|
65
|
+
if msg.start_with?(INVALID_TRAINING_EXAMPLE_PREFIX)
|
66
|
+
raise Asimov::InvalidTrainingExampleError,
|
67
|
+
msg
|
68
|
+
end
|
69
|
+
if msg.start_with?(ADDITIONAL_PROPERTIES_ERROR_PREFIX)
|
70
|
+
raise Asimov::UnsupportedParameterError,
|
71
|
+
msg
|
72
|
+
end
|
73
|
+
|
74
|
+
if match_invalid_parameter_value?(msg)
|
75
|
+
raise Asimov::InvalidParameterValueError,
|
76
|
+
msg
|
77
|
+
end
|
78
|
+
|
79
|
+
raise Asimov::RequestError, msg
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.match_invalid_parameter_value?(msg)
|
83
|
+
msg.include?(INVALID_PARAMETER_VALUE_STRING) ||
|
84
|
+
msg.include?(BELOW_MINIMUM_STRING) ||
|
85
|
+
msg.include?(ABOVE_MAXIMUM_STRING) ||
|
86
|
+
msg.start_with?(INVALID_PARAMETER_VALUE_PREFIX_2)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.match_404(resp)
|
90
|
+
return unless resp.code == 404
|
91
|
+
|
92
|
+
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
|
+
raise Asimov::NotFoundError, msg
|
98
|
+
end
|
99
|
+
|
100
|
+
# rubocop:enable Naming/VariableNumber
|
101
|
+
# rubocop:enable Metrics/MethodLength
|
102
|
+
|
103
|
+
##
|
104
|
+
# Extracts the error message from the API response
|
105
|
+
##
|
106
|
+
def self.error_message(resp)
|
107
|
+
# This handles fragments which can occur in a streamed download
|
108
|
+
pr = resp.respond_to?(:parsed_response) ? resp.parsed_response : JSON.parse(resp)
|
109
|
+
return "" unless pr.is_a?(Hash) && pr["error"].is_a?(Hash)
|
110
|
+
|
111
|
+
pr["error"]["message"] || ""
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require_relative "./api_error_translator"
|
2
|
+
require_relative "./network_error_translator"
|
3
|
+
|
4
|
+
module Asimov
|
5
|
+
module ApiV1
|
6
|
+
##
|
7
|
+
# Base class for API interface implementations. Currently
|
8
|
+
# manages the network logic for the interface.
|
9
|
+
##
|
10
|
+
class Base
|
11
|
+
extend Forwardable
|
12
|
+
include HTTParty
|
13
|
+
|
14
|
+
base_uri "https://api.openai.com/v1"
|
15
|
+
|
16
|
+
def initialize(client: nil)
|
17
|
+
@client = client
|
18
|
+
end
|
19
|
+
def_delegators :@client, :headers, :request_options
|
20
|
+
|
21
|
+
def http_delete(path:)
|
22
|
+
wrap_response_with_error_handling do
|
23
|
+
self.class.delete(
|
24
|
+
path,
|
25
|
+
{ headers: headers }.merge!(request_options)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def http_get(path:)
|
31
|
+
wrap_response_with_error_handling do
|
32
|
+
self.class.get(
|
33
|
+
path,
|
34
|
+
{ headers: headers }.merge!(request_options)
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def json_post(path:, parameters:)
|
40
|
+
wrap_response_with_error_handling do
|
41
|
+
self.class.post(
|
42
|
+
path,
|
43
|
+
{ headers: headers,
|
44
|
+
body: parameters&.to_json }.merge!(request_options)
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def multipart_post(path:, parameters: nil)
|
50
|
+
wrap_response_with_error_handling do
|
51
|
+
self.class.post(
|
52
|
+
path,
|
53
|
+
{ headers: headers("multipart/form-data"),
|
54
|
+
body: parameters }.merge!(request_options)
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def http_streamed_download(path:, writer:)
|
60
|
+
self.class.get(path,
|
61
|
+
{ headers: headers,
|
62
|
+
stream_body: true }.merge!(request_options)) do |fragment|
|
63
|
+
fragment.code == 200 ? writer.write(fragment) : check_for_api_error(fragment)
|
64
|
+
end
|
65
|
+
rescue Asimov::RequestError => e
|
66
|
+
# Raise any translated API errors
|
67
|
+
raise e
|
68
|
+
rescue StandardError => e
|
69
|
+
# Otherwise translate the error to a network error
|
70
|
+
NetworkErrorTranslator.translate(e)
|
71
|
+
end
|
72
|
+
|
73
|
+
def wrap_response_with_error_handling
|
74
|
+
resp = begin
|
75
|
+
yield
|
76
|
+
rescue StandardError => e
|
77
|
+
NetworkErrorTranslator.translate(e)
|
78
|
+
end
|
79
|
+
check_for_api_error(resp)
|
80
|
+
resp.parsed_response
|
81
|
+
end
|
82
|
+
|
83
|
+
def check_for_api_error(resp)
|
84
|
+
return if resp.code == 200
|
85
|
+
|
86
|
+
ApiErrorTranslator.translate(resp)
|
87
|
+
resp
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Asimov
|
2
|
+
module ApiV1
|
3
|
+
##
|
4
|
+
# Class interface for API methods in the "/completions" URI subspace.
|
5
|
+
##
|
6
|
+
class Completions < Base
|
7
|
+
def create(parameters:)
|
8
|
+
raise MissingRequiredParameterError.new(:model) unless parameters[:model]
|
9
|
+
|
10
|
+
json_post(path: "/completions", parameters: parameters)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Asimov
|
2
|
+
module ApiV1
|
3
|
+
##
|
4
|
+
# Class interface for API methods in the "/edits" URI subspace.
|
5
|
+
##
|
6
|
+
class Edits < Base
|
7
|
+
def create(parameters:)
|
8
|
+
raise MissingRequiredParameterError.new(:model) unless parameters[:model]
|
9
|
+
raise MissingRequiredParameterError.new(:instruction) unless parameters[:instruction]
|
10
|
+
|
11
|
+
json_post(path: "/edits", parameters: parameters)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Asimov
|
2
|
+
module ApiV1
|
3
|
+
##
|
4
|
+
# Class interface for API methods in the "/embeddings" URI subspace.
|
5
|
+
##
|
6
|
+
class Embeddings < Base
|
7
|
+
def create(parameters:)
|
8
|
+
raise MissingRequiredParameterError.new(:model) unless parameters[:model]
|
9
|
+
raise MissingRequiredParameterError.new(:input) unless parameters[:input]
|
10
|
+
|
11
|
+
json_post(path: "/embeddings", parameters: parameters)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative "../utils/file_manager"
|
2
|
+
require_relative "../utils/jsonl_validator"
|
3
|
+
require_relative "../utils/training_file_validator"
|
4
|
+
require_relative "../utils/classifications_file_validator"
|
5
|
+
require_relative "../utils/text_entry_file_validator"
|
6
|
+
|
7
|
+
module Asimov
|
8
|
+
module ApiV1
|
9
|
+
##
|
10
|
+
# Class interface for API methods in the "/files" URI subspace.
|
11
|
+
##
|
12
|
+
class Files < Base
|
13
|
+
URI_PREFIX = "/files".freeze
|
14
|
+
|
15
|
+
##
|
16
|
+
# Lists files that have been uploaded to OpenAI
|
17
|
+
##
|
18
|
+
def list
|
19
|
+
http_get(path: URI_PREFIX)
|
20
|
+
end
|
21
|
+
|
22
|
+
def upload(parameters:)
|
23
|
+
raise MissingRequiredParameterError.new(:file) unless parameters[:file]
|
24
|
+
raise MissingRequiredParameterError.new(:purpose) unless parameters[:purpose]
|
25
|
+
|
26
|
+
validate(parameters[:file], parameters[:purpose])
|
27
|
+
|
28
|
+
multipart_post(
|
29
|
+
path: URI_PREFIX,
|
30
|
+
parameters: parameters.merge(file: Utils::FileManager.open(parameters[:file]))
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def retrieve(file_id:)
|
35
|
+
http_get(path: "#{URI_PREFIX}/#{file_id}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete(file_id:)
|
39
|
+
http_delete(path: "#{URI_PREFIX}/#{file_id}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def content(file_id:, writer:)
|
43
|
+
http_streamed_download(path: "#{URI_PREFIX}/#{file_id}/content", writer: writer)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def validate(filename, purpose)
|
49
|
+
validator_class(purpose).new.validate(Utils::FileManager.open(filename))
|
50
|
+
end
|
51
|
+
|
52
|
+
def validator_class(purpose)
|
53
|
+
case purpose
|
54
|
+
when "fine-tune"
|
55
|
+
Utils::TrainingFileValidator
|
56
|
+
when "classifications"
|
57
|
+
Utils::ClassificationsFileValidator
|
58
|
+
when "answers", "search"
|
59
|
+
Utils::TextEntryFileValidator
|
60
|
+
else
|
61
|
+
Utils::JsonlValidator
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Asimov
|
2
|
+
module ApiV1
|
3
|
+
##
|
4
|
+
# Class interface for API methods in the "/fine-tunes" URI subspace.
|
5
|
+
##
|
6
|
+
class Finetunes < Base
|
7
|
+
URI_PREFIX = "/fine-tunes".freeze
|
8
|
+
|
9
|
+
def list
|
10
|
+
http_get(path: URI_PREFIX)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(parameters:)
|
14
|
+
raise MissingRequiredParameterError.new(:training_file) unless parameters[:training_file]
|
15
|
+
|
16
|
+
json_post(path: URI_PREFIX, parameters: parameters)
|
17
|
+
end
|
18
|
+
|
19
|
+
def retrieve(fine_tune_id:)
|
20
|
+
http_get(path: "#{URI_PREFIX}/#{fine_tune_id}")
|
21
|
+
end
|
22
|
+
|
23
|
+
def cancel(fine_tune_id:)
|
24
|
+
multipart_post(path: "#{URI_PREFIX}/#{fine_tune_id}/cancel")
|
25
|
+
end
|
26
|
+
|
27
|
+
def events(fine_tune_id:)
|
28
|
+
http_get(path: "#{URI_PREFIX}/#{fine_tune_id}/events")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative "../utils/file_manager"
|
2
|
+
|
3
|
+
module Asimov
|
4
|
+
module ApiV1
|
5
|
+
##
|
6
|
+
# Class interface for API methods in the "/images" URI subspace.
|
7
|
+
##
|
8
|
+
class Images < Base
|
9
|
+
URI_PREFIX = "/images".freeze
|
10
|
+
|
11
|
+
def create(parameters:)
|
12
|
+
raise MissingRequiredParameterError.new(:prompt) unless parameters[:prompt]
|
13
|
+
|
14
|
+
json_post(path: "#{URI_PREFIX}/generations", parameters: parameters)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_edit(parameters:)
|
18
|
+
raise MissingRequiredParameterError.new(:prompt) unless parameters[:prompt]
|
19
|
+
|
20
|
+
multipart_post(path: "#{URI_PREFIX}/edits", parameters: open_files(parameters))
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_variation(parameters:)
|
24
|
+
multipart_post(path: "#{URI_PREFIX}/variations", parameters: open_files(parameters))
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def open_files(parameters)
|
30
|
+
raise MissingRequiredParameterError.new(:image) unless parameters[:image]
|
31
|
+
|
32
|
+
parameters = parameters.merge(image: Utils::FileManager.open(parameters[:image]))
|
33
|
+
parameters.merge!(mask: Utils::FileManager.open(parameters[:mask])) if parameters[:mask]
|
34
|
+
parameters
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Asimov
|
2
|
+
module ApiV1
|
3
|
+
##
|
4
|
+
# Class interface for API methods in the "/models" URI subspace.
|
5
|
+
##
|
6
|
+
class Models < Base
|
7
|
+
URI_PREFIX = "/models".freeze
|
8
|
+
|
9
|
+
def list
|
10
|
+
http_get(path: URI_PREFIX)
|
11
|
+
end
|
12
|
+
|
13
|
+
def retrieve(model_id:)
|
14
|
+
http_get(path: "#{URI_PREFIX}/#{model_id}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(model_id:)
|
18
|
+
http_delete(path: "#{URI_PREFIX}/#{model_id}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Asimov
|
2
|
+
module ApiV1
|
3
|
+
##
|
4
|
+
# Class interface for API methods in the "/moderations" URI subspace.
|
5
|
+
##
|
6
|
+
class Moderations < Base
|
7
|
+
def create(parameters:)
|
8
|
+
raise MissingRequiredParameterError.new(:input) unless parameters[:input]
|
9
|
+
|
10
|
+
json_post(path: "/moderations", parameters: parameters)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Asimov
|
2
|
+
module ApiV1
|
3
|
+
##
|
4
|
+
# Translates errors that are generated by Net::HTTP into Asimov::NetworkErrors
|
5
|
+
# or a specific subclass to allow better handling by clients of the library.
|
6
|
+
##
|
7
|
+
class NetworkErrorTranslator
|
8
|
+
# rubocop:disable Metrics/MethodLength
|
9
|
+
|
10
|
+
##
|
11
|
+
# Translates an original error generated by the underlying network
|
12
|
+
# stack to an Asimov::NetworkError or a specific subclass,
|
13
|
+
# preserving the message from the original error.
|
14
|
+
##
|
15
|
+
def self.translate(orig_error)
|
16
|
+
case orig_error
|
17
|
+
when Net::OpenTimeout
|
18
|
+
raise Asimov::OpenTimeout, orig_error.message
|
19
|
+
when Net::ReadTimeout
|
20
|
+
raise Asimov::ReadTimeout, orig_error.message
|
21
|
+
when Net::WriteTimeout
|
22
|
+
raise Asimov::WriteTimeout, orig_error.message
|
23
|
+
when Timeout::Error, Errno::ETIMEDOUT
|
24
|
+
raise Asimov::TimeoutError, orig_error.message
|
25
|
+
else
|
26
|
+
# Errno::EINVAL, Errno::ECONNRESET, EOFError,
|
27
|
+
# Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError
|
28
|
+
raise Asimov::NetworkError, orig_error.message
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# rubocop:enable Metrics/MethodLength
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "httparty"
|
3
|
+
require_relative "headers_factory"
|
4
|
+
require_relative "utils/request_options_validator"
|
5
|
+
require_relative "api_v1/base"
|
6
|
+
require_relative "api_v1/completions"
|
7
|
+
require_relative "api_v1/edits"
|
8
|
+
require_relative "api_v1/embeddings"
|
9
|
+
require_relative "api_v1/files"
|
10
|
+
require_relative "api_v1/finetunes"
|
11
|
+
require_relative "api_v1/images"
|
12
|
+
require_relative "api_v1/models"
|
13
|
+
require_relative "api_v1/moderations"
|
14
|
+
|
15
|
+
module Asimov
|
16
|
+
##
|
17
|
+
# Asimov::Client is the main class which developers will use to interact with
|
18
|
+
# OpenAI.
|
19
|
+
##
|
20
|
+
class Client
|
21
|
+
extend Forwardable
|
22
|
+
|
23
|
+
attr_reader :api_key, :organization_id, :api_version, :request_options
|
24
|
+
|
25
|
+
##
|
26
|
+
# Creates a new Asimov::Client. Includes several optional named parameters:
|
27
|
+
#
|
28
|
+
# api_key - The OpenAI API key that this Asimov::Client instance will use. If unspecified,
|
29
|
+
# defaults to the application-wide configuration
|
30
|
+
# organization_id - The OpenAI organization identifier that this Asimov::Client instance
|
31
|
+
# will use. If unspecified, defaults to the application-wide configuration.
|
32
|
+
##
|
33
|
+
def initialize(api_key: nil, organization_id: HeadersFactory::NULL_ORGANIZATION_ID,
|
34
|
+
request_options: {})
|
35
|
+
@headers_factory = HeadersFactory.new(api_key,
|
36
|
+
organization_id)
|
37
|
+
@request_options = Asimov.configuration.request_options
|
38
|
+
.merge(Utils::RequestOptionsValidator.validate(request_options))
|
39
|
+
.freeze
|
40
|
+
end
|
41
|
+
def_delegators :@headers_factory, :api_key, :organization_id, :headers
|
42
|
+
|
43
|
+
##
|
44
|
+
# Use the completions method to access API calls in the /completions URI space.
|
45
|
+
##
|
46
|
+
def completions
|
47
|
+
@completions ||= Asimov::ApiV1::Completions.new(client: self)
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Use the edits method to access API calls in the /edits URI space.
|
52
|
+
##
|
53
|
+
def edits
|
54
|
+
@edits ||= Asimov::ApiV1::Edits.new(client: self)
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Use the embeddings method to access API calls in the /embeddings URI space.
|
59
|
+
##
|
60
|
+
def embeddings
|
61
|
+
@embeddings ||= Asimov::ApiV1::Embeddings.new(client: self)
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Use the files method to access API calls in the /files URI space.
|
66
|
+
##
|
67
|
+
def files
|
68
|
+
@files ||= Asimov::ApiV1::Files.new(client: self)
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Use the finetunes method to access API calls in the /fine-tunes URI space.
|
73
|
+
##
|
74
|
+
def finetunes
|
75
|
+
@finetunes ||= Asimov::ApiV1::Finetunes.new(client: self)
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Use the images method to access API calls in the /images URI space.
|
80
|
+
##
|
81
|
+
def images
|
82
|
+
@images ||= Asimov::ApiV1::Images.new(client: self)
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Use the models method to access API calls in the /models URI space.
|
87
|
+
##
|
88
|
+
def models
|
89
|
+
@models ||= Asimov::ApiV1::Models.new(client: self)
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Use the moderations method to access API calls in the /moderations URI space.
|
94
|
+
##
|
95
|
+
def moderations
|
96
|
+
@moderations ||= Asimov::ApiV1::Moderations.new(client: self)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|