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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.standard.yml +3 -0
  4. data/.yardopts +14 -0
  5. data/CHANGELOG.md +24 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/CONTRIBUTING.md +109 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +423 -0
  10. data/Rakefile +10 -0
  11. data/examples/README.md +75 -0
  12. data/examples/configuration.rb +58 -0
  13. data/examples/embeddings.rb +195 -0
  14. data/examples/multimodal.rb +126 -0
  15. data/examples/rails_chat/README.md +69 -0
  16. data/examples/rails_chat/app/controllers/chat_controller.rb +26 -0
  17. data/examples/rails_chat/app/views/chat/index.html.erb +112 -0
  18. data/examples/rails_chat/config/routes.rb +8 -0
  19. data/examples/rails_initializer.rb +46 -0
  20. data/examples/system_instructions.rb +101 -0
  21. data/lib/geminize/chat.rb +98 -0
  22. data/lib/geminize/client.rb +318 -0
  23. data/lib/geminize/configuration.rb +98 -0
  24. data/lib/geminize/conversation_repository.rb +161 -0
  25. data/lib/geminize/conversation_service.rb +126 -0
  26. data/lib/geminize/embeddings.rb +145 -0
  27. data/lib/geminize/error_mapper.rb +96 -0
  28. data/lib/geminize/error_parser.rb +120 -0
  29. data/lib/geminize/errors.rb +185 -0
  30. data/lib/geminize/middleware/error_handler.rb +72 -0
  31. data/lib/geminize/model_info.rb +91 -0
  32. data/lib/geminize/models/chat_request.rb +186 -0
  33. data/lib/geminize/models/chat_response.rb +118 -0
  34. data/lib/geminize/models/content_request.rb +530 -0
  35. data/lib/geminize/models/content_response.rb +99 -0
  36. data/lib/geminize/models/conversation.rb +156 -0
  37. data/lib/geminize/models/embedding_request.rb +222 -0
  38. data/lib/geminize/models/embedding_response.rb +1064 -0
  39. data/lib/geminize/models/memory.rb +88 -0
  40. data/lib/geminize/models/message.rb +140 -0
  41. data/lib/geminize/models/model.rb +171 -0
  42. data/lib/geminize/models/model_list.rb +124 -0
  43. data/lib/geminize/models/stream_response.rb +99 -0
  44. data/lib/geminize/rails/app/controllers/concerns/geminize/controller.rb +105 -0
  45. data/lib/geminize/rails/app/helpers/geminize_helper.rb +125 -0
  46. data/lib/geminize/rails/controller_additions.rb +41 -0
  47. data/lib/geminize/rails/engine.rb +29 -0
  48. data/lib/geminize/rails/helper_additions.rb +37 -0
  49. data/lib/geminize/rails.rb +50 -0
  50. data/lib/geminize/railtie.rb +33 -0
  51. data/lib/geminize/request_builder.rb +57 -0
  52. data/lib/geminize/text_generation.rb +285 -0
  53. data/lib/geminize/validators.rb +150 -0
  54. data/lib/geminize/vector_utils.rb +164 -0
  55. data/lib/geminize/version.rb +5 -0
  56. data/lib/geminize.rb +527 -0
  57. data/lib/generators/geminize/install_generator.rb +22 -0
  58. data/lib/generators/geminize/templates/README +31 -0
  59. data/lib/generators/geminize/templates/initializer.rb +38 -0
  60. data/sig/geminize.rbs +4 -0
  61. 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