asimov 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/Gemfile +2 -0
- data/lib/asimov/api_v1/audio.rb +52 -0
- data/lib/asimov/api_v1/base.rb +47 -28
- data/lib/asimov/api_v1/chat.rb +29 -0
- data/lib/asimov/api_v1/completions.rb +3 -2
- data/lib/asimov/api_v1/edits.rb +4 -4
- data/lib/asimov/api_v1/embeddings.rb +8 -2
- data/lib/asimov/api_v1/files.rb +8 -8
- data/lib/asimov/api_v1/finetunes.rb +8 -7
- data/lib/asimov/api_v1/images.rb +12 -9
- data/lib/asimov/api_v1/models.rb +5 -5
- data/lib/asimov/api_v1/moderations.rb +2 -1
- data/lib/asimov/client.rb +35 -2
- data/lib/asimov/configuration.rb +12 -1
- data/lib/asimov/error.rb +12 -0
- data/lib/asimov/utils/chat_messages_validator.rb +63 -0
- data/lib/asimov/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d49b0bc867c3e318501548e7fcf45a3dd48bc469b05f959b5839144e3f9d472
|
4
|
+
data.tar.gz: 1193a9a5eea75c3d3b57d09ca10ccd5e50ee01d44ecce648c985ac2bcfd5cded
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 349259c93c833021ebfc46f7912f8c36109dc51693c3828972d263282f514adee94b5416d9e89c2dc1cc1d868d01cabce20ef1df01e83279eac80d156b1ded61
|
7
|
+
data.tar.gz: 67deac4b3306ffdc5ab36aa49b7ab66093dc4938131b3264ce2ef2cd6a628840cd04d3474f39fe7bc1f70dc5c0d9dba7bd136c28c46c58b776ff2494ae825030
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## Unreleased
|
9
9
|
|
10
|
+
## [1.1.0] - 2023-03-01
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Made API base URI configurable to support services that proxy API calls (like Helicone)
|
15
|
+
- Added support for the chat and audio endpoints
|
16
|
+
|
10
17
|
## [1.0.0] - 2023-01-24
|
11
18
|
|
12
19
|
This version has complete coverage of the OpenAI API (except for stream: true behavior), has
|
data/Gemfile
CHANGED
@@ -3,9 +3,11 @@ source "https://rubygems.org"
|
|
3
3
|
gemspec
|
4
4
|
|
5
5
|
group :development, :test do
|
6
|
+
gem "faker"
|
6
7
|
gem "rake", "~> 13.0"
|
7
8
|
gem "rspec", "~> 3.12"
|
8
9
|
gem "rubocop", "~> 1.44"
|
10
|
+
gem "rubocop-performance"
|
9
11
|
gem "rubocop-rake"
|
10
12
|
gem "rubocop-rspec"
|
11
13
|
gem "simplecov", require: false
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative "../utils/chat_messages_validator"
|
2
|
+
|
3
|
+
module Asimov
|
4
|
+
module ApiV1
|
5
|
+
##
|
6
|
+
# Class interface for API methods in the "/audio" URI subspace.
|
7
|
+
##
|
8
|
+
class Audio < Base
|
9
|
+
RESOURCE = "audio".freeze
|
10
|
+
|
11
|
+
##
|
12
|
+
# Creates a transcription request with the specified parameters.
|
13
|
+
#
|
14
|
+
# @param [String] model the model to use for the completion
|
15
|
+
# @param [Hash] parameters the set of parameters being passed to the API
|
16
|
+
##
|
17
|
+
def create_transcription(file:, model:, parameters: {})
|
18
|
+
raise MissingRequiredParameterError.new(:model) unless model
|
19
|
+
|
20
|
+
rest_create_w_multipart_params(resource: [RESOURCE, "transcriptions"],
|
21
|
+
parameters:
|
22
|
+
open_file(parameters.merge({
|
23
|
+
file: file, model: model
|
24
|
+
})))
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Creates a transcription request with the specified parameters.
|
29
|
+
#
|
30
|
+
# @param [String] model the model to use for the completion
|
31
|
+
# @param [Hash] parameters the set of parameters being passed to the API
|
32
|
+
##
|
33
|
+
def create_translation(file:, model:, parameters: {})
|
34
|
+
raise MissingRequiredParameterError.new(:model) unless model
|
35
|
+
|
36
|
+
rest_create_w_multipart_params(resource: [RESOURCE, "translations"],
|
37
|
+
parameters:
|
38
|
+
open_file(parameters.merge({
|
39
|
+
file: file, model: model
|
40
|
+
})))
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def open_file(parameters)
|
46
|
+
raise MissingRequiredParameterError.new(:file) unless parameters[:file]
|
47
|
+
|
48
|
+
parameters.merge(file: Utils::FileManager.open(parameters[:file]))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/asimov/api_v1/base.rb
CHANGED
@@ -15,53 +15,67 @@ module Asimov
|
|
15
15
|
extend Forwardable
|
16
16
|
include HTTParty
|
17
17
|
|
18
|
-
base_uri "https://api.openai.com/v1"
|
19
|
-
|
20
18
|
def initialize(client: nil)
|
21
19
|
@client = client
|
22
20
|
end
|
23
|
-
def_delegators :@client, :headers, :request_options
|
21
|
+
def_delegators :@client, :headers, :request_options, :openai_api_base
|
24
22
|
|
25
23
|
##
|
26
|
-
# Executes
|
24
|
+
# Executes a REST index for the specified resource
|
27
25
|
#
|
28
|
-
# @param [String]
|
29
|
-
# base_uri) against which the DELETE is executed.
|
26
|
+
# @param [String] resource the pluralized resource name
|
30
27
|
##
|
31
|
-
def
|
28
|
+
def rest_index(resource:)
|
29
|
+
wrap_response_with_error_handling do
|
30
|
+
self.class.get(
|
31
|
+
absolute_path("/#{Array(resource).join('/')}"),
|
32
|
+
{ headers: headers }.merge!(request_options)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Executes a REST delete on the specified resource.
|
39
|
+
#
|
40
|
+
# @param [String] resource the pluralized resource name
|
41
|
+
# @param [String] id the id of the resource to delete
|
42
|
+
##
|
43
|
+
def rest_delete(resource:, id:)
|
32
44
|
wrap_response_with_error_handling do
|
33
45
|
self.class.delete(
|
34
|
-
|
46
|
+
absolute_path("/#{resource}/#{CGI.escape(id)}"),
|
35
47
|
{ headers: headers }.merge!(request_options)
|
36
48
|
)
|
37
49
|
end
|
38
50
|
end
|
39
51
|
|
40
52
|
##
|
41
|
-
# Executes
|
53
|
+
# Executes a REST get on the specified resource.
|
42
54
|
#
|
43
|
-
# @param [String]
|
44
|
-
#
|
55
|
+
# @param [String] resource the pluralized resource name
|
56
|
+
# @param [String] id the id of the resource get
|
45
57
|
##
|
46
|
-
def
|
58
|
+
def rest_get(resource:, id:)
|
47
59
|
wrap_response_with_error_handling do
|
48
60
|
self.class.get(
|
49
|
-
|
61
|
+
absolute_path("/#{resource}/#{CGI.escape(id)}"),
|
50
62
|
{ headers: headers }.merge!(request_options)
|
51
63
|
)
|
52
64
|
end
|
53
65
|
end
|
54
66
|
|
55
67
|
##
|
56
|
-
# Executes
|
68
|
+
# Executes a REST create with JSON-encoded parameters for the specified
|
69
|
+
# resource.
|
57
70
|
#
|
58
|
-
# @param [String]
|
59
|
-
#
|
71
|
+
# @param [String] the resource to be created.
|
72
|
+
# @param [Hash] parameters the parameters to include with the request
|
73
|
+
# to create the resource
|
60
74
|
##
|
61
|
-
def
|
75
|
+
def rest_create_w_json_params(resource:, parameters:)
|
62
76
|
wrap_response_with_error_handling do
|
63
77
|
self.class.post(
|
64
|
-
|
78
|
+
absolute_path("/#{Array(resource).join('/')}"),
|
65
79
|
{ headers: headers,
|
66
80
|
body: parameters&.to_json }.merge!(request_options)
|
67
81
|
)
|
@@ -69,15 +83,17 @@ module Asimov
|
|
69
83
|
end
|
70
84
|
|
71
85
|
##
|
72
|
-
# Executes
|
86
|
+
# Executes a REST create with multipart-encoded parameters for the specified
|
87
|
+
# resource.
|
73
88
|
#
|
74
|
-
# @param [String]
|
75
|
-
#
|
89
|
+
# @param [String] the resource to be created.
|
90
|
+
# @param [Hash] parameters the optional parameters to include with the request
|
91
|
+
# to create the resource
|
76
92
|
##
|
77
|
-
def
|
93
|
+
def rest_create_w_multipart_params(resource:, parameters: nil)
|
78
94
|
wrap_response_with_error_handling do
|
79
95
|
self.class.post(
|
80
|
-
|
96
|
+
absolute_path("/#{Array(resource).join('/')}"),
|
81
97
|
{ headers: headers("multipart/form-data"),
|
82
98
|
body: parameters }.merge!(request_options)
|
83
99
|
)
|
@@ -85,15 +101,14 @@ module Asimov
|
|
85
101
|
end
|
86
102
|
|
87
103
|
##
|
88
|
-
# Executes an
|
104
|
+
# Executes an REST get on the specified path, streaming the resulting body
|
89
105
|
# to the writer in case of success.
|
90
106
|
#
|
91
|
-
# @param [
|
92
|
-
# base_uri) against which the POST is executed.
|
107
|
+
# @param [Array] resource the resource path elements as an array
|
93
108
|
# @param [Writer] writer an object, typically a File, that responds to a `write` method
|
94
109
|
##
|
95
|
-
def
|
96
|
-
self.class.get(
|
110
|
+
def rest_get_streamed_download(resource:, writer:)
|
111
|
+
self.class.get(absolute_path("/#{Array(resource).join('/')}"),
|
97
112
|
{ headers: headers,
|
98
113
|
stream_body: true }.merge!(request_options)) do |fragment|
|
99
114
|
fragment.code == 200 ? writer.write(fragment) : check_for_api_error(fragment)
|
@@ -105,6 +120,10 @@ module Asimov
|
|
105
120
|
|
106
121
|
private
|
107
122
|
|
123
|
+
def absolute_path(path)
|
124
|
+
"#{openai_api_base}#{path}"
|
125
|
+
end
|
126
|
+
|
108
127
|
def wrap_response_with_error_handling
|
109
128
|
resp = begin
|
110
129
|
yield
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative "../utils/chat_messages_validator"
|
2
|
+
|
3
|
+
module Asimov
|
4
|
+
module ApiV1
|
5
|
+
##
|
6
|
+
# Class interface for API methods in the "/chat" URI subspace.
|
7
|
+
##
|
8
|
+
class Chat < Base
|
9
|
+
RESOURCE = "chat".freeze
|
10
|
+
|
11
|
+
##
|
12
|
+
# Creates a completion request with the specified parameters.
|
13
|
+
#
|
14
|
+
# @param [String] model the model to use for the completion
|
15
|
+
# @param [Hash] parameters the set of parameters being passed to the API
|
16
|
+
##
|
17
|
+
def create_completions(model:, messages:, parameters: {})
|
18
|
+
raise MissingRequiredParameterError.new(:model) unless model
|
19
|
+
raise MissingRequiredParameterError.new(:messages) unless messages
|
20
|
+
raise StreamingResponseNotSupportedError if parameters[:stream]
|
21
|
+
|
22
|
+
messages = Utils::ChatMessagesValidator.validate_and_normalize(messages)
|
23
|
+
rest_create_w_json_params(resource: [RESOURCE, "completions"],
|
24
|
+
parameters: parameters.merge({ model: model,
|
25
|
+
messages: messages }))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -5,7 +5,7 @@ module Asimov
|
|
5
5
|
##
|
6
6
|
class Completions < Base
|
7
7
|
##
|
8
|
-
#
|
8
|
+
# Creates a completion request with the specified parameters.
|
9
9
|
#
|
10
10
|
# @param [String] model the model to use for the completion
|
11
11
|
# @param [Hash] parameters the set of parameters being passed to the API
|
@@ -14,7 +14,8 @@ module Asimov
|
|
14
14
|
raise MissingRequiredParameterError.new(:model) unless model
|
15
15
|
raise StreamingResponseNotSupportedError if parameters[:stream]
|
16
16
|
|
17
|
-
|
17
|
+
rest_create_w_json_params(resource: "completions",
|
18
|
+
parameters: parameters.merge({ model: model }))
|
18
19
|
end
|
19
20
|
end
|
20
21
|
end
|
data/lib/asimov/api_v1/edits.rb
CHANGED
@@ -5,7 +5,7 @@ module Asimov
|
|
5
5
|
##
|
6
6
|
class Edits < Base
|
7
7
|
##
|
8
|
-
#
|
8
|
+
# Creates an edit resource with the specified parameters.
|
9
9
|
#
|
10
10
|
# @param [Hash] parameters the set of parameters being passed to the API
|
11
11
|
##
|
@@ -13,9 +13,9 @@ module Asimov
|
|
13
13
|
raise MissingRequiredParameterError.new(:model) unless model
|
14
14
|
raise MissingRequiredParameterError.new(:instruction) unless instruction
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
rest_create_w_json_params(resource: "edits",
|
17
|
+
parameters: parameters.merge({ model: model,
|
18
|
+
instruction: instruction }))
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -5,15 +5,21 @@ module Asimov
|
|
5
5
|
##
|
6
6
|
class Embeddings < Base
|
7
7
|
##
|
8
|
-
#
|
8
|
+
# Creates an embedding resource with the specified parameters.
|
9
9
|
#
|
10
|
+
# @param [String] model the id for the model used to create the embedding
|
11
|
+
# @param [String] parameters the (optional) additional parameters being
|
12
|
+
# provided to inform embedding creation.
|
10
13
|
# @param [Hash] parameters the set of parameters being passed to the API
|
11
14
|
##
|
12
15
|
def create(model:, input:, parameters: {})
|
13
16
|
raise MissingRequiredParameterError.new(:model) unless model
|
14
17
|
raise MissingRequiredParameterError.new(:input) unless input
|
15
18
|
|
16
|
-
|
19
|
+
rest_create_w_json_params(resource: "embeddings",
|
20
|
+
parameters: parameters.merge({
|
21
|
+
model: model, input: input
|
22
|
+
}))
|
17
23
|
end
|
18
24
|
end
|
19
25
|
end
|
data/lib/asimov/api_v1/files.rb
CHANGED
@@ -10,14 +10,14 @@ module Asimov
|
|
10
10
|
# Class interface for API methods in the "/files" URI subspace.
|
11
11
|
##
|
12
12
|
class Files < Base
|
13
|
-
|
14
|
-
private_constant :
|
13
|
+
RESOURCE = "files".freeze
|
14
|
+
private_constant :RESOURCE
|
15
15
|
|
16
16
|
##
|
17
17
|
# Lists files that have been uploaded to OpenAI
|
18
18
|
##
|
19
19
|
def list
|
20
|
-
|
20
|
+
rest_index(resource: RESOURCE)
|
21
21
|
end
|
22
22
|
|
23
23
|
##
|
@@ -32,8 +32,8 @@ module Asimov
|
|
32
32
|
|
33
33
|
validate(file, purpose)
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
rest_create_w_multipart_params(
|
36
|
+
resource: RESOURCE,
|
37
37
|
parameters: parameters.merge(file: Utils::FileManager.open(file), purpose: purpose)
|
38
38
|
)
|
39
39
|
end
|
@@ -44,7 +44,7 @@ module Asimov
|
|
44
44
|
# @param [String] file_id the id of the file to be retrieved
|
45
45
|
##
|
46
46
|
def retrieve(file_id:)
|
47
|
-
|
47
|
+
rest_get(resource: RESOURCE, id: file_id)
|
48
48
|
end
|
49
49
|
|
50
50
|
##
|
@@ -53,7 +53,7 @@ module Asimov
|
|
53
53
|
# @param [String] file_id the id of the file to be deleted
|
54
54
|
##
|
55
55
|
def delete(file_id:)
|
56
|
-
|
56
|
+
rest_delete(resource: RESOURCE, id: file_id)
|
57
57
|
end
|
58
58
|
|
59
59
|
##
|
@@ -65,7 +65,7 @@ module Asimov
|
|
65
65
|
# as it is received from the API
|
66
66
|
##
|
67
67
|
def content(file_id:, writer:)
|
68
|
-
|
68
|
+
rest_get_streamed_download(resource: [RESOURCE, file_id, "content"], writer: writer)
|
69
69
|
end
|
70
70
|
|
71
71
|
private
|
@@ -4,14 +4,14 @@ module Asimov
|
|
4
4
|
# Class interface for API methods in the "/fine-tunes" URI subspace.
|
5
5
|
##
|
6
6
|
class Finetunes < Base
|
7
|
-
|
8
|
-
private_constant :
|
7
|
+
RESOURCE = "fine-tunes".freeze
|
8
|
+
private_constant :RESOURCE
|
9
9
|
|
10
10
|
##
|
11
11
|
# Lists the set of fine-tuning jobs for this API key and (optionally) organization.
|
12
12
|
##
|
13
13
|
def list
|
14
|
-
|
14
|
+
rest_index(resource: RESOURCE)
|
15
15
|
end
|
16
16
|
|
17
17
|
##
|
@@ -23,7 +23,8 @@ module Asimov
|
|
23
23
|
def create(training_file:, parameters: {})
|
24
24
|
raise MissingRequiredParameterError.new(:training_file) unless training_file
|
25
25
|
|
26
|
-
|
26
|
+
rest_create_w_json_params(resource: RESOURCE,
|
27
|
+
parameters: parameters.merge(training_file: training_file))
|
27
28
|
end
|
28
29
|
|
29
30
|
##
|
@@ -32,7 +33,7 @@ module Asimov
|
|
32
33
|
# @param [String] fine_tune_id the id of fine tuning job
|
33
34
|
##
|
34
35
|
def retrieve(fine_tune_id:)
|
35
|
-
|
36
|
+
rest_get(resource: RESOURCE, id: fine_tune_id)
|
36
37
|
end
|
37
38
|
|
38
39
|
##
|
@@ -41,7 +42,7 @@ module Asimov
|
|
41
42
|
# @param [String] fine_tune_id the id of fine tuning job
|
42
43
|
##
|
43
44
|
def cancel(fine_tune_id:)
|
44
|
-
|
45
|
+
rest_create_w_multipart_params(resource: [RESOURCE, fine_tune_id, "cancel"])
|
45
46
|
end
|
46
47
|
|
47
48
|
##
|
@@ -50,7 +51,7 @@ module Asimov
|
|
50
51
|
# @param [String] fine_tune_id the id of fine tuning job
|
51
52
|
##
|
52
53
|
def list_events(fine_tune_id:)
|
53
|
-
|
54
|
+
rest_index(resource: [RESOURCE, fine_tune_id, "events"])
|
54
55
|
end
|
55
56
|
end
|
56
57
|
end
|
data/lib/asimov/api_v1/images.rb
CHANGED
@@ -6,8 +6,8 @@ module Asimov
|
|
6
6
|
# Class interface for API methods in the "/images" URI subspace.
|
7
7
|
##
|
8
8
|
class Images < Base
|
9
|
-
|
10
|
-
private_constant :
|
9
|
+
RESOURCE = "images".freeze
|
10
|
+
private_constant :RESOURCE
|
11
11
|
|
12
12
|
##
|
13
13
|
# Creates an image using the specified prompt.
|
@@ -18,8 +18,8 @@ module Asimov
|
|
18
18
|
def create(prompt:, parameters: {})
|
19
19
|
raise MissingRequiredParameterError.new(:prompt) unless prompt
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
rest_create_w_json_params(resource: [RESOURCE, "generations"],
|
22
|
+
parameters: parameters.merge({ prompt: prompt }))
|
23
23
|
end
|
24
24
|
|
25
25
|
##
|
@@ -32,8 +32,11 @@ module Asimov
|
|
32
32
|
def create_edit(image:, prompt:, parameters: {})
|
33
33
|
raise MissingRequiredParameterError.new(:prompt) unless prompt
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
rest_create_w_multipart_params(resource: [RESOURCE, "edits"],
|
36
|
+
parameters: open_files(parameters.merge({
|
37
|
+
image: image,
|
38
|
+
prompt: prompt
|
39
|
+
})))
|
37
40
|
end
|
38
41
|
|
39
42
|
##
|
@@ -44,8 +47,8 @@ module Asimov
|
|
44
47
|
# @option parameters [String] :mask mask file name or a File-like object
|
45
48
|
##
|
46
49
|
def create_variation(image:, parameters: {})
|
47
|
-
|
48
|
-
|
50
|
+
rest_create_w_multipart_params(resource: [RESOURCE, "variations"],
|
51
|
+
parameters: open_files(parameters.merge({ image: image })))
|
49
52
|
end
|
50
53
|
|
51
54
|
private
|
@@ -54,7 +57,7 @@ module Asimov
|
|
54
57
|
raise MissingRequiredParameterError.new(:image) unless parameters[:image]
|
55
58
|
|
56
59
|
parameters = parameters.merge(image: Utils::FileManager.open(parameters[:image]))
|
57
|
-
parameters
|
60
|
+
parameters[:mask] = Utils::FileManager.open(parameters[:mask]) if parameters[:mask]
|
58
61
|
parameters
|
59
62
|
end
|
60
63
|
end
|
data/lib/asimov/api_v1/models.rb
CHANGED
@@ -4,15 +4,15 @@ module Asimov
|
|
4
4
|
# Class interface for API methods in the "/models" URI subspace.
|
5
5
|
##
|
6
6
|
class Models < Base
|
7
|
-
|
8
|
-
private_constant :
|
7
|
+
RESOURCE = "models".freeze
|
8
|
+
private_constant :RESOURCE
|
9
9
|
|
10
10
|
##
|
11
11
|
# Lists the models accessible to this combination of OpenAI API
|
12
12
|
# key and organization id.
|
13
13
|
##
|
14
14
|
def list
|
15
|
-
|
15
|
+
rest_index(resource: RESOURCE)
|
16
16
|
end
|
17
17
|
|
18
18
|
##
|
@@ -22,7 +22,7 @@ module Asimov
|
|
22
22
|
# @param [String] model_id the id of the model to be retrieved
|
23
23
|
##
|
24
24
|
def retrieve(model_id:)
|
25
|
-
|
25
|
+
rest_get(resource: RESOURCE, id: model_id)
|
26
26
|
end
|
27
27
|
|
28
28
|
##
|
@@ -32,7 +32,7 @@ module Asimov
|
|
32
32
|
# @param [String] model_id the id of the model to be deleted
|
33
33
|
##
|
34
34
|
def delete(model_id:)
|
35
|
-
|
35
|
+
rest_delete(resource: RESOURCE, id: model_id)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -13,7 +13,8 @@ module Asimov
|
|
13
13
|
def create(input:, parameters: {})
|
14
14
|
raise MissingRequiredParameterError.new(:input) unless input
|
15
15
|
|
16
|
-
|
16
|
+
rest_create_w_json_params(resource: "moderations",
|
17
|
+
parameters: parameters.merge({ input: input }))
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
data/lib/asimov/client.rb
CHANGED
@@ -3,6 +3,8 @@ require "httparty"
|
|
3
3
|
require_relative "headers_factory"
|
4
4
|
require_relative "utils/request_options_validator"
|
5
5
|
require_relative "api_v1/base"
|
6
|
+
require_relative "api_v1/audio"
|
7
|
+
require_relative "api_v1/chat"
|
6
8
|
require_relative "api_v1/completions"
|
7
9
|
require_relative "api_v1/edits"
|
8
10
|
require_relative "api_v1/embeddings"
|
@@ -20,7 +22,7 @@ module Asimov
|
|
20
22
|
class Client
|
21
23
|
extend Forwardable
|
22
24
|
|
23
|
-
attr_reader :api_key, :organization_id, :api_version, :request_options
|
25
|
+
attr_reader :api_key, :organization_id, :api_version, :request_options, :openai_api_base
|
24
26
|
|
25
27
|
##
|
26
28
|
# Creates a new Asimov::Client. Includes several optional named parameters:
|
@@ -29,17 +31,36 @@ module Asimov
|
|
29
31
|
# defaults to the application-wide configuration
|
30
32
|
# organization_id - The OpenAI organization identifier that this Asimov::Client instance
|
31
33
|
# will use. If unspecified, defaults to the application-wide configuration.
|
34
|
+
# request_options - HTTParty request options that will be passed to the underlying network
|
35
|
+
# client. Merges (and overrides) global configuration value.
|
36
|
+
# openai_api_base - Custom base URI for the API calls made by this client. Defaults to global
|
37
|
+
# configuration value.
|
32
38
|
##
|
33
39
|
def initialize(api_key: nil, organization_id: HeadersFactory::NULL_ORGANIZATION_ID,
|
34
|
-
request_options: {})
|
40
|
+
request_options: {}, openai_api_base: nil)
|
35
41
|
@headers_factory = HeadersFactory.new(api_key,
|
36
42
|
organization_id)
|
37
43
|
@request_options = Asimov.configuration.request_options
|
38
44
|
.merge(Utils::RequestOptionsValidator.validate(request_options))
|
39
45
|
.freeze
|
46
|
+
initialize_openai_api_base(openai_api_base)
|
40
47
|
end
|
41
48
|
def_delegators :@headers_factory, :api_key, :organization_id, :headers
|
42
49
|
|
50
|
+
##
|
51
|
+
# Use the audio method to access API calls in the /audio URI space.
|
52
|
+
##
|
53
|
+
def audio
|
54
|
+
@audio ||= Asimov::ApiV1::Audio.new(client: self)
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Use the chat method to access API calls in the /chat URI space.
|
59
|
+
##
|
60
|
+
def chat
|
61
|
+
@chat ||= Asimov::ApiV1::Chat.new(client: self)
|
62
|
+
end
|
63
|
+
|
43
64
|
##
|
44
65
|
# Use the completions method to access API calls in the /completions URI space.
|
45
66
|
##
|
@@ -95,5 +116,17 @@ module Asimov
|
|
95
116
|
def moderations
|
96
117
|
@moderations ||= Asimov::ApiV1::Moderations.new(client: self)
|
97
118
|
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def initialize_openai_api_base(openai_api_base)
|
123
|
+
@openai_api_base = openai_api_base || Asimov.configuration.openai_api_base
|
124
|
+
if @openai_api_base
|
125
|
+
@openai_api_base = HTTParty.normalize_base_uri(@openai_api_base)
|
126
|
+
else
|
127
|
+
raise Asimov::MissingBaseUriError,
|
128
|
+
"No API Base URI was provided for this client."
|
129
|
+
end
|
130
|
+
end
|
98
131
|
end
|
99
132
|
end
|
data/lib/asimov/configuration.rb
CHANGED
@@ -9,7 +9,9 @@ module Asimov
|
|
9
9
|
class Configuration
|
10
10
|
attr_accessor :api_key, :organization_id
|
11
11
|
|
12
|
-
attr_reader :request_options
|
12
|
+
attr_reader :request_options, :openai_api_base
|
13
|
+
|
14
|
+
DEFAULT_OPENAI_BASE_URI = "https://api.openai.com/v1".freeze
|
13
15
|
|
14
16
|
##
|
15
17
|
# Initializes the Configuration object and resets it to default values.
|
@@ -25,6 +27,15 @@ module Asimov
|
|
25
27
|
@api_key = nil
|
26
28
|
@organization_id = nil
|
27
29
|
@request_options = {}
|
30
|
+
@openai_api_base = DEFAULT_OPENAI_BASE_URI
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Sets the openai_api_base on the Configuration. Typically not invoked
|
35
|
+
# directly, but rather through use of `Asimov.configure`.
|
36
|
+
##
|
37
|
+
def openai_api_base=(val)
|
38
|
+
@openai_api_base = val ? HTTParty.normalize_base_uri(val) : nil
|
28
39
|
end
|
29
40
|
|
30
41
|
##
|
data/lib/asimov/error.rb
CHANGED
@@ -15,6 +15,12 @@ module Asimov
|
|
15
15
|
##
|
16
16
|
class MissingApiKeyError < ConfigurationError; end
|
17
17
|
|
18
|
+
##
|
19
|
+
# Error that occurs when there is no configured
|
20
|
+
# base URI for a newly created Asimov::Client.
|
21
|
+
##
|
22
|
+
class MissingBaseUriError < ConfigurationError; end
|
23
|
+
|
18
24
|
##
|
19
25
|
# Errors that occur when making an API request. They
|
20
26
|
# can occur either through local validation or
|
@@ -136,6 +142,12 @@ module Asimov
|
|
136
142
|
##
|
137
143
|
class InvalidClassificationError < FileDataError; end
|
138
144
|
|
145
|
+
##
|
146
|
+
# Error that occurs when the provided chat messages
|
147
|
+
# parameter is not valid.
|
148
|
+
##
|
149
|
+
class InvalidChatMessagesError < RequestError; end
|
150
|
+
|
139
151
|
##
|
140
152
|
# Error that occurs when an invalid value is provided
|
141
153
|
# for an expected parameter in a request.
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Asimov
|
2
|
+
module Utils
|
3
|
+
##
|
4
|
+
# Validator for OpenAI's chat message format
|
5
|
+
##
|
6
|
+
class ChatMessagesValidator
|
7
|
+
def self.validate_and_normalize(messages)
|
8
|
+
new.validate(messages)
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate(messages)
|
12
|
+
raise InvalidChatMessagesError, "Chat messages cannot be nil." if messages.nil?
|
13
|
+
|
14
|
+
unless messages.is_a?(Array)
|
15
|
+
raise InvalidChatMessagesError,
|
16
|
+
"Chat messages must be an array."
|
17
|
+
end
|
18
|
+
|
19
|
+
messages.map do |message|
|
20
|
+
validate_message(normalize_parsed(message))
|
21
|
+
end
|
22
|
+
rescue JSON::ParserError
|
23
|
+
raise InvalidChatMessagesError, "Chat messages must be valid JSON."
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate_message(message)
|
27
|
+
raise InvalidChatMessagesError, "Chat messages must be hashes." unless message.is_a?(Hash)
|
28
|
+
|
29
|
+
validate_role(message["role"])
|
30
|
+
|
31
|
+
content = message["content"]
|
32
|
+
raise InvalidChatMessagesError, "Chat messages must have content." if content.nil?
|
33
|
+
|
34
|
+
validate_keys(message)
|
35
|
+
message
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate_keys(json)
|
39
|
+
additional_keys = json.keys - %w[role content]
|
40
|
+
return if additional_keys.empty?
|
41
|
+
|
42
|
+
raise InvalidChatMessagesError,
|
43
|
+
"Chat messages must not have additional keys - #{additional_keys.join(', ')}."
|
44
|
+
end
|
45
|
+
|
46
|
+
ALLOWED_ROLES = %w[assistant system user].freeze
|
47
|
+
def validate_role(role)
|
48
|
+
raise InvalidChatMessagesError, "Chat messages must have a role." if role.nil?
|
49
|
+
|
50
|
+
return true if ALLOWED_ROLES.include?(role)
|
51
|
+
|
52
|
+
raise InvalidChatMessagesError,
|
53
|
+
"The value '#{role}' is not a valid role for a chat message."
|
54
|
+
end
|
55
|
+
|
56
|
+
def normalize_parsed(message)
|
57
|
+
return message if message.is_a?(String)
|
58
|
+
|
59
|
+
JSON.parse(message.respond_to?(:to_json) ? message.to_json : message)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/asimov/version.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: 1.
|
4
|
+
version: 1.1.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-
|
11
|
+
date: 2023-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -44,7 +44,9 @@ files:
|
|
44
44
|
- README.md
|
45
45
|
- lib/asimov.rb
|
46
46
|
- lib/asimov/api_v1/api_error_translator.rb
|
47
|
+
- lib/asimov/api_v1/audio.rb
|
47
48
|
- lib/asimov/api_v1/base.rb
|
49
|
+
- lib/asimov/api_v1/chat.rb
|
48
50
|
- lib/asimov/api_v1/completions.rb
|
49
51
|
- lib/asimov/api_v1/edits.rb
|
50
52
|
- lib/asimov/api_v1/embeddings.rb
|
@@ -58,6 +60,7 @@ files:
|
|
58
60
|
- lib/asimov/configuration.rb
|
59
61
|
- lib/asimov/error.rb
|
60
62
|
- lib/asimov/headers_factory.rb
|
63
|
+
- lib/asimov/utils/chat_messages_validator.rb
|
61
64
|
- lib/asimov/utils/classifications_file_validator.rb
|
62
65
|
- lib/asimov/utils/file_manager.rb
|
63
66
|
- lib/asimov/utils/jsonl_validator.rb
|
@@ -88,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
91
|
- !ruby/object:Gem::Version
|
89
92
|
version: '0'
|
90
93
|
requirements: []
|
91
|
-
rubygems_version: 3.4.
|
94
|
+
rubygems_version: 3.4.7
|
92
95
|
signing_key:
|
93
96
|
specification_version: 4
|
94
97
|
summary: A Ruby client for the OpenAI API support for multiple API configurations
|