ai-chat 0.0.8 → 0.1.1
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/{LICENSE.txt → LICENSE} +6 -6
- data/README.md +410 -94
- data/ai-chat.gemspec +33 -0
- data/lib/ai/chat.rb +158 -148
- data/lib/ai/response.rb +12 -0
- data/lib/ai-chat.rb +1 -11
- data/lib/ai_chat.rb +6 -0
- metadata +44 -77
- data/.config/rubocop/config.yml +0 -2
- data/.reek.yml +0 -7
- data/.ruby-version +0 -1
- data/CHANGELOG.md +0 -28
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -6
- data/Rakefile +0 -10
- data/ai_chat.gemspec +0 -48
- data/lib/ai/chat/version.rb +0 -7
- data/test_program/Gemfile +0 -4
- data/test_program/test_ai_chat.rb +0 -157
data/ai-chat.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "ai-chat"
|
5
|
+
spec.version = "0.1.1"
|
6
|
+
spec.authors = ["Raghu Betina"]
|
7
|
+
spec.email = ["raghu@firstdraft.com"]
|
8
|
+
spec.homepage = "https://github.com/firstdraft/ai-chat"
|
9
|
+
spec.summary = "A beginner-friendly Ruby interface for OpenAI's API"
|
10
|
+
spec.license = "MIT"
|
11
|
+
|
12
|
+
spec.metadata = {
|
13
|
+
"bug_tracker_uri" => "https://github.com/firstdraft/ai-chat/issues",
|
14
|
+
"changelog_uri" => "https://github.com/firstdraft/ai-chat/blob/main/CHANGELOG.md",
|
15
|
+
"homepage_uri" => "https://github.com/firstdraft/ai-chat",
|
16
|
+
"label" => "AI Chat",
|
17
|
+
"rubygems_mfa_required" => "true",
|
18
|
+
"source_code_uri" => "https://github.com/firstdraft/ai-chat"
|
19
|
+
}
|
20
|
+
|
21
|
+
|
22
|
+
spec.required_ruby_version = "~> 3.2"
|
23
|
+
spec.add_dependency "zeitwerk", "~> 2.7"
|
24
|
+
spec.add_dependency "openai", "~> 0.14"
|
25
|
+
spec.add_runtime_dependency "mime-types", "~> 3.0"
|
26
|
+
spec.add_runtime_dependency "base64", "~> 0.1" # Works for all Ruby versions
|
27
|
+
|
28
|
+
spec.add_development_dependency "dotenv"
|
29
|
+
spec.add_development_dependency "refinements", "~> 11.1"
|
30
|
+
|
31
|
+
spec.extra_rdoc_files = Dir["README*", "LICENSE*"]
|
32
|
+
spec.files = Dir["*.gemspec", "lib/**/*"]
|
33
|
+
end
|
data/lib/ai/chat.rb
CHANGED
@@ -1,76 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "base64"
|
4
|
+
require "mime-types"
|
5
|
+
require "openai"
|
6
|
+
|
7
|
+
require_relative "response"
|
4
8
|
|
5
9
|
module AI
|
10
|
+
# Main namespace.
|
6
11
|
class Chat
|
7
|
-
|
8
|
-
|
12
|
+
def self.loader(registry = Zeitwerk::Registry)
|
13
|
+
@loader ||= registry.loaders.each.find { |loader| loader.tag == "ai-chat" }
|
14
|
+
end
|
9
15
|
|
10
|
-
|
16
|
+
attr_accessor :messages, :schema, :model, :web_search
|
17
|
+
attr_reader :reasoning_effort, :client
|
11
18
|
|
12
|
-
|
13
|
-
|
19
|
+
VALID_REASONING_EFFORTS = [:low, :medium, :high].freeze
|
20
|
+
|
21
|
+
def initialize(api_key: nil, api_key_env_var: "OPENAI_API_KEY")
|
22
|
+
@api_key = api_key || ENV.fetch(api_key_env_var)
|
14
23
|
@messages = []
|
15
|
-
@model = "gpt-4.1-mini"
|
16
24
|
@reasoning_effort = nil
|
25
|
+
@model = "gpt-4.1-nano"
|
26
|
+
@client = OpenAI::Client.new(api_key: @api_key)
|
17
27
|
end
|
18
|
-
|
19
|
-
def reasoning_effort=(value)
|
20
|
-
if value.nil?
|
21
|
-
@reasoning_effort = nil
|
22
|
-
else
|
23
|
-
# Convert string to symbol if needed
|
24
|
-
symbol_value = value.is_a?(String) ? value.to_sym : value
|
25
|
-
|
26
|
-
if VALID_REASONING_EFFORTS.include?(symbol_value)
|
27
|
-
@reasoning_effort = symbol_value
|
28
|
-
else
|
29
|
-
valid_values = VALID_REASONING_EFFORTS.map { |v| ":#{v} or \"#{v}\"" }.join(", ")
|
30
|
-
raise ArgumentError, "Invalid reasoning_effort value: '#{value}'. Must be one of: #{valid_values}"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def system(content)
|
36
|
-
messages.push({role: "system", content: content})
|
37
|
-
end
|
38
|
-
|
39
|
-
def user(content, image: nil, images: nil)
|
40
|
-
if content.is_a?(Array)
|
41
|
-
processed_content = content.map do |item|
|
42
|
-
if item.key?("image") || item.key?(:image)
|
43
|
-
image_value = item.fetch("image") { item.fetch(:image) }
|
44
|
-
{
|
45
|
-
type: "input_image",
|
46
|
-
image_url: process_image(image_value)
|
47
|
-
}
|
48
|
-
elsif item.key?("text") || item.key?(:text)
|
49
|
-
text_value = item.fetch("text") { item.fetch(:text) }
|
50
|
-
{
|
51
|
-
type: "input_text",
|
52
|
-
text: text_value
|
53
|
-
}
|
54
|
-
else
|
55
|
-
item
|
56
|
-
end
|
57
|
-
end
|
58
28
|
|
29
|
+
def add(content, role: "user", response: nil, image: nil, images: nil, file: nil, files: nil)
|
30
|
+
if image.nil? && images.nil? && file.nil? && files.nil?
|
59
31
|
messages.push(
|
60
32
|
{
|
61
|
-
role:
|
62
|
-
content:
|
63
|
-
|
64
|
-
|
65
|
-
elsif image.nil? && images.nil?
|
66
|
-
messages.push(
|
67
|
-
{
|
68
|
-
role: "user",
|
69
|
-
content: content
|
70
|
-
}
|
33
|
+
role: role,
|
34
|
+
content: content,
|
35
|
+
response: response
|
36
|
+
}.compact
|
71
37
|
)
|
38
|
+
|
72
39
|
else
|
73
|
-
|
40
|
+
text_and_files_array = [
|
74
41
|
{
|
75
42
|
type: "input_text",
|
76
43
|
text: content
|
@@ -81,122 +48,133 @@ module AI
|
|
81
48
|
images_array = images.map do |image|
|
82
49
|
{
|
83
50
|
type: "input_image",
|
84
|
-
image_url:
|
51
|
+
image_url: process_file(image)
|
85
52
|
}
|
86
53
|
end
|
87
54
|
|
88
|
-
|
89
|
-
|
90
|
-
|
55
|
+
text_and_files_array += images_array
|
56
|
+
elsif image
|
57
|
+
text_and_files_array.push(
|
91
58
|
{
|
92
59
|
type: "input_image",
|
93
|
-
image_url:
|
60
|
+
image_url: process_file(image)
|
94
61
|
}
|
95
62
|
)
|
63
|
+
elsif files && !files.empty?
|
64
|
+
files_array = files.map do |file|
|
65
|
+
{
|
66
|
+
type: "input_file",
|
67
|
+
filename: "test",
|
68
|
+
file_data: process_file(file)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
text_and_files_array += files_array
|
73
|
+
else
|
74
|
+
text_and_files_array.push(
|
75
|
+
{
|
76
|
+
type: "input_file",
|
77
|
+
filename: "test",
|
78
|
+
file_data: process_file(file)
|
79
|
+
}
|
80
|
+
)
|
81
|
+
|
96
82
|
end
|
97
83
|
|
98
84
|
messages.push(
|
99
85
|
{
|
100
|
-
role:
|
101
|
-
content:
|
86
|
+
role: role,
|
87
|
+
content: text_and_files_array
|
102
88
|
}
|
103
89
|
)
|
104
90
|
end
|
105
91
|
end
|
106
92
|
|
107
|
-
def
|
108
|
-
|
93
|
+
def system(message)
|
94
|
+
add(message, role: "system")
|
109
95
|
end
|
110
96
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
"model" => model,
|
119
|
-
"input" => messages
|
120
|
-
}
|
121
|
-
|
122
|
-
# Add reasoning parameter if specified
|
123
|
-
if !@reasoning_effort.nil?
|
124
|
-
# Convert symbol back to string for the API request
|
125
|
-
request_body_hash["reasoning"] = {
|
126
|
-
"effort" => @reasoning_effort.to_s
|
127
|
-
}
|
128
|
-
end
|
97
|
+
def user(message, image: nil, images: nil, file: nil, files: nil)
|
98
|
+
add(message, role: "user", image: image, images: images, file: file, files: files)
|
99
|
+
end
|
100
|
+
|
101
|
+
def assistant(message, response: nil)
|
102
|
+
add(message, role: "assistant", response: response)
|
103
|
+
end
|
129
104
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
105
|
+
def generate!
|
106
|
+
response = create_response
|
107
|
+
|
108
|
+
if web_search
|
109
|
+
message = response.output.last.content.first.text
|
110
|
+
chat_response = Response.new(response)
|
111
|
+
assistant(message, response: chat_response)
|
112
|
+
elsif schema
|
113
|
+
# filtering out refusals...
|
114
|
+
json_response = response.output.flat_map { _1.content }.select { _1.is_a?(OpenAI::Models::Responses::ResponseOutputText)}.first.text
|
115
|
+
chat_response = Response.new(response)
|
116
|
+
message = JSON.parse(json_response, symbolize_names: true)
|
117
|
+
assistant(message, response: chat_response)
|
118
|
+
else
|
119
|
+
message = response.output.last.content.first.text
|
120
|
+
chat_response = Response.new(response)
|
121
|
+
assistant(message, response: chat_response)
|
147
122
|
end
|
148
123
|
|
149
|
-
|
150
|
-
|
151
|
-
uri = URI("https://api.openai.com/v1/responses")
|
152
|
-
raw_response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
153
|
-
request = Net::HTTP::Post.new(uri, request_headers_hash)
|
154
|
-
request.body = request_body_json
|
155
|
-
http.request(request)
|
156
|
-
end
|
124
|
+
message
|
125
|
+
end
|
157
126
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
127
|
+
def pick_up_from(response_id)
|
128
|
+
response = client.responses.retrieve(response_id)
|
129
|
+
chat_response = Response.new(response)
|
130
|
+
message = response.output.flat_map { _1.content }.select { _1.is_a?(OpenAI::Models::Responses::ResponseOutputText)}.first.text
|
131
|
+
assistant(message, response: chat_response)
|
132
|
+
message
|
133
|
+
end
|
162
134
|
|
163
|
-
|
164
|
-
|
165
|
-
|
135
|
+
def reasoning_effort=(value)
|
136
|
+
if value.nil?
|
137
|
+
@reasoning_effort = nil
|
138
|
+
else
|
139
|
+
# Convert string to symbol if needed
|
140
|
+
symbol_value = value.is_a?(String) ? value.to_sym : value
|
166
141
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
raise "OpenAI API Error: #{error_message}"
|
173
|
-
end
|
174
|
-
|
175
|
-
# Extract the text content from the response
|
176
|
-
content = ""
|
177
|
-
|
178
|
-
# Parse response according to the documented structure
|
179
|
-
if parsed_response.key?("output") && parsed_response["output"].is_a?(Array) && !parsed_response["output"].empty?
|
180
|
-
output_item = parsed_response["output"][0]
|
181
|
-
|
182
|
-
if output_item["type"] == "message" && output_item.key?("content")
|
183
|
-
content_items = output_item["content"]
|
184
|
-
output_text_item = content_items.find { |item| item["type"] == "output_text" }
|
185
|
-
|
186
|
-
if output_text_item && output_text_item.key?("text")
|
187
|
-
content = output_text_item["text"]
|
188
|
-
end
|
142
|
+
if VALID_REASONING_EFFORTS.include?(symbol_value)
|
143
|
+
@reasoning_effort = symbol_value
|
144
|
+
else
|
145
|
+
valid_values = VALID_REASONING_EFFORTS.map { |v| ":#{v} or \"#{v}\"" }.join(", ")
|
146
|
+
raise ArgumentError, "Invalid reasoning_effort value: '#{value}'. Must be one of: #{valid_values}"
|
189
147
|
end
|
190
148
|
end
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
149
|
+
end
|
150
|
+
|
151
|
+
def schema=(value)
|
152
|
+
if value.is_a?(String)
|
153
|
+
@schema = JSON.parse(value, symbolize_names: true)
|
154
|
+
unless @schema.key?(:format) || @schema.key?("format")
|
155
|
+
@schema = { format: @schema }
|
156
|
+
end
|
157
|
+
elsif value.is_a?(Hash)
|
158
|
+
if value.key?(:format) || value.key?("format")
|
159
|
+
@schema = value
|
160
|
+
else
|
161
|
+
@schema = { format: value }
|
162
|
+
end
|
163
|
+
else
|
164
|
+
raise ArgumentError, "Invalid schema value: '#{value}'. Must be a String containing JSON or a Hash."
|
195
165
|
end
|
166
|
+
end
|
196
167
|
|
197
|
-
|
168
|
+
def last
|
169
|
+
messages.last
|
170
|
+
end
|
171
|
+
|
172
|
+
def last_response
|
173
|
+
last[:response]
|
174
|
+
end
|
198
175
|
|
199
|
-
|
176
|
+
def last_response_id
|
177
|
+
last_response&.id
|
200
178
|
end
|
201
179
|
|
202
180
|
def inspect
|
@@ -208,6 +186,20 @@ module AI
|
|
208
186
|
# Custom exception class for input classification errors.
|
209
187
|
class InputClassificationError < StandardError; end
|
210
188
|
|
189
|
+
def create_response
|
190
|
+
parameters = {
|
191
|
+
model: model,
|
192
|
+
input: strip_responses(messages),
|
193
|
+
tools: tools,
|
194
|
+
text: schema,
|
195
|
+
reasoning: {
|
196
|
+
effort: reasoning_effort
|
197
|
+
}.compact
|
198
|
+
}.compact
|
199
|
+
parameters = parameters.delete_if { |k, v| v.empty? }
|
200
|
+
client.responses.create(**parameters)
|
201
|
+
end
|
202
|
+
|
211
203
|
def classify_obj(obj)
|
212
204
|
if obj.is_a?(String)
|
213
205
|
# Attempt to parse as a URL.
|
@@ -236,7 +228,7 @@ module AI
|
|
236
228
|
end
|
237
229
|
end
|
238
230
|
|
239
|
-
def
|
231
|
+
def process_file(obj)
|
240
232
|
case classify_obj(obj)
|
241
233
|
when :url
|
242
234
|
obj
|
@@ -262,13 +254,31 @@ module AI
|
|
262
254
|
mime_type = MIME::Types.type_for(filename).first.to_s
|
263
255
|
mime_type = "image/jpeg" if mime_type.empty?
|
264
256
|
|
265
|
-
|
257
|
+
file_data = obj.read
|
266
258
|
obj.rewind if obj.respond_to?(:rewind)
|
267
259
|
|
268
|
-
base64_string = Base64.strict_encode64(
|
260
|
+
base64_string = Base64.strict_encode64(file_data)
|
269
261
|
|
270
262
|
"data:#{mime_type};base64,#{base64_string}"
|
271
263
|
end
|
272
264
|
end
|
265
|
+
|
266
|
+
def strip_responses(messages)
|
267
|
+
messages.each do |message|
|
268
|
+
message.delete(:response) if message.key?(:response)
|
269
|
+
message[:content] = JSON.generate(message[:content]) if message[:content].is_a?(Hash)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def tools
|
274
|
+
tools_list = []
|
275
|
+
if web_search
|
276
|
+
tools_list << { type: "web_search_preview" }
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def extract_message(response)
|
281
|
+
response.output.flat_map { _1.content }.select { _1.is_a?(OpenAI::Models::Responses::ResponseOutputText)}.first.text
|
282
|
+
end
|
273
283
|
end
|
274
284
|
end
|
data/lib/ai/response.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module AI
|
2
|
+
class Response
|
3
|
+
attr_reader :id, :model, :usage, :total_tokens
|
4
|
+
|
5
|
+
def initialize(response)
|
6
|
+
@id = response.id
|
7
|
+
@model = response.model
|
8
|
+
@usage = response.usage.to_h.slice(:input_tokens, :output_tokens, :total_tokens)
|
9
|
+
@total_tokens = @usage[:total_tokens]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/ai-chat.rb
CHANGED
data/lib/ai_chat.rb
ADDED
metadata
CHANGED
@@ -1,175 +1,142 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ai-chat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Raghu Betina
|
8
|
-
|
9
|
-
bindir:
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: zeitwerk
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.7'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: openai
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0.
|
33
|
+
version: '0.14'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0.
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rake
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '13.0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '13.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rspec
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '3.12'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '3.12'
|
40
|
+
version: '0.14'
|
69
41
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
42
|
+
name: mime-types
|
71
43
|
requirement: !ruby/object:Gem::Requirement
|
72
44
|
requirements:
|
73
45
|
- - "~>"
|
74
46
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
76
|
-
type: :
|
47
|
+
version: '3.0'
|
48
|
+
type: :runtime
|
77
49
|
prerelease: false
|
78
50
|
version_requirements: !ruby/object:Gem::Requirement
|
79
51
|
requirements:
|
80
52
|
- - "~>"
|
81
53
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
54
|
+
version: '3.0'
|
83
55
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
56
|
+
name: base64
|
85
57
|
requirement: !ruby/object:Gem::Requirement
|
86
58
|
requirements:
|
87
59
|
- - "~>"
|
88
60
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
90
|
-
type: :
|
61
|
+
version: '0.1'
|
62
|
+
type: :runtime
|
91
63
|
prerelease: false
|
92
64
|
version_requirements: !ruby/object:Gem::Requirement
|
93
65
|
requirements:
|
94
66
|
- - "~>"
|
95
67
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
68
|
+
version: '0.1'
|
97
69
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
70
|
+
name: dotenv
|
99
71
|
requirement: !ruby/object:Gem::Requirement
|
100
72
|
requirements:
|
101
|
-
- - "
|
73
|
+
- - ">="
|
102
74
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
75
|
+
version: '0'
|
104
76
|
type: :development
|
105
77
|
prerelease: false
|
106
78
|
version_requirements: !ruby/object:Gem::Requirement
|
107
79
|
requirements:
|
108
|
-
- - "
|
80
|
+
- - ">="
|
109
81
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
82
|
+
version: '0'
|
111
83
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
84
|
+
name: refinements
|
113
85
|
requirement: !ruby/object:Gem::Requirement
|
114
86
|
requirements:
|
115
87
|
- - "~>"
|
116
88
|
- !ruby/object:Gem::Version
|
117
|
-
version: '1
|
89
|
+
version: '11.1'
|
118
90
|
type: :development
|
119
91
|
prerelease: false
|
120
92
|
version_requirements: !ruby/object:Gem::Requirement
|
121
93
|
requirements:
|
122
94
|
- - "~>"
|
123
95
|
- !ruby/object:Gem::Version
|
124
|
-
version: '1
|
125
|
-
description:
|
126
|
-
it as easy as possible to use OpenAI's Responses API. Supports Structured Output
|
127
|
-
and Image Processing.
|
96
|
+
version: '11.1'
|
97
|
+
description:
|
128
98
|
email:
|
129
99
|
- raghu@firstdraft.com
|
130
|
-
- jelani@firstdraft.com
|
131
100
|
executables: []
|
132
101
|
extensions: []
|
133
|
-
extra_rdoc_files:
|
102
|
+
extra_rdoc_files:
|
103
|
+
- README.md
|
104
|
+
- LICENSE
|
134
105
|
files:
|
135
|
-
-
|
136
|
-
- ".reek.yml"
|
137
|
-
- ".ruby-version"
|
138
|
-
- CHANGELOG.md
|
139
|
-
- CODE_OF_CONDUCT.md
|
140
|
-
- Gemfile
|
141
|
-
- LICENSE.txt
|
106
|
+
- LICENSE
|
142
107
|
- README.md
|
143
|
-
-
|
144
|
-
- ai_chat.gemspec
|
108
|
+
- ai-chat.gemspec
|
145
109
|
- lib/ai-chat.rb
|
146
110
|
- lib/ai/chat.rb
|
147
|
-
- lib/ai/
|
148
|
-
-
|
149
|
-
- test_program/test_ai_chat.rb
|
111
|
+
- lib/ai/response.rb
|
112
|
+
- lib/ai_chat.rb
|
150
113
|
homepage: https://github.com/firstdraft/ai-chat
|
151
114
|
licenses:
|
152
115
|
- MIT
|
153
116
|
metadata:
|
117
|
+
bug_tracker_uri: https://github.com/firstdraft/ai-chat/issues
|
118
|
+
changelog_uri: https://github.com/firstdraft/ai-chat/blob/main/CHANGELOG.md
|
154
119
|
homepage_uri: https://github.com/firstdraft/ai-chat
|
120
|
+
label: AI Chat
|
121
|
+
rubygems_mfa_required: 'true'
|
155
122
|
source_code_uri: https://github.com/firstdraft/ai-chat
|
156
|
-
|
123
|
+
post_install_message:
|
157
124
|
rdoc_options: []
|
158
125
|
require_paths:
|
159
126
|
- lib
|
160
127
|
required_ruby_version: !ruby/object:Gem::Requirement
|
161
128
|
requirements:
|
162
|
-
- - "
|
129
|
+
- - "~>"
|
163
130
|
- !ruby/object:Gem::Version
|
164
|
-
version: 2
|
131
|
+
version: '3.2'
|
165
132
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
166
133
|
requirements:
|
167
134
|
- - ">="
|
168
135
|
- !ruby/object:Gem::Version
|
169
136
|
version: '0'
|
170
137
|
requirements: []
|
171
|
-
rubygems_version: 3.
|
138
|
+
rubygems_version: 3.5.23
|
139
|
+
signing_key:
|
172
140
|
specification_version: 4
|
173
|
-
summary:
|
174
|
-
easy as possible to use OpenAI's Responses API.
|
141
|
+
summary: A beginner-friendly Ruby interface for OpenAI's API
|
175
142
|
test_files: []
|
data/.config/rubocop/config.yml
DELETED