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,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Geminize
|
6
|
+
module Models
|
7
|
+
# Represents a message in the Gemini format
|
8
|
+
class Memory
|
9
|
+
# @return [String] The role of the sender (user, model, system)
|
10
|
+
attr_reader :role
|
11
|
+
|
12
|
+
# @return [Array<Hash>] The parts of the message (e.g., text content)
|
13
|
+
attr_reader :parts
|
14
|
+
|
15
|
+
# Initialize a new memory
|
16
|
+
# @param role [String] The role of the sender (user, model, system)
|
17
|
+
# @param parts [Array<Hash>] The parts of the message
|
18
|
+
def initialize(role = "", parts = nil)
|
19
|
+
@role = role
|
20
|
+
@parts = parts || [{text: ""}]
|
21
|
+
@parts.freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
# Convert the memory to a hash suitable for API requests
|
25
|
+
# @return [Hash] The memory as a hash
|
26
|
+
def to_h
|
27
|
+
{
|
28
|
+
role: @role,
|
29
|
+
parts: @parts
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Convert the memory to a JSON string
|
34
|
+
# @param opts [Hash] JSON generate options
|
35
|
+
# @return [String] The memory as a JSON string
|
36
|
+
def to_json(*opts)
|
37
|
+
if opts.first && opts.first[:pretty]
|
38
|
+
JSON.pretty_generate(to_h)
|
39
|
+
else
|
40
|
+
to_h.to_json(*opts)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Equality comparison
|
45
|
+
# @param other [Object] The object to compare with
|
46
|
+
# @return [Boolean] True if the objects are equal
|
47
|
+
def ==(other)
|
48
|
+
return false unless other.is_a?(Memory)
|
49
|
+
role == other.role && parts == other.parts
|
50
|
+
end
|
51
|
+
|
52
|
+
# Create a Memory object from a hash
|
53
|
+
# @param hash [Hash] The hash to create from
|
54
|
+
# @return [Memory] A new Memory object
|
55
|
+
def self.from_hash(hash)
|
56
|
+
# Handle both string and symbol keys
|
57
|
+
role = hash[:role] || hash["role"] || ""
|
58
|
+
|
59
|
+
# Handle parts
|
60
|
+
parts_data = hash[:parts] || hash["parts"]
|
61
|
+
parts = if parts_data
|
62
|
+
# Convert string keys to symbols
|
63
|
+
parts_data.map do |part|
|
64
|
+
if part.is_a?(Hash)
|
65
|
+
part_with_symbol_keys = {}
|
66
|
+
part.each { |k, v| part_with_symbol_keys[k.to_sym] = v }
|
67
|
+
part_with_symbol_keys
|
68
|
+
else
|
69
|
+
part
|
70
|
+
end
|
71
|
+
end
|
72
|
+
else
|
73
|
+
[{text: ""}]
|
74
|
+
end
|
75
|
+
|
76
|
+
new(role, parts)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Create a Memory object from a JSON string
|
80
|
+
# @param json [String] The JSON string
|
81
|
+
# @return [Memory] A new Memory object
|
82
|
+
def self.from_json(json)
|
83
|
+
hash = JSON.parse(json)
|
84
|
+
from_hash(hash)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Base class for messages in a conversation
|
6
|
+
class Message
|
7
|
+
# @return [String] The content of the message
|
8
|
+
attr_reader :content
|
9
|
+
|
10
|
+
# @return [Time] When the message was created
|
11
|
+
attr_reader :timestamp
|
12
|
+
|
13
|
+
# @return [String] The role of the message sender (user or model)
|
14
|
+
attr_reader :role
|
15
|
+
|
16
|
+
# Initialize a new message
|
17
|
+
# @param content [String] The content of the message
|
18
|
+
# @param role [String] The role of the message sender
|
19
|
+
# @param timestamp [Time, nil] When the message was created
|
20
|
+
def initialize(content, role, timestamp = nil)
|
21
|
+
@content = content
|
22
|
+
@role = role
|
23
|
+
@timestamp = timestamp || Time.now
|
24
|
+
validate!
|
25
|
+
end
|
26
|
+
|
27
|
+
# Convert the message to a hash suitable for the API
|
28
|
+
# @return [Hash] The message as a hash
|
29
|
+
def to_hash
|
30
|
+
{
|
31
|
+
role: @role,
|
32
|
+
parts: [
|
33
|
+
{
|
34
|
+
text: @content
|
35
|
+
}
|
36
|
+
]
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
# Alias for to_hash for consistency with Ruby conventions
|
41
|
+
# @return [Hash] The message as a hash
|
42
|
+
def to_h
|
43
|
+
to_hash
|
44
|
+
end
|
45
|
+
|
46
|
+
# Check if this message is from the user
|
47
|
+
# @return [Boolean] True if the message is from the user
|
48
|
+
def user?
|
49
|
+
@role == "user"
|
50
|
+
end
|
51
|
+
|
52
|
+
# Check if this message is from the model
|
53
|
+
# @return [Boolean] True if the message is from the model
|
54
|
+
def model?
|
55
|
+
@role == "model"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Serialize the message to a JSON string
|
59
|
+
# @return [String] The message as a JSON string
|
60
|
+
def to_json(*args)
|
61
|
+
to_h.to_json(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Create a message from a hash
|
65
|
+
# @param hash [Hash] The hash to create the message from
|
66
|
+
# @return [Message] A new message object
|
67
|
+
def self.from_hash(hash)
|
68
|
+
content = extract_content(hash)
|
69
|
+
role = hash["role"]
|
70
|
+
timestamp = hash["timestamp"] ? Time.parse(hash["timestamp"]) : Time.now
|
71
|
+
|
72
|
+
case role
|
73
|
+
when "user"
|
74
|
+
UserMessage.new(content, timestamp)
|
75
|
+
when "model"
|
76
|
+
ModelMessage.new(content, timestamp)
|
77
|
+
else
|
78
|
+
new(content, role, timestamp)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Extract content from a message hash
|
83
|
+
# @param hash [Hash] The message hash
|
84
|
+
# @return [String] The extracted content
|
85
|
+
def self.extract_content(hash)
|
86
|
+
parts = hash["parts"]
|
87
|
+
return "" unless parts && !parts.empty?
|
88
|
+
|
89
|
+
parts.map { |part| part["text"] }.compact.join(" ")
|
90
|
+
end
|
91
|
+
|
92
|
+
private_class_method :extract_content
|
93
|
+
|
94
|
+
# Validate the message parameters
|
95
|
+
# @raise [Geminize::ValidationError] If any parameter is invalid
|
96
|
+
def validate!
|
97
|
+
validate_content!
|
98
|
+
validate_role!
|
99
|
+
end
|
100
|
+
|
101
|
+
# Validate the content parameter
|
102
|
+
# @raise [Geminize::ValidationError] If the content is invalid
|
103
|
+
def validate_content!
|
104
|
+
Validators.validate_not_empty!(@content, "Content")
|
105
|
+
end
|
106
|
+
|
107
|
+
# Validate the role parameter
|
108
|
+
# @raise [Geminize::ValidationError] If the role is invalid
|
109
|
+
def validate_role!
|
110
|
+
allowed_roles = ["user", "model", "system"]
|
111
|
+
unless allowed_roles.include?(@role)
|
112
|
+
raise Geminize::ValidationError.new(
|
113
|
+
"Role must be one of: #{allowed_roles.join(", ")}",
|
114
|
+
"INVALID_ARGUMENT"
|
115
|
+
)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Represents a message from the user
|
121
|
+
class UserMessage < Message
|
122
|
+
# Initialize a new user message
|
123
|
+
# @param content [String] The content of the message
|
124
|
+
# @param timestamp [Time, nil] When the message was created
|
125
|
+
def initialize(content, timestamp = nil)
|
126
|
+
super(content, "user", timestamp)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Represents a message from the model
|
131
|
+
class ModelMessage < Message
|
132
|
+
# Initialize a new model message
|
133
|
+
# @param content [String] The content of the message
|
134
|
+
# @param timestamp [Time, nil] When the message was created
|
135
|
+
def initialize(content, timestamp = nil)
|
136
|
+
super(content, "model", timestamp)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Represents an AI model from the Gemini API.
|
6
|
+
class Model
|
7
|
+
# @return [String] The unique identifier for the model
|
8
|
+
attr_reader :id
|
9
|
+
|
10
|
+
# @return [String] The display name of the model
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
# @return [String] The model version
|
14
|
+
attr_reader :version
|
15
|
+
|
16
|
+
# @return [String] The model description
|
17
|
+
attr_reader :description
|
18
|
+
|
19
|
+
# @return [Array<String>] List of supported capabilities (e.g., 'text', 'vision', 'embedding')
|
20
|
+
attr_reader :capabilities
|
21
|
+
|
22
|
+
# @return [Hash] Model limitations and constraints
|
23
|
+
attr_reader :limitations
|
24
|
+
|
25
|
+
# @return [Array<String>] Recommended use cases for this model
|
26
|
+
attr_reader :use_cases
|
27
|
+
|
28
|
+
# @return [Hash] Raw model data from the API
|
29
|
+
attr_reader :raw_data
|
30
|
+
|
31
|
+
# Create a new Model instance
|
32
|
+
# @param attributes [Hash] Model attributes
|
33
|
+
# @option attributes [String] :id The model ID
|
34
|
+
# @option attributes [String] :name The model name
|
35
|
+
# @option attributes [String] :version The model version
|
36
|
+
# @option attributes [String] :description The model description
|
37
|
+
# @option attributes [Array<String>] :capabilities List of capabilities
|
38
|
+
# @option attributes [Hash] :limitations Model limitations
|
39
|
+
# @option attributes [Array<String>] :use_cases Recommended use cases
|
40
|
+
# @option attributes [Hash] :raw_data Raw model data from API
|
41
|
+
def initialize(attributes = {})
|
42
|
+
@id = attributes[:id]
|
43
|
+
@name = attributes[:name]
|
44
|
+
@version = attributes[:version]
|
45
|
+
@description = attributes[:description]
|
46
|
+
@capabilities = attributes[:capabilities] || []
|
47
|
+
@limitations = attributes[:limitations] || {}
|
48
|
+
@use_cases = attributes[:use_cases] || []
|
49
|
+
@raw_data = attributes[:raw_data] || {}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Check if model supports a specific capability
|
53
|
+
# @param capability [String] Capability to check for
|
54
|
+
# @return [Boolean] True if the model supports the capability
|
55
|
+
def supports?(capability)
|
56
|
+
capabilities.include?(capability.to_s.downcase)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Convert model to a hash representation
|
60
|
+
# @return [Hash] Hash representation of the model
|
61
|
+
def to_h
|
62
|
+
{
|
63
|
+
id: id,
|
64
|
+
name: name,
|
65
|
+
version: version,
|
66
|
+
description: description,
|
67
|
+
capabilities: capabilities,
|
68
|
+
limitations: limitations,
|
69
|
+
use_cases: use_cases
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
# Convert model to JSON string
|
74
|
+
# @return [String] JSON representation of the model
|
75
|
+
def to_json(*args)
|
76
|
+
to_h.to_json(*args)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Create a Model from API response data
|
80
|
+
# @param data [Hash] Raw API response data
|
81
|
+
# @return [Model] New Model instance
|
82
|
+
def self.from_api_data(data)
|
83
|
+
# Extract capabilities from model data
|
84
|
+
capabilities = extract_capabilities(data)
|
85
|
+
|
86
|
+
# Extract limitations from model data
|
87
|
+
limitations = extract_limitations(data)
|
88
|
+
|
89
|
+
# Extract use cases from model data
|
90
|
+
use_cases = extract_use_cases(data)
|
91
|
+
|
92
|
+
new(
|
93
|
+
id: data["name"]&.split("/")&.last,
|
94
|
+
name: data["displayName"],
|
95
|
+
version: extract_version(data),
|
96
|
+
description: data["description"],
|
97
|
+
capabilities: capabilities,
|
98
|
+
limitations: limitations,
|
99
|
+
use_cases: use_cases,
|
100
|
+
raw_data: data
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
private_class_method def self.extract_version(data)
|
105
|
+
# Extract version from model name or other fields
|
106
|
+
# Example: if name is "gemini-1.0-pro", extract "1.0"
|
107
|
+
if data["displayName"]
|
108
|
+
match = data["displayName"].match(/[-_](\d+\.\d+)[-_]/)
|
109
|
+
return match[1] if match
|
110
|
+
|
111
|
+
# Try another pattern (e.g., "Gemini 1.5 Pro")
|
112
|
+
match = data["displayName"].match(/\s(\d+\.\d+)\s/)
|
113
|
+
return match[1] if match
|
114
|
+
end
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
|
118
|
+
private_class_method def self.extract_capabilities(data)
|
119
|
+
capabilities = []
|
120
|
+
|
121
|
+
# Example capability extraction, adjust based on actual API response format
|
122
|
+
capabilities << "text" if data.dig("supportedGenerationMethods")&.include?("generateText")
|
123
|
+
capabilities << "chat" if data.dig("supportedGenerationMethods")&.include?("generateMessage")
|
124
|
+
capabilities << "vision" if data.dig("supportedGenerationMethods")&.include?("generateContent") &&
|
125
|
+
data.dig("inputSetting", "supportMultiModal")
|
126
|
+
capabilities << "embedding" if data.dig("supportedGenerationMethods")&.include?("embedContent")
|
127
|
+
|
128
|
+
capabilities
|
129
|
+
end
|
130
|
+
|
131
|
+
private_class_method def self.extract_limitations(data)
|
132
|
+
limitations = {}
|
133
|
+
|
134
|
+
# Extract token limits
|
135
|
+
if data.dig("inputTokenLimit")
|
136
|
+
limitations[:input_token_limit] = data["inputTokenLimit"]
|
137
|
+
end
|
138
|
+
|
139
|
+
if data.dig("outputTokenLimit")
|
140
|
+
limitations[:output_token_limit] = data["outputTokenLimit"]
|
141
|
+
end
|
142
|
+
|
143
|
+
# Extract any other limitations from the API data
|
144
|
+
limitations
|
145
|
+
end
|
146
|
+
|
147
|
+
private_class_method def self.extract_use_cases(data)
|
148
|
+
# Extract use cases from the description or other fields
|
149
|
+
# This is a simple implementation - adjust based on actual API data
|
150
|
+
use_cases = []
|
151
|
+
|
152
|
+
if data["description"]
|
153
|
+
if data["description"].include?("chat")
|
154
|
+
use_cases << "conversational_ai"
|
155
|
+
end
|
156
|
+
|
157
|
+
if data["description"].include?("vision") || data["description"].include?("image")
|
158
|
+
use_cases << "image_understanding"
|
159
|
+
end
|
160
|
+
|
161
|
+
if data["description"].include?("embedding")
|
162
|
+
use_cases << "semantic_search"
|
163
|
+
use_cases << "clustering"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
use_cases
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Geminize
|
6
|
+
module Models
|
7
|
+
# Represents a collection of AI models with filtering capabilities
|
8
|
+
class ModelList
|
9
|
+
include Enumerable
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
# @return [Array<Model>] The list of models
|
13
|
+
attr_reader :models
|
14
|
+
|
15
|
+
# Delegate array methods to the underlying models array
|
16
|
+
def_delegators :@models, :[], :size, :length, :empty?, :first, :last
|
17
|
+
|
18
|
+
# Create a new ModelList
|
19
|
+
# @param models [Array<Model>] Initial list of models
|
20
|
+
def initialize(models = [])
|
21
|
+
@models = models
|
22
|
+
end
|
23
|
+
|
24
|
+
# Implement Enumerable's required each method
|
25
|
+
# @yield [Model] Each model in the list
|
26
|
+
def each(&block)
|
27
|
+
@models.each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add a model to the list
|
31
|
+
# @param model [Model] The model to add
|
32
|
+
# @return [ModelList] The updated model list
|
33
|
+
def add(model)
|
34
|
+
@models << model
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
# Find a model by its ID
|
39
|
+
# @param id [String] The model ID to search for
|
40
|
+
# @return [Model, nil] The found model or nil
|
41
|
+
def find_by_id(id)
|
42
|
+
@models.find { |model| model.id == id }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Find all models that support a specific capability
|
46
|
+
# @param capability [String] The capability to filter by
|
47
|
+
# @return [ModelList] A new ModelList containing only matching models
|
48
|
+
def filter_by_capability(capability)
|
49
|
+
filtered = @models.select { |model| model.supports?(capability) }
|
50
|
+
ModelList.new(filtered)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Find all models that support vision capabilities
|
54
|
+
# @return [ModelList] A new ModelList containing only vision-capable models
|
55
|
+
def vision_models
|
56
|
+
filter_by_capability("vision")
|
57
|
+
end
|
58
|
+
|
59
|
+
# Find all models that support embedding capabilities
|
60
|
+
# @return [ModelList] A new ModelList containing only embedding-capable models
|
61
|
+
def embedding_models
|
62
|
+
filter_by_capability("embedding")
|
63
|
+
end
|
64
|
+
|
65
|
+
# Find all models that support text generation
|
66
|
+
# @return [ModelList] A new ModelList containing only text generation models
|
67
|
+
def text_models
|
68
|
+
filter_by_capability("text")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Find all models that support chat/conversation
|
72
|
+
# @return [ModelList] A new ModelList containing only chat-capable models
|
73
|
+
def chat_models
|
74
|
+
filter_by_capability("chat")
|
75
|
+
end
|
76
|
+
|
77
|
+
# Filter models by version
|
78
|
+
# @param version [String] The version to filter by
|
79
|
+
# @return [ModelList] A new ModelList containing only matching models
|
80
|
+
def filter_by_version(version)
|
81
|
+
filtered = @models.select { |model| model.version == version }
|
82
|
+
ModelList.new(filtered)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Filter models by name pattern
|
86
|
+
# @param pattern [String, Regexp] The pattern to match model names against
|
87
|
+
# @return [ModelList] A new ModelList containing only matching models
|
88
|
+
def filter_by_name(pattern)
|
89
|
+
pattern = Regexp.new(pattern.to_s, Regexp::IGNORECASE) if pattern.is_a?(String)
|
90
|
+
filtered = @models.select { |model| model.name&.match?(pattern) }
|
91
|
+
ModelList.new(filtered)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Create a ModelList from API response data
|
95
|
+
# @param data [Hash] API response containing models
|
96
|
+
# @return [ModelList] New ModelList instance
|
97
|
+
def self.from_api_data(data)
|
98
|
+
models = []
|
99
|
+
|
100
|
+
# Process model data from API response
|
101
|
+
# The exact structure will depend on the Gemini API response format
|
102
|
+
if data.key?("models")
|
103
|
+
models = data["models"].map do |model_data|
|
104
|
+
Model.from_api_data(model_data)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
new(models)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Convert to array of hashes representation
|
112
|
+
# @return [Array<Hash>] Array of model hashes
|
113
|
+
def to_a
|
114
|
+
@models.map(&:to_h)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Convert to JSON string
|
118
|
+
# @return [String] JSON representation
|
119
|
+
def to_json(*args)
|
120
|
+
to_a.to_json(*args)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Represents a streaming response chunk from the Gemini API
|
6
|
+
class StreamResponse
|
7
|
+
# @return [Hash] The raw API response data for this chunk
|
8
|
+
attr_reader :raw_chunk
|
9
|
+
|
10
|
+
# @return [String, nil] The text content in this chunk
|
11
|
+
attr_reader :text
|
12
|
+
|
13
|
+
# @return [String, nil] The finish reason if this is the last chunk
|
14
|
+
attr_reader :finish_reason
|
15
|
+
|
16
|
+
# @return [Hash, nil] Token usage metrics (only available in final chunk)
|
17
|
+
attr_reader :usage_metrics
|
18
|
+
|
19
|
+
# Initialize a new streaming response chunk
|
20
|
+
# @param chunk_data [Hash] The raw API response chunk
|
21
|
+
def initialize(chunk_data)
|
22
|
+
@raw_chunk = chunk_data
|
23
|
+
parse_chunk
|
24
|
+
end
|
25
|
+
|
26
|
+
# Check if this is the final chunk in the stream
|
27
|
+
# @return [Boolean] True if this is the final chunk
|
28
|
+
def final_chunk?
|
29
|
+
!@finish_reason.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Check if this chunk has usage metrics
|
33
|
+
# @return [Boolean] True if this chunk contains usage metrics
|
34
|
+
def has_usage_metrics?
|
35
|
+
!@usage_metrics.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get the prompt token count if available
|
39
|
+
# @return [Integer, nil] Prompt token count or nil if not available
|
40
|
+
def prompt_tokens
|
41
|
+
return nil unless @usage_metrics
|
42
|
+
@usage_metrics["promptTokenCount"]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get the completion token count if available
|
46
|
+
# @return [Integer, nil] Completion token count or nil if not available
|
47
|
+
def completion_tokens
|
48
|
+
return nil unless @usage_metrics
|
49
|
+
@usage_metrics["candidatesTokenCount"]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get the total token count if available
|
53
|
+
# @return [Integer, nil] Total token count or nil if not available
|
54
|
+
def total_tokens
|
55
|
+
return nil unless @usage_metrics
|
56
|
+
(prompt_tokens || 0) + (completion_tokens || 0)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create a StreamResponse object from a raw API response chunk
|
60
|
+
# @param chunk_data [Hash] The raw API response chunk
|
61
|
+
# @return [StreamResponse] A new StreamResponse object
|
62
|
+
def self.from_hash(chunk_data)
|
63
|
+
new(chunk_data)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# Parse the chunk data and extract relevant information
|
69
|
+
def parse_chunk
|
70
|
+
@text = nil
|
71
|
+
@finish_reason = nil
|
72
|
+
@usage_metrics = nil
|
73
|
+
|
74
|
+
# First check if the response has the expected fields
|
75
|
+
if @raw_chunk.is_a?(Hash)
|
76
|
+
candidates = @raw_chunk["candidates"]
|
77
|
+
if candidates && !candidates.empty?
|
78
|
+
# Extract finish reason if available (last chunk)
|
79
|
+
if candidates.first["finishReason"]
|
80
|
+
@finish_reason = candidates.first["finishReason"]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Extract text content if available
|
84
|
+
content = candidates.first["content"]
|
85
|
+
if content && content["parts"] && !content["parts"].empty?
|
86
|
+
parts_text = content["parts"].map { |part| part["text"] }.compact
|
87
|
+
@text = parts_text.join(" ") unless parts_text.empty?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Extract usage metrics if available
|
92
|
+
if @raw_chunk["usageMetadata"]
|
93
|
+
@usage_metrics = @raw_chunk["usageMetadata"]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|