genai-rb 0.1.0 → 0.2.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.
data/lib/genai/chat.rb CHANGED
@@ -1,203 +1,206 @@
1
- module Genai
2
- module ChatValidator
3
- def self.validate_content(content)
4
- return false unless content[:parts] && !content[:parts].empty?
5
- content[:parts].each do |part|
6
- return false if part.empty?
7
- return false if part[:text] && part[:text].empty?
8
- end
9
- true
10
- end
11
-
12
- def self.validate_contents(contents)
13
- return false if contents.empty?
14
- contents.each do |content|
15
- return false unless validate_content(content)
16
- end
17
- true
18
- end
19
-
20
- def self.validate_response(response)
21
- return false unless response[:candidates] && !response[:candidates].empty?
22
- return false unless response[:candidates][0][:content]
23
- validate_content(response[:candidates][0][:content])
24
- end
25
-
26
- def self.extract_curated_history(comprehensive_history)
27
- return [] if comprehensive_history.empty?
28
-
29
- curated_history = []
30
- length = comprehensive_history.length
31
- i = 0
32
-
33
- while i < length
34
- unless ["user", "model"].include?(comprehensive_history[i][:role])
35
- raise ArgumentError, "Role must be user or model, but got #{comprehensive_history[i][:role]}"
36
- end
37
-
38
- if comprehensive_history[i][:role] == "user"
39
- current_input = comprehensive_history[i]
40
- curated_history << current_input
41
- i += 1
42
- else
43
- current_output = []
44
- is_valid = true
45
-
46
- while i < length && comprehensive_history[i][:role] == "model"
47
- current_output << comprehensive_history[i]
48
- if is_valid && !validate_content(comprehensive_history[i])
49
- is_valid = false
50
- end
51
- i += 1
52
- end
53
-
54
- if is_valid
55
- curated_history.concat(current_output)
56
- elsif !curated_history.empty?
57
- curated_history.pop
58
- end
59
- end
60
- end
61
-
62
- curated_history
63
- end
64
- end
65
-
66
- class BaseChat
67
- attr_reader :model, :config, :comprehensive_history, :curated_history
68
-
69
- def initialize(model:, config: nil, history: [])
70
- @model = model
71
- @config = config
72
-
73
- content_models = history.map do |content|
74
- content.is_a?(Hash) ? content : content
75
- end
76
-
77
- @comprehensive_history = content_models
78
- @curated_history = ChatValidator.extract_curated_history(content_models)
79
- end
80
-
81
- def record_history(user_input:, model_output:, automatic_function_calling_history: [], is_valid: true)
82
- input_contents = if !automatic_function_calling_history.empty?
83
- automatic_function_calling_history[@curated_history.length..-1] || [user_input]
84
- else
85
- [user_input]
86
- end
87
-
88
- output_contents = model_output.empty? ? [{ role: "model", parts: [] }] : model_output
89
-
90
- @comprehensive_history.concat(input_contents)
91
- @comprehensive_history.concat(output_contents)
92
-
93
- if is_valid
94
- @curated_history.concat(input_contents)
95
- @curated_history.concat(output_contents)
96
- end
97
- end
98
-
99
- def get_history(curated: false)
100
- curated ? @curated_history : @comprehensive_history
101
- end
102
- end
103
-
104
- class Chat < BaseChat
105
- def initialize(client:, model:, config: nil, history: [])
106
- @client = client
107
- super(model: model, config: config, history: history)
108
- end
109
-
110
- def send_message(message, config: nil)
111
- input_content = case message
112
- when String
113
- { role: "user", parts: [{ text: message }] }
114
- when Array
115
- { role: "user", parts: message }
116
- when Hash
117
- message
118
- else
119
- raise ArgumentError, "Message must be a String, Array, or Hash"
120
- end
121
-
122
- model_instance = @client.model(@model)
123
- response = model_instance.generate_content(
124
- contents: @curated_history + [input_content],
125
- config: config || @config
126
- )
127
-
128
- model_output = if response[:candidates] && !response[:candidates].empty? && response[:candidates][0][:content]
129
- [response[:candidates][0][:content]]
130
- else
131
- []
132
- end
133
-
134
- automatic_function_calling_history = response[:automatic_function_calling_history] || []
135
-
136
- record_history(
137
- user_input: input_content,
138
- model_output: model_output,
139
- automatic_function_calling_history: automatic_function_calling_history,
140
- is_valid: ChatValidator.validate_response(response)
141
- )
142
-
143
- response
144
- end
145
-
146
- def send_message_stream(message, config: nil)
147
- input_content = case message
148
- when String
149
- { role: "user", parts: [{ text: message }] }
150
- when Array
151
- { role: "user", parts: message }
152
- when Hash
153
- message
154
- else
155
- raise ArgumentError, "Message must be a String, Array, or Hash"
156
- end
157
-
158
-
159
- send_message(message, config: config)
160
- end
161
- end
162
-
163
- class Chats
164
- def initialize(client)
165
- @client = client
166
- @user_sessions = {}
167
- end
168
-
169
- def create(model:, config: nil, history: [])
170
- Chat.new(client: @client, model: model, config: config, history: history)
171
- end
172
-
173
- def get_user_session(user_id, model: "gemini-2.0-flash", config: nil)
174
- unless @user_sessions[user_id]
175
- @user_sessions[user_id] = create(
176
- model: model,
177
- config: config || {
178
- temperature: 0.7,
179
- max_output_tokens: 2048
180
- }
181
- )
182
- end
183
-
184
- @user_sessions[user_id]
185
- end
186
-
187
- def clear_user_session(user_id)
188
- @user_sessions.delete(user_id)
189
- end
190
-
191
- def clear_all_sessions
192
- @user_sessions.clear
193
- end
194
-
195
- def get_session_count
196
- @user_sessions.length
197
- end
198
-
199
- def get_active_users
200
- @user_sessions.keys
201
- end
202
- end
203
- end
1
+ module Genai
2
+ module ChatValidator
3
+ def self.validate_content(content)
4
+ message_content = content[:content] || content["content"]
5
+ return true if message_content.is_a?(String) && !message_content.empty?
6
+
7
+ parts = content[:parts] || content["parts"]
8
+ return false unless parts && !parts.empty?
9
+ parts.each do |part|
10
+ return false if part.empty?
11
+ text = part[:text] || part["text"]
12
+ return false if text && text.empty?
13
+ end
14
+ true
15
+ end
16
+
17
+ def self.validate_contents(contents)
18
+ return false if contents.empty?
19
+ contents.each do |content|
20
+ return false unless validate_content(content)
21
+ end
22
+ true
23
+ end
24
+
25
+ def self.validate_response(response)
26
+ return false unless response[:candidates] && !response[:candidates].empty?
27
+ return false unless response[:candidates][0][:content]
28
+ validate_content(response[:candidates][0][:content])
29
+ end
30
+
31
+ def self.extract_curated_history(comprehensive_history)
32
+ return [] if comprehensive_history.empty?
33
+
34
+ curated_history = []
35
+ length = comprehensive_history.length
36
+ i = 0
37
+
38
+ while i < length
39
+ role = comprehensive_history[i][:role] || comprehensive_history[i]["role"]
40
+
41
+ unless ["user", "model", "assistant", "system", "developer"].include?(role)
42
+ raise ArgumentError, "Unsupported role: #{role}"
43
+ end
44
+
45
+ if ["user", "system", "developer"].include?(role)
46
+ current_input = comprehensive_history[i]
47
+ curated_history << current_input
48
+ i += 1
49
+ else
50
+ current_output = []
51
+ is_valid = true
52
+
53
+ while i < length
54
+ current_role = comprehensive_history[i][:role] || comprehensive_history[i]["role"]
55
+ break unless ["model", "assistant"].include?(current_role)
56
+
57
+ current_output << comprehensive_history[i]
58
+ if is_valid && !validate_content(comprehensive_history[i])
59
+ is_valid = false
60
+ end
61
+ i += 1
62
+ end
63
+
64
+ if is_valid
65
+ curated_history.concat(current_output)
66
+ elsif !curated_history.empty?
67
+ curated_history.pop
68
+ end
69
+ end
70
+ end
71
+
72
+ curated_history
73
+ end
74
+ end
75
+
76
+ class BaseChat
77
+ attr_reader :model, :config, :comprehensive_history, :curated_history
78
+
79
+ def initialize(model:, config: nil, history: [])
80
+ @model = model
81
+ @config = config
82
+
83
+ content_models = history.map do |content|
84
+ content.is_a?(Hash) ? content : content
85
+ end
86
+
87
+ @comprehensive_history = content_models
88
+ @curated_history = ChatValidator.extract_curated_history(content_models)
89
+ end
90
+
91
+ def record_history(user_input:, model_output:, automatic_function_calling_history: [], is_valid: true)
92
+ input_contents = if !automatic_function_calling_history.empty?
93
+ automatic_function_calling_history[@curated_history.length..-1] || [user_input]
94
+ else
95
+ [user_input]
96
+ end
97
+
98
+ output_contents = model_output.empty? ? [{ role: "model", parts: [] }] : model_output
99
+
100
+ @comprehensive_history.concat(input_contents)
101
+ @comprehensive_history.concat(output_contents)
102
+
103
+ if is_valid
104
+ @curated_history.concat(input_contents)
105
+ @curated_history.concat(output_contents)
106
+ end
107
+ end
108
+
109
+ def get_history(curated: false)
110
+ curated ? @curated_history : @comprehensive_history
111
+ end
112
+ end
113
+
114
+ class Chat < BaseChat
115
+ def initialize(client:, model:, config: nil, history: [])
116
+ @client = client
117
+ super(model: model, config: config, history: history)
118
+ end
119
+
120
+ def send_message(message, config: nil)
121
+ input_content = case message
122
+ when String
123
+ { role: "user", parts: [{ text: message }] }
124
+ when Array
125
+ { role: "user", parts: message }
126
+ when Hash
127
+ message
128
+ else
129
+ raise ArgumentError, "Message must be a String, Array, or Hash"
130
+ end
131
+
132
+ model_instance = @client.model(@model)
133
+ response = model_instance.generate_content(
134
+ contents: @curated_history + [input_content],
135
+ config: config || @config
136
+ )
137
+
138
+ model_output = response.to_s.empty? ? [] : [{ role: "model", parts: [{ text: response.to_s }] }]
139
+
140
+ record_history(
141
+ user_input: input_content,
142
+ model_output: model_output,
143
+ is_valid: !response.to_s.empty?
144
+ )
145
+
146
+ response
147
+ end
148
+
149
+ def send_message_stream(message, config: nil)
150
+ input_content = case message
151
+ when String
152
+ { role: "user", parts: [{ text: message }] }
153
+ when Array
154
+ { role: "user", parts: message }
155
+ when Hash
156
+ message
157
+ else
158
+ raise ArgumentError, "Message must be a String, Array, or Hash"
159
+ end
160
+
161
+
162
+ send_message(message, config: config)
163
+ end
164
+ end
165
+
166
+ class Chats
167
+ def initialize(client)
168
+ @client = client
169
+ @user_sessions = {}
170
+ end
171
+
172
+ def create(model:, config: nil, history: [])
173
+ Chat.new(client: @client, model: model, config: config, history: history)
174
+ end
175
+
176
+ def get_user_session(user_id, model: "gpt-5.5", config: nil)
177
+ unless @user_sessions[user_id]
178
+ @user_sessions[user_id] = create(
179
+ model: model,
180
+ config: config || {
181
+ temperature: 0.7,
182
+ max_tokens: 2048
183
+ }
184
+ )
185
+ end
186
+
187
+ @user_sessions[user_id]
188
+ end
189
+
190
+ def clear_user_session(user_id)
191
+ @user_sessions.delete(user_id)
192
+ end
193
+
194
+ def clear_all_sessions
195
+ @user_sessions.clear
196
+ end
197
+
198
+ def get_session_count
199
+ @user_sessions.length
200
+ end
201
+
202
+ def get_active_users
203
+ @user_sessions.keys
204
+ end
205
+ end
206
+ end
data/lib/genai/config.rb CHANGED
@@ -1,13 +1,13 @@
1
- require "net/http"
2
- require "json"
3
-
1
+ require "net/http"
2
+ require "json"
3
+
4
4
  module Genai
