bdd_openai 1.0.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: a9ba274250360c79996a8892b04ee815fa9d56b6f86ef89fb1c358ef1d5947d5
4
+ data.tar.gz: 59d776ac2bdc32da4b925714088cb405151fd4be634e7377b32f6ae85ccf725f
5
+ SHA512:
6
+ metadata.gz: 233a711547dd485ac98b58c6a69287e431a198b2f5cb566bd4712607afcb034ba053fb6b52476fd2642b1bd288c091b90df6270f2bc3f3d39c31ad8fc264ad54
7
+ data.tar.gz: 4b4fd9a050fc82eaedb572e1871b3d4152bc6a0cfe0cb81c1643c7af7296d49831d0103716796e00d709c479bb6161859b736f81561cd6265fa655e6ca8a8ce7
data/.env.sample ADDED
@@ -0,0 +1 @@
1
+ OPENAI_API_KEY=put-your-openai-api-key-here
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,299 @@
1
+ ########################################
2
+ # EmploymentHero Shared Rubocop Config #
3
+ ########################################
4
+ # rubocop ~> 1.15.0
5
+
6
+ AllCops:
7
+ Exclude:
8
+ - 'db/schema.rb'
9
+ - 'node_modules/**/*'
10
+ - 'vendor/**/*'
11
+ - '.git/**/*'
12
+ EnabledByDefault: false
13
+ DisabledByDefault: false
14
+ NewCops: disable
15
+
16
+ Bundler:
17
+ Severity: warning
18
+
19
+ Bundler/OrderedGems:
20
+ Description: >-
21
+ Gems within groups in the Gemfile should be alphabetically sorted.
22
+ Enabled: false
23
+ VersionAdded: '0.46'
24
+ VersionChanged: '0.47'
25
+ TreatCommentsAsGroupSeparators: true
26
+ Include:
27
+ - '**/*.gemfile'
28
+ - '**/Gemfile'
29
+ - '**/gems.rb'
30
+
31
+ Gemspec:
32
+ Severity: warning
33
+
34
+ Layout/ClassStructure:
35
+ Severity: warning
36
+ Description: 'Enforces a configured order of definitions within a class body.'
37
+ StyleGuide: 'https://github.com/rubocop-hq/ruby-style-guide#consistent-classes'
38
+ Enabled: true
39
+ VersionAdded: '0.52'
40
+ Categories:
41
+ module_inclusion:
42
+ - include
43
+ - prepend
44
+ - extend
45
+ ExpectedOrder:
46
+ - module_inclusion
47
+ - constants
48
+ - public_class_methods
49
+ - initializer
50
+ - public_methods
51
+ - protected_methods
52
+ - private_methods
53
+
54
+ Metrics:
55
+ Severity: warning
56
+
57
+ Metrics/BlockLength:
58
+ Description: 'Avoid long blocks with many lines.'
59
+ Enabled: true
60
+ VersionAdded: '0.44'
61
+ VersionChanged: '0.58'
62
+ CountComments: false # count full line comments?
63
+ Max: 25
64
+ IgnoredMethods:
65
+ # By default, exclude the `#refine` method, as it tends to have larger
66
+ # associated blocks.
67
+ - configure
68
+ - included
69
+ - refine
70
+ - transaction
71
+ # rake methods
72
+ - namespace
73
+ - task
74
+ # rails methods
75
+ - create_table
76
+ - update_table
77
+ - setup
78
+ # grape api methods
79
+ - helpers
80
+ - group
81
+ - resource
82
+ - resources
83
+ - segment
84
+ - get
85
+ - post
86
+ - put
87
+ - patch
88
+ - delete
89
+ - route_param
90
+ # rspec methods
91
+ - after
92
+ - let
93
+ - let!
94
+ - before
95
+ - context
96
+ - describe
97
+ - describe_api
98
+ - feature
99
+ - shared_context
100
+ - shared_examples
101
+ - shared_examples_for
102
+ # factory_bot methods
103
+ - define
104
+ - factory
105
+ # cucumber methods
106
+ - scenario
107
+ - then
108
+ - when
109
+ - within
110
+ # pundit spec methods
111
+ - permissions
112
+ # Benchmark methods
113
+ - measure
114
+
115
+ Metrics/ClassLength:
116
+ Description: 'Avoid classes longer than 200 lines of code.'
117
+ Enabled: true
118
+ VersionAdded: '0.25'
119
+ CountComments: false # count full line comments?
120
+ Max: 200
121
+
122
+ # Avoid complex methods.
123
+ Metrics/CyclomaticComplexity:
124
+ Description: >-
125
+ A complexity metric that is strongly correlated to the number
126
+ of test cases needed to validate a method.
127
+ Enabled: true
128
+ VersionAdded: '0.25'
129
+ Max: 10
130
+
131
+ Layout/LineLength:
132
+ Description: 'Limit lines to 120 characters.'
133
+ StyleGuide: '#80-character-limits'
134
+ Enabled: true
135
+ VersionAdded: '0.25'
136
+ VersionChanged: '0.46'
137
+ Max: 120
138
+ # To make it possible to copy or click on URIs in the code, we allow lines
139
+ # containing a URI to be longer than Max.
140
+ AllowHeredoc: true
141
+ AllowURI: true
142
+ URISchemes:
143
+ - http
144
+ - https
145
+ # The IgnoreCopDirectives option causes the LineLength rule to ignore cop
146
+ # directives like '# rubocop: enable ...' when calculating a line's length.
147
+ IgnoreCopDirectives: true
148
+ # The IgnoredPatterns option is a list of !ruby/regexp and/or string
149
+ # elements. Strings will be converted to Regexp objects. A line that matches
150
+ # any regular expression listed in this option will be ignored by LineLength.
151
+ IgnoredPatterns: []
152
+
153
+ Metrics/MethodLength:
154
+ Description: 'Avoid methods longer than 15 lines of code.'
155
+ StyleGuide: '#short-methods'
156
+ Enabled: true
157
+ VersionAdded: '0.25'
158
+ VersionChanged: '0.59.2'
159
+ CountComments: false # count full line comments?
160
+ Max: 15
161
+ IgnoredMethods: []
162
+
163
+ Metrics/ModuleLength:
164
+ Description: 'Avoid modules longer than 200 lines of code.'
165
+ Enabled: true
166
+ VersionAdded: '0.31'
167
+ CountComments: false # count full line comments?
168
+ Max: 200
169
+
170
+ Metrics/PerceivedComplexity:
171
+ Description: >-
172
+ A complexity metric geared towards measuring complexity for a
173
+ human reader.
174
+ Enabled: true
175
+ VersionAdded: '0.25'
176
+ Max: 10
177
+
178
+ Security:
179
+ Severity: warning
180
+
181
+ Style/Alias:
182
+ Description: 'Use alias instead of alias_method.'
183
+ StyleGuide: '#alias-method'
184
+ Enabled: false
185
+ VersionAdded: '0.9'
186
+ VersionChanged: '0.36'
187
+ EnforcedStyle: prefer_alias
188
+ SupportedStyles:
189
+ - prefer_alias
190
+ - prefer_alias_method
191
+
192
+ Style/AndOr:
193
+ Severity: true
194
+ Description: 'Use &&/|| instead of and/or.'
195
+ StyleGuide: '#no-and-or-or'
196
+ Enabled: true
197
+ VersionAdded: '0.9'
198
+ VersionChanged: '0.25'
199
+ # Whether `and` and `or` are banned only in conditionals (conditionals)
200
+ # or completely (always).
201
+ EnforcedStyle: conditionals
202
+ SupportedStyles:
203
+ - always
204
+ - conditionals
205
+
206
+ Style/BlockDelimiters:
207
+ Severity: warning
208
+ Description: >-
209
+ Avoid using {...} for multi-line blocks (multiline chaining is
210
+ always ugly).
211
+ Prefer {...} over do...end for single-line blocks.
212
+ StyleGuide: '#single-line-blocks'
213
+ Enabled: true
214
+ VersionAdded: '0.30'
215
+ VersionChanged: '0.35'
216
+ EnforcedStyle: braces_for_chaining
217
+ SupportedStyles:
218
+ # The `line_count_based` style enforces braces around single line blocks and
219
+ # do..end around multi-line blocks.
220
+ - line_count_based
221
+ # The `semantic` style enforces braces around functional blocks, where the
222
+ # primary purpose of the block is to return a value and do..end for
223
+ # procedural blocks, where the primary purpose of the block is its
224
+ # side-effects.
225
+ #
226
+ # This looks at the usage of a block's method to determine its type (e.g. is
227
+ # the result of a `map` assigned to a variable or passed to another
228
+ # method) but exceptions are permitted in the `ProceduralMethods`,
229
+ # `FunctionalMethods` and `IgnoredMethods` sections below.
230
+ - semantic
231
+ # The `braces_for_chaining` style enforces braces around single line blocks
232
+ # and do..end around multi-line blocks, except for multi-line blocks whose
233
+ # return value is being chained with another method (in which case braces
234
+ # are enforced).
235
+ - braces_for_chaining
236
+ ProceduralMethods:
237
+ # Methods that are known to be procedural in nature but look functional from
238
+ # their usage, e.g.
239
+ #
240
+ # time = Benchmark.realtime do
241
+ # foo.bar
242
+ # end
243
+ #
244
+ # Here, the return value of the block is discarded but the return value of
245
+ # `Benchmark.realtime` is used.
246
+ - benchmark
247
+ - bm
248
+ - bmbm
249
+ - create
250
+ - each_with_object
251
+ - measure
252
+ - new
253
+ - realtime
254
+ - tap
255
+ - with_object
256
+ FunctionalMethods:
257
+ # Methods that are known to be functional in nature but look procedural from
258
+ # their usage, e.g.
259
+ #
260
+ # let(:foo) { Foo.new }
261
+ #
262
+ # Here, the return value of `Foo.new` is used to define a `foo` helper but
263
+ # doesn't appear to be used from the return value of `let`.
264
+ - let
265
+ - let!
266
+ - subject
267
+ - watch
268
+ IgnoredMethods:
269
+ # Methods that can be either procedural or functional and cannot be
270
+ # categorised from their usage alone, e.g.
271
+ #
272
+ # foo = lambda do |x|
273
+ # puts "Hello, #{x}"
274
+ # end
275
+ #
276
+ # foo = lambda do |x|
277
+ # x * 100
278
+ # end
279
+ #
280
+ # Here, it is impossible to tell from the return value of `lambda` whether
281
+ # the inner block's return value is significant.
282
+ - lambda
283
+ - proc
284
+ - it
285
+
286
+ Style/Documentation:
287
+ Description: 'Document classes and non-namespace modules.'
288
+ Enabled: false
289
+ VersionAdded: '0.9'
290
+ Exclude:
291
+ - 'spec/**/*'
292
+ - 'test/**/*'
293
+
294
+ Style/MultipleComparison:
295
+ Description: >-
296
+ Avoid comparing a variable with multiple items in a conditional,
297
+ use Array#include? instead.
298
+ Enabled: false
299
+ VersionAdded: '0.49'
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ inherit_from:
2
+ - https://raw.githubusercontent.com/Thinkei/Thinkei/master/.rubocop_default_v1.yml
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7.8
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [1.0.0] - 2023-12-10
6
+
7
+ ### Added
8
+
9
+ - Init the Gem project using RubyMine template
10
+ - Create module BddOpenai::Files
11
+
12
+ ### Changed
13
+
14
+ - N/A
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at brendon.dao@employmenthero.com. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Brendon Dao
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,62 @@
1
+ # BddOpenai
2
+
3
+ This gem is a personal exercise to get familiar with Ruby and Gem creating. gem Gem include only one top level namespace of TestOpenai which you can find all you need inside. Starting by creating an instance of BddOpenai::Files::Client.
4
+
5
+ For this exercise, the client will provide wrapper to some of OpenAI File APIs. API doc can bbe found here:
6
+ - https://platform.openai.com/docs/api-reference/files
7
+
8
+ Other reference links:
9
+ - https://github.com/brendondaoateh/bdd_openai
10
+ - https://rubygems.org/gems/bdd_openai
11
+ - https://rubydoc.info/gems/bdd_openai
12
+
13
+ ## Installation
14
+
15
+ You can install the gem normally through RubyGems.org.
16
+
17
+ Install the gem and add to the application's Gemfile by executing:
18
+
19
+ $ bundle add bdd_openai
20
+
21
+ If bundler is not being used to manage dependencies, install the gem by executing:
22
+
23
+ $ gem install bdd_openai
24
+
25
+ ## Usage
26
+
27
+ Visit `spec/bdd_openai/files/client_spec.rb` for full sample code. Or:
28
+
29
+ You can direct interact with gem by:
30
+
31
+ ```ruby
32
+ require "bdd_openai"
33
+
34
+ client = BddOpenai::Files::Client.new(ENV["OPENAI_API_KEY"])
35
+
36
+ client.list_files
37
+
38
+ client.upload_file("assistants", "spec/fixtures/sample.pdf")
39
+
40
+ client.delete_file("file-id")
41
+
42
+ client.retrieve_file("file-id")
43
+ ```
44
+
45
+ ## Testing
46
+
47
+ - Copy file `.env.sample` to `.env` and fill in your personal OpenAI API's key
48
+ - Run
49
+ ```
50
+ bundle install
51
+ rspec
52
+ ```
53
+
54
+ ### Test coverage
55
+
56
+ The repository is integrated with [SimpleCov](https://github.com/simplecov-ruby/simplecov) to generate test coverage report. You can find the report at `coverage/index.html` after running the test.
57
+
58
+ ![test_coverage_screenshot](docs/test_coverage_screenshot_231211_125800.png)
59
+
60
+ ## Contributing
61
+
62
+ Bug reports and pull requests are welcome on GitHub at https://github.com/brendondaoateh/bdd_openai.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/bdd_openai/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'bdd_openai'
7
+ spec.version = BddOpenai::VERSION
8
+ spec.authors = ['Brendon Dao']
9
+ spec.email = ['brendon.dao@employmenthero.com']
10
+ spec.metadata['source_code_uri'] = 'https://github.com/brendondaoateh/bdd_openai'
11
+ spec.summary = 'Write a short summary, because RubyGems requires one.'
12
+ spec.description = 'Write a longer description or delete this line.'
13
+
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 2.7.0'
16
+
17
+ # spec.metadata["allowed_push_host"] = "Set to your gem server 'https://example.com'"
18
+
19
+ spec.homepage = spec.metadata['source_code_uri']
20
+ spec.metadata['homepage_uri'] = spec.homepage
21
+ spec.metadata['changelog_uri'] = 'https://github.com/brendondaoateh/bdd_openai/blob/main/CHANGELOG.md'
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (File.expand_path(f) == __FILE__) ||
28
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
29
+ end
30
+ end
31
+ spec.bindir = 'exe'
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ['lib']
34
+
35
+ # Uncomment to register a new dependency of your gem
36
+ # spec.add_dependency "example-gem", "~> 1.0"
37
+
38
+ # For more information and examples about making a new gem, check out our
39
+ # guide at: https://bundler.io/guides/creating_gem.html
40
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BddOpenai
4
+ # Namespace for all external clients
5
+ module Client
6
+ # An HTTP client
7
+ class HttpClient
8
+ # @param uri [String]
9
+ # @param headers [Hash]
10
+ # @param disable_ssl [Boolean]
11
+ # @return [Net::HTTPResponse]
12
+ def call_get(uri, headers, disable_ssl: false)
13
+ http = Net::HTTP.new(uri.host, uri.port)
14
+ http.use_ssl = disable_ssl ? false : true
15
+ request = Net::HTTP::Get.new(uri.path, headers)
16
+ http.request(request)
17
+ end
18
+
19
+ # @param uri [String]
20
+ # @param req_body [String] Request body in the form of JSON string.
21
+ # @param headers [Hash]
22
+ # @param disable_ssl [Boolean]
23
+ # @return [Net::HTTPResponse]
24
+ def call_post(uri, req_body, headers, disable_ssl: false)
25
+ http = Net::HTTP.new(uri.host, uri.port)
26
+ http.use_ssl = disable_ssl ? false : true
27
+ request = Net::HTTP::Post.new(uri.path, headers)
28
+ request.body = req_body unless req_body.nil?
29
+ http.request(request)
30
+ end
31
+
32
+ # @param fields [Hash] The fields to be sent in the request body
33
+ # @param file_fields [Hash] The file fields to be sent in the request body, with value as the file path.
34
+ # @return [String, String (frozen)] The request body and the boundary string
35
+ def create_multipart_body(fields, file_fields)
36
+ boundary = "----#{SecureRandom.hex(16)}"
37
+ body = +''
38
+ fields.each do |name, value|
39
+ body << "--#{boundary}\r\n"
40
+ body << "Content-Disposition: form-data; name=\"#{name}\"\r\n\r\n"
41
+ body << "#{value}\r\n"
42
+ end
43
+ file_fields.each do |name, file_path|
44
+ body << "--#{boundary}\r\n"
45
+ body << "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{File.basename(file_path)}\"\r\n"
46
+ body << "Content-Type: application/octet-stream\r\n\r\n"
47
+ body << "#{File.read(file_path)}\r\n"
48
+ end
49
+ body << "--#{boundary}--\r\n"
50
+ [body, boundary]
51
+ end
52
+
53
+ # @param uri [String]
54
+ # @param headers [Hash]
55
+ # @param disable_ssl [Boolean]
56
+ # @return [Net::HTTPResponse]
57
+ def call_delete(uri, headers, disable_ssl: false)
58
+ http = Net::HTTP.new(uri.host, uri.port)
59
+ http.use_ssl = disable_ssl ? false : true
60
+ request = Net::HTTP::Delete.new(uri.path, headers)
61
+ http.request(request)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BddOpenai
4
+ # A error response of openai
5
+ class ErrorResponse
6
+ # @return [String] error message
7
+ attr_accessor :message
8
+ # @return [String] type of error
9
+ attr_accessor :type
10
+ # @return [String] parameter that caused the error
11
+ attr_accessor :param
12
+ # @return [String] error code of openai
13
+ attr_accessor :code
14
+
15
+ # @param response_body [String] The full response body of OpenAI API
16
+ # @return [BddOpenai::ErrorResponse]
17
+ def self.from_json(response_body)
18
+ data = JSON.parse(response_body)['error']
19
+ BddOpenai::ErrorResponse.new(
20
+ message: data['message'],
21
+ type: data['type'],
22
+ param: data['param'],
23
+ code: data['code']
24
+ )
25
+ end
26
+
27
+ def initialize(**args)
28
+ args.each do |k, v|
29
+ instance_variable_set("@#{k}", v) unless v.nil?
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BddOpenai
4
+ # Module for OpenAI Files API
5
+ # Ref: https://platform.openai.com/docs/api-reference/files
6
+ module Files
7
+ # Client for OpenAI Files API
8
+ class Client
9
+ # @param api_key [String] The key of the OpenAI API
10
+ def initialize(api_key = '')
11
+ @http_client = BddOpenai::Client::HttpClient.new
12
+ @openai_api_domain = 'https://api.openai.com/v1'
13
+ @openai_api_key = api_key
14
+ end
15
+
16
+ def default_headers
17
+ {
18
+ "Authorization": "Bearer #{@openai_api_key}",
19
+ "Content-Type": 'application/json'
20
+ }
21
+ end
22
+
23
+ # @return [Array<BddOpenai::Files::File>, BddOpenai::ErrorResponse]
24
+ def list_files
25
+ uri = URI.parse("#{@openai_api_domain}/files")
26
+ response = @http_client.call_get(uri, default_headers)
27
+ return BddOpenai::ErrorResponse.from_json(response.body) unless response.code == '200'
28
+
29
+ JSON.parse(response.body)['data'].map do |file|
30
+ BddOpenai::Files::File.from_json(file.to_json)
31
+ end
32
+ end
33
+
34
+ # @param purpose [String] The intended purpose of the file. One of: "fine-tune", "assistants".
35
+ # @param file_path [String] The path of the file to upload
36
+ # @return [BddOpenai::Files::File, BddOpenai::ErrorResponse]
37
+ def upload_file(purpose, file_path)
38
+ uri = URI.parse("#{@openai_api_domain}/files")
39
+ body, boundary = @http_client.create_multipart_body({ purpose: purpose }, { file: file_path })
40
+ headers = default_headers
41
+ .merge({
42
+ "Content-Type": "multipart/form-data; boundary=#{boundary}"
43
+ })
44
+ response = @http_client.call_post(uri, body, headers)
45
+ return BddOpenai::ErrorResponse.from_json(response.body) unless response.code == '200'
46
+
47
+ BddOpenai::Files::File.from_json(response.body)
48
+ end
49
+
50
+ # @param file_id [String] The id of the file to delete
51
+ # @return [true, BddOpenai::ErrorResponse]
52
+ def delete_file(file_id)
53
+ uri = URI.parse("#{@openai_api_domain}/files/#{file_id}")
54
+ response = @http_client.call_delete(uri, default_headers)
55
+ return BddOpenai::ErrorResponse.from_json(response.body) unless response.code == '200'
56
+
57
+ true
58
+ end
59
+
60
+ # @param file_id [String] The id of the file to retrieve
61
+ # @return [BddOpenai::Files::File, BddOpenai::ErrorResponse]
62
+ def retrieve_file(file_id)
63
+ uri = URI.parse("#{@openai_api_domain}/files/#{file_id}")
64
+ response = @http_client.call_get(uri, default_headers)
65
+ return BddOpenai::ErrorResponse.from_json(response.body) unless response.code == '200'
66
+
67
+ BddOpenai::Files::File.from_json(response.body)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BddOpenai
4
+ # Module for OpenAI Files API
5
+ # Ref: https://platform.openai.com/docs/api-reference/files
6
+ module Files
7
+ # The File object
8
+ # Ref: https://platform.openai.com/docs/api-reference/files/object
9
+ class File
10
+ # @return [String] The file identifier, which can be referenced in the API endpoints.
11
+ attr_accessor :id
12
+ # @return [Integer] The size of the file, in bytes.
13
+ attr_accessor :bytes
14
+ # @return [Integer] The Unix timestamp (in seconds) for when the file was created.
15
+ attr_accessor :created_at
16
+ # @return [String] The name of the file.
17
+ attr_accessor :filename
18
+ # @return [String] The object type, which is always "file".
19
+ attr_accessor :object
20
+ # @return [String] The intended purpose of the file. Supported values are \
21
+ # "fine-tune", "fine-tune-results", "assistants", and "assistants_output".
22
+ attr_accessor :purpose
23
+
24
+ def self.from_json(json_string)
25
+ data = JSON.parse(json_string)
26
+ new(
27
+ id: data['id'],
28
+ bytes: data['bytes'],
29
+ created_at: data['created_at'],
30
+ filename: data['filename'],
31
+ object: data['object'],
32
+ purpose: data['purpose']
33
+ )
34
+ end
35
+
36
+ def initialize(**args)
37
+ args.each do |k, v|
38
+ instance_variable_set("@#{k}", v) unless v.nil?
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BddOpenai
4
+ VERSION = '1.0.0'
5
+ end
data/lib/bdd_openai.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+ require 'securerandom'
6
+ require_relative 'bdd_openai/clients/http'
7
+ require_relative 'bdd_openai/error_response'
8
+ require_relative 'bdd_openai/files/client'
9
+ require_relative 'bdd_openai/files/file'
10
+ require_relative 'bdd_openai/version'
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bdd_openai
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brendon Dao
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-12-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Write a longer description or delete this line.
14
+ email:
15
+ - brendon.dao@employmenthero.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".env.sample"
21
+ - ".rspec"
22
+ - ".rubocop-https---raw-githubusercontent-com-Thinkei-Thinkei-master--rubocop-default-v1-yml"
23
+ - ".rubocop.yml"
24
+ - ".ruby-version"
25
+ - CHANGELOG.md
26
+ - CODE_OF_CONDUCT.md
27
+ - LICENSE.txt
28
+ - README.md
29
+ - Rakefile
30
+ - bdd_openai.gemspec
31
+ - docs/test_coverage_screenshot_231211_125800.png
32
+ - lib/bdd_openai.rb
33
+ - lib/bdd_openai/clients/http.rb
34
+ - lib/bdd_openai/error_response.rb
35
+ - lib/bdd_openai/files/client.rb
36
+ - lib/bdd_openai/files/file.rb
37
+ - lib/bdd_openai/version.rb
38
+ homepage: https://github.com/brendondaoateh/bdd_openai
39
+ licenses:
40
+ - MIT
41
+ metadata:
42
+ source_code_uri: https://github.com/brendondaoateh/bdd_openai
43
+ homepage_uri: https://github.com/brendondaoateh/bdd_openai
44
+ changelog_uri: https://github.com/brendondaoateh/bdd_openai/blob/main/CHANGELOG.md
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.7.0
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.1.6
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Write a short summary, because RubyGems requires one.
64
+ test_files: []