geminize 0.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 +7 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/.yardopts +14 -0
- data/CHANGELOG.md +24 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/CONTRIBUTING.md +109 -0
- data/LICENSE.txt +21 -0
- data/README.md +423 -0
- data/Rakefile +10 -0
- data/examples/README.md +75 -0
- data/examples/configuration.rb +58 -0
- data/examples/embeddings.rb +195 -0
- data/examples/multimodal.rb +126 -0
- data/examples/rails_chat/README.md +69 -0
- data/examples/rails_chat/app/controllers/chat_controller.rb +26 -0
- data/examples/rails_chat/app/views/chat/index.html.erb +112 -0
- data/examples/rails_chat/config/routes.rb +8 -0
- data/examples/rails_initializer.rb +46 -0
- data/examples/system_instructions.rb +101 -0
- data/lib/geminize/chat.rb +98 -0
- data/lib/geminize/client.rb +318 -0
- data/lib/geminize/configuration.rb +98 -0
- data/lib/geminize/conversation_repository.rb +161 -0
- data/lib/geminize/conversation_service.rb +126 -0
- data/lib/geminize/embeddings.rb +145 -0
- data/lib/geminize/error_mapper.rb +96 -0
- data/lib/geminize/error_parser.rb +120 -0
- data/lib/geminize/errors.rb +185 -0
- data/lib/geminize/middleware/error_handler.rb +72 -0
- data/lib/geminize/model_info.rb +91 -0
- data/lib/geminize/models/chat_request.rb +186 -0
- data/lib/geminize/models/chat_response.rb +118 -0
- data/lib/geminize/models/content_request.rb +530 -0
- data/lib/geminize/models/content_response.rb +99 -0
- data/lib/geminize/models/conversation.rb +156 -0
- data/lib/geminize/models/embedding_request.rb +222 -0
- data/lib/geminize/models/embedding_response.rb +1064 -0
- data/lib/geminize/models/memory.rb +88 -0
- data/lib/geminize/models/message.rb +140 -0
- data/lib/geminize/models/model.rb +171 -0
- data/lib/geminize/models/model_list.rb +124 -0
- data/lib/geminize/models/stream_response.rb +99 -0
- data/lib/geminize/rails/app/controllers/concerns/geminize/controller.rb +105 -0
- data/lib/geminize/rails/app/helpers/geminize_helper.rb +125 -0
- data/lib/geminize/rails/controller_additions.rb +41 -0
- data/lib/geminize/rails/engine.rb +29 -0
- data/lib/geminize/rails/helper_additions.rb +37 -0
- data/lib/geminize/rails.rb +50 -0
- data/lib/geminize/railtie.rb +33 -0
- data/lib/geminize/request_builder.rb +57 -0
- data/lib/geminize/text_generation.rb +285 -0
- data/lib/geminize/validators.rb +150 -0
- data/lib/geminize/vector_utils.rb +164 -0
- data/lib/geminize/version.rb +5 -0
- data/lib/geminize.rb +527 -0
- data/lib/generators/geminize/install_generator.rb +22 -0
- data/lib/generators/geminize/templates/README +31 -0
- data/lib/generators/geminize/templates/initializer.rb +38 -0
- data/sig/geminize.rbs +4 -0
- metadata +218 -0
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
require "time"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
module Geminize
|
8
|
+
module Models
|
9
|
+
# Represents a conversation with message history
|
10
|
+
class Conversation
|
11
|
+
# @return [String] Unique identifier for the conversation
|
12
|
+
attr_reader :id
|
13
|
+
|
14
|
+
# @return [String] Optional title for the conversation
|
15
|
+
attr_accessor :title
|
16
|
+
|
17
|
+
# @return [Time] When the conversation was created
|
18
|
+
attr_reader :created_at
|
19
|
+
|
20
|
+
# @return [Time] When the conversation was last updated
|
21
|
+
attr_reader :updated_at
|
22
|
+
|
23
|
+
# @return [Array<Message>] The messages in the conversation
|
24
|
+
attr_reader :messages
|
25
|
+
|
26
|
+
# @return [String, nil] System instruction to guide model behavior
|
27
|
+
attr_accessor :system_instruction
|
28
|
+
|
29
|
+
# Initialize a new conversation
|
30
|
+
# @param id [String, nil] Unique identifier for the conversation
|
31
|
+
# @param title [String, nil] Optional title for the conversation
|
32
|
+
# @param messages [Array<Message>, nil] Initial messages
|
33
|
+
# @param created_at [Time, nil] When the conversation was created
|
34
|
+
# @param system_instruction [String, nil] System instruction to guide model behavior
|
35
|
+
def initialize(id = nil, title = nil, messages = nil, created_at = nil, system_instruction = nil)
|
36
|
+
@id = id || SecureRandom.uuid
|
37
|
+
@title = title
|
38
|
+
@messages = messages || []
|
39
|
+
@created_at = created_at || Time.now
|
40
|
+
@updated_at = @created_at
|
41
|
+
@system_instruction = system_instruction
|
42
|
+
end
|
43
|
+
|
44
|
+
# Add a user message to the conversation
|
45
|
+
# @param content [String] The content of the message
|
46
|
+
# @return [UserMessage] The added message
|
47
|
+
def add_user_message(content)
|
48
|
+
message = UserMessage.new(content)
|
49
|
+
add_message(message)
|
50
|
+
@updated_at = Time.now
|
51
|
+
message
|
52
|
+
end
|
53
|
+
|
54
|
+
# Add a model message to the conversation
|
55
|
+
# @param content [String] The content of the message
|
56
|
+
# @return [ModelMessage] The added message
|
57
|
+
def add_model_message(content)
|
58
|
+
message = ModelMessage.new(content)
|
59
|
+
add_message(message)
|
60
|
+
@updated_at = Time.now
|
61
|
+
message
|
62
|
+
end
|
63
|
+
|
64
|
+
# Add a message to the conversation
|
65
|
+
# @param message [Message] The message to add
|
66
|
+
# @return [Message] The added message
|
67
|
+
def add_message(message)
|
68
|
+
@messages << message
|
69
|
+
@updated_at = Time.now
|
70
|
+
message
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get the messages as an array of hashes for the API
|
74
|
+
# @return [Array<Hash>] The messages as hashes
|
75
|
+
def messages_as_hashes
|
76
|
+
@messages.map(&:to_hash)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get the last message in the conversation
|
80
|
+
# @return [Message, nil] The last message or nil if there are no messages
|
81
|
+
def last_message
|
82
|
+
@messages.last
|
83
|
+
end
|
84
|
+
|
85
|
+
# Check if the conversation has any messages
|
86
|
+
# @return [Boolean] True if the conversation has messages
|
87
|
+
def has_messages?
|
88
|
+
!@messages.empty?
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get the number of messages in the conversation
|
92
|
+
# @return [Integer] The number of messages
|
93
|
+
def message_count
|
94
|
+
@messages.size
|
95
|
+
end
|
96
|
+
|
97
|
+
# Clear all messages from the conversation
|
98
|
+
# @return [self] The conversation instance
|
99
|
+
def clear
|
100
|
+
@messages = []
|
101
|
+
@updated_at = Time.now
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
# Convert the conversation to a hash
|
106
|
+
# @return [Hash] The conversation as a hash
|
107
|
+
def to_hash
|
108
|
+
{
|
109
|
+
id: @id,
|
110
|
+
title: @title,
|
111
|
+
created_at: @created_at.iso8601,
|
112
|
+
updated_at: @updated_at.iso8601,
|
113
|
+
messages: @messages.map(&:to_hash),
|
114
|
+
system_instruction: @system_instruction
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
# Alias for to_hash for consistency with Ruby conventions
|
119
|
+
# @return [Hash] The conversation as a hash
|
120
|
+
def to_h
|
121
|
+
to_hash
|
122
|
+
end
|
123
|
+
|
124
|
+
# Serialize the conversation to a JSON string
|
125
|
+
# @return [String] The conversation as a JSON string
|
126
|
+
def to_json(*args)
|
127
|
+
to_h.to_json(*args)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Create a conversation from a hash
|
131
|
+
# @param hash [Hash] The hash to create the conversation from
|
132
|
+
# @return [Conversation] A new conversation object
|
133
|
+
def self.from_hash(hash)
|
134
|
+
id = hash["id"]
|
135
|
+
title = hash["title"]
|
136
|
+
created_at = hash["created_at"] ? Time.parse(hash["created_at"]) : Time.now
|
137
|
+
system_instruction = hash["system_instruction"]
|
138
|
+
|
139
|
+
messages = []
|
140
|
+
if hash["messages"]&.is_a?(Array)
|
141
|
+
messages = hash["messages"].map { |msg_hash| Message.from_hash(msg_hash) }
|
142
|
+
end
|
143
|
+
|
144
|
+
new(id, title, messages, created_at, system_instruction)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Create a conversation from a JSON string
|
148
|
+
# @param json [String] The JSON string
|
149
|
+
# @return [Conversation] A new conversation object
|
150
|
+
def self.from_json(json)
|
151
|
+
hash = JSON.parse(json)
|
152
|
+
from_hash(hash)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Represents a request for embedding generation from the Gemini API
|
6
|
+
class EmbeddingRequest
|
7
|
+
attr_reader :text, :model_name, :task_type, :dimensions, :title
|
8
|
+
|
9
|
+
# Task type constants
|
10
|
+
TASK_TYPE_UNSPECIFIED = "TASK_TYPE_UNSPECIFIED"
|
11
|
+
RETRIEVAL_QUERY = "RETRIEVAL_QUERY"
|
12
|
+
RETRIEVAL_DOCUMENT = "RETRIEVAL_DOCUMENT"
|
13
|
+
SEMANTIC_SIMILARITY = "SEMANTIC_SIMILARITY"
|
14
|
+
CLASSIFICATION = "CLASSIFICATION"
|
15
|
+
CLUSTERING = "CLUSTERING"
|
16
|
+
QUESTION_ANSWERING = "QUESTION_ANSWERING"
|
17
|
+
FACT_VERIFICATION = "FACT_VERIFICATION"
|
18
|
+
CODE_RETRIEVAL_QUERY = "CODE_RETRIEVAL_QUERY"
|
19
|
+
|
20
|
+
# Supported task types for embeddings
|
21
|
+
TASK_TYPES = [
|
22
|
+
TASK_TYPE_UNSPECIFIED, # Default unspecified type
|
23
|
+
RETRIEVAL_QUERY, # For embedding queries for retrieval
|
24
|
+
RETRIEVAL_DOCUMENT, # For embedding documents for retrieval
|
25
|
+
SEMANTIC_SIMILARITY, # For embeddings that will be compared for similarity
|
26
|
+
CLASSIFICATION, # For embeddings that will be used for classification
|
27
|
+
CLUSTERING, # For embeddings that will be clustered
|
28
|
+
QUESTION_ANSWERING, # For embeddings used in question answering
|
29
|
+
FACT_VERIFICATION, # For embeddings used in fact verification
|
30
|
+
CODE_RETRIEVAL_QUERY # For embeddings used in code retrieval
|
31
|
+
].freeze
|
32
|
+
|
33
|
+
# Initialize a new embedding request
|
34
|
+
# @param model_name [String] The model name to use
|
35
|
+
# @param text [String, Array<String>] The input text(s) to embed
|
36
|
+
# @param task_type [String] The embedding task type
|
37
|
+
# @param params [Hash] Additional parameters
|
38
|
+
# @option params [Integer] :dimensions Desired dimensionality of the embeddings
|
39
|
+
# @option params [String] :title Optional title for the request
|
40
|
+
def initialize(text = nil, model_name = nil, **options)
|
41
|
+
# Support both old positional params and new named params
|
42
|
+
if text.nil? && model_name.nil?
|
43
|
+
# New named parameters style
|
44
|
+
@model_name = options.delete(:model_name)
|
45
|
+
@text = options.delete(:text)
|
46
|
+
else
|
47
|
+
# Old positional parameters style
|
48
|
+
@text = text
|
49
|
+
@model_name = model_name
|
50
|
+
end
|
51
|
+
|
52
|
+
@task_type = options[:task_type] || RETRIEVAL_DOCUMENT
|
53
|
+
@dimensions = options[:dimensions]
|
54
|
+
@title = options[:title]
|
55
|
+
|
56
|
+
validate!
|
57
|
+
end
|
58
|
+
|
59
|
+
# Get the request as a hash
|
60
|
+
# @return [Hash] The request hash
|
61
|
+
def to_hash
|
62
|
+
if batch?
|
63
|
+
build_batch_request
|
64
|
+
else
|
65
|
+
build_single_request
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Alias for to_hash
|
70
|
+
def to_h
|
71
|
+
to_hash
|
72
|
+
end
|
73
|
+
|
74
|
+
# Check if this request contains multiple texts
|
75
|
+
# @return [Boolean] True if the request contains multiple texts
|
76
|
+
def batch?
|
77
|
+
@text.is_a?(Array)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Alias for batch?
|
81
|
+
alias_method :multiple?, :batch?
|
82
|
+
|
83
|
+
# Create a single request hash for the given text
|
84
|
+
# @param text_input [String] The text to create a request for
|
85
|
+
# @return [Hash] The request hash for a single text
|
86
|
+
def single_request_hash(text_input)
|
87
|
+
request = {
|
88
|
+
model: @model_name,
|
89
|
+
content: {
|
90
|
+
parts: [
|
91
|
+
{
|
92
|
+
text: text_input.to_s
|
93
|
+
}
|
94
|
+
]
|
95
|
+
},
|
96
|
+
taskType: @task_type
|
97
|
+
}
|
98
|
+
|
99
|
+
# Add title if provided
|
100
|
+
request[:content][:title] = @title if @title
|
101
|
+
|
102
|
+
# Add optional parameters if they exist
|
103
|
+
request[:dimensions] = @dimensions if @dimensions
|
104
|
+
|
105
|
+
request
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Validate the request parameters
|
111
|
+
# @raise [Geminize::ValidationError] If any parameter is invalid
|
112
|
+
# @return [Boolean] true if all parameters are valid
|
113
|
+
def validate!
|
114
|
+
validate_text!
|
115
|
+
validate_model_name!
|
116
|
+
validate_dimensions!
|
117
|
+
validate_task_type!
|
118
|
+
true
|
119
|
+
end
|
120
|
+
|
121
|
+
# Validate input text
|
122
|
+
# @raise [Geminize::ValidationError] If text is invalid
|
123
|
+
def validate_text!
|
124
|
+
if @text.nil?
|
125
|
+
raise Geminize::ValidationError.new("text cannot be nil", "INVALID_ARGUMENT")
|
126
|
+
end
|
127
|
+
|
128
|
+
if @text.is_a?(Array)
|
129
|
+
if @text.empty?
|
130
|
+
raise Geminize::ValidationError.new("Text array cannot be empty", "INVALID_ARGUMENT")
|
131
|
+
end
|
132
|
+
|
133
|
+
@text.each_with_index do |t, index|
|
134
|
+
if t.nil? || (t.is_a?(String) && t.empty?)
|
135
|
+
raise Geminize::ValidationError.new("Text at index #{index} cannot be nil or empty", "INVALID_ARGUMENT")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
elsif @text.is_a?(String) && @text.empty?
|
139
|
+
raise Geminize::ValidationError.new("Text cannot be empty", "INVALID_ARGUMENT")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Validate the model name
|
144
|
+
# @raise [Geminize::ValidationError] If model name is invalid
|
145
|
+
def validate_model_name!
|
146
|
+
if @model_name.nil?
|
147
|
+
raise Geminize::ValidationError.new("model_name cannot be nil", "INVALID_ARGUMENT")
|
148
|
+
end
|
149
|
+
|
150
|
+
if @model_name.is_a?(String) && @model_name.empty?
|
151
|
+
raise Geminize::ValidationError.new("Model name cannot be empty", "INVALID_ARGUMENT")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Validate dimensions if provided
|
156
|
+
# @raise [Geminize::ValidationError] If dimensions are invalid
|
157
|
+
def validate_dimensions!
|
158
|
+
return if @dimensions.nil?
|
159
|
+
|
160
|
+
Validators.validate_positive_integer!(@dimensions, "Dimensions")
|
161
|
+
end
|
162
|
+
|
163
|
+
# Validate task type if provided
|
164
|
+
# @raise [Geminize::ValidationError] If task type is invalid
|
165
|
+
def validate_task_type!
|
166
|
+
return if @task_type.nil?
|
167
|
+
|
168
|
+
unless TASK_TYPES.include?(@task_type)
|
169
|
+
task_types_str = TASK_TYPES.map { |t| "\"#{t}\"" }.join(", ")
|
170
|
+
raise Geminize::ValidationError.new(
|
171
|
+
"task_type must be one of: #{task_types_str}",
|
172
|
+
"INVALID_ARGUMENT"
|
173
|
+
)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Create a single request hash
|
178
|
+
# @return [Hash] The request hash for a single text
|
179
|
+
def build_single_request
|
180
|
+
request = {
|
181
|
+
model: @model_name,
|
182
|
+
content: {
|
183
|
+
parts: [
|
184
|
+
{
|
185
|
+
text: @text.to_s
|
186
|
+
}
|
187
|
+
]
|
188
|
+
},
|
189
|
+
taskType: @task_type
|
190
|
+
}
|
191
|
+
|
192
|
+
# Add title if provided
|
193
|
+
request[:content][:title] = @title if @title
|
194
|
+
|
195
|
+
# Add optional parameters if they exist
|
196
|
+
request[:dimensions] = @dimensions if @dimensions
|
197
|
+
|
198
|
+
request
|
199
|
+
end
|
200
|
+
|
201
|
+
# Create a batch request hash
|
202
|
+
# @return [Hash] The request hash for a batch of texts
|
203
|
+
def build_batch_request
|
204
|
+
{
|
205
|
+
requests: @text.map do |text_item|
|
206
|
+
{
|
207
|
+
model: "models/#{@model_name}",
|
208
|
+
content: {
|
209
|
+
parts: [
|
210
|
+
{
|
211
|
+
text: text_item
|
212
|
+
}
|
213
|
+
]
|
214
|
+
},
|
215
|
+
taskType: @task_type
|
216
|
+
}
|
217
|
+
end
|
218
|
+
}
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|