5
5
  class Config
6
6
  attr_accessor :api_key, :base_url, :timeout, :max_retries
7
7
 
8
8
  def initialize(api_key: nil, base_url: nil, timeout: 60, max_retries: 3)
9
- @api_key = api_key || ENV["GEMINI_API_KEY"]
10
- @base_url = base_url || "https://generativelanguage.googleapis.com"
9
+ @api_key = api_key || ENV["OPENAI_API_KEY"]
10
+ @base_url = (base_url || ENV["OPENAI_BASE_URL"] || "https://api.openai.com/v1").sub(%r{/\z}, "")
11
11
  @timeout = timeout
12
12
  @max_retries = max_retries
13
13
 
@@ -15,18 +15,18 @@ module Genai
15
15
  end
16
16
 
17
17
  def validate_config!
18
- raise Error, "API key is required. Set GEMINI_API_KEY environment variable or pass api_key parameter." if @api_key.nil? || @api_key.empty?
18
+ raise Error, "API key is required. Set OPENAI_API_KEY environment variable or pass api_key parameter." if @api_key.nil? || @api_key.empty?
19
19
  end
20
20
 
21
21
  def api_url(endpoint)
22
- "#{@base_url}/v1beta/#{endpoint}"
22
+ "#{@base_url}/#{endpoint}"
23
23
  end
24
24
 
25
25
  def headers
26
26
  {
27
27
  "Content-Type" => "application/json",
28
- "x-goog-api-key" => @api_key
28
+ "Authorization" => "Bearer #{@api_key}"
29
29
  }
30
30
  end
31
31
  end
32
- end
32
+ end