asimov 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "simplecov", require: false, group: :test
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