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,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
# High-level service for managing conversations
|
5
|
+
class ConversationService
|
6
|
+
# @return [Geminize::ConversationRepository] The repository for storing conversations
|
7
|
+
attr_reader :repository
|
8
|
+
|
9
|
+
# @return [Geminize::Client] The client instance
|
10
|
+
attr_reader :client
|
11
|
+
|
12
|
+
# Initialize a new conversation service
|
13
|
+
# @param repository [Geminize::ConversationRepository, nil] The repository to use
|
14
|
+
# @param client [Geminize::Client, nil] The client to use
|
15
|
+
# @param options [Hash] Additional options
|
16
|
+
def initialize(repository = nil, client = nil, options = {})
|
17
|
+
@repository = repository || Geminize.conversation_repository
|
18
|
+
@client = client || Client.new(options)
|
19
|
+
@options = options
|
20
|
+
end
|
21
|
+
|
22
|
+
# Create a new conversation
|
23
|
+
# @param title [String, nil] Optional title for the conversation
|
24
|
+
# @param system_instruction [String, nil] Optional system instruction to guide model behavior
|
25
|
+
# @return [Models::Conversation] The new conversation
|
26
|
+
def create_conversation(title = nil, system_instruction = nil)
|
27
|
+
# Create a conversation with the appropriate arguments
|
28
|
+
conversation = if system_instruction
|
29
|
+
Models::Conversation.new(nil, title, nil, nil, system_instruction)
|
30
|
+
else
|
31
|
+
Models::Conversation.new(nil, title)
|
32
|
+
end
|
33
|
+
@repository.save(conversation)
|
34
|
+
conversation
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get a conversation by ID
|
38
|
+
# @param id [String] The ID of the conversation to get
|
39
|
+
# @return [Models::Conversation, nil] The conversation or nil if not found
|
40
|
+
# @raise [Geminize::GeminizeError] If the conversation cannot be loaded
|
41
|
+
def get_conversation(id)
|
42
|
+
@repository.load(id)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Send a message in a conversation and get a response
|
46
|
+
# @param conversation_id [String] The ID of the conversation
|
47
|
+
# @param message [String] The message to send
|
48
|
+
# @param model_name [String, nil] The model to use
|
49
|
+
# @param params [Hash] Additional generation parameters
|
50
|
+
# @return [Hash] The response and updated conversation
|
51
|
+
# @raise [Geminize::GeminizeError] If the request fails
|
52
|
+
def send_message(conversation_id, message, model_name = nil, params = {})
|
53
|
+
# Load the conversation
|
54
|
+
conversation = get_conversation(conversation_id)
|
55
|
+
raise Geminize::GeminizeError.new("Conversation not found: #{conversation_id}", nil, nil) unless conversation
|
56
|
+
|
57
|
+
# Create a chat instance with the conversation
|
58
|
+
chat = Chat.new(conversation, @client, @options)
|
59
|
+
|
60
|
+
# Send the message
|
61
|
+
response = chat.send_message(message, model_name, params)
|
62
|
+
|
63
|
+
# Save the updated conversation
|
64
|
+
@repository.save(conversation)
|
65
|
+
|
66
|
+
{
|
67
|
+
response: response,
|
68
|
+
conversation: conversation
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
# List all available conversations
|
73
|
+
# @return [Array<Hash>] An array of conversation metadata
|
74
|
+
def list_conversations
|
75
|
+
@repository.list
|
76
|
+
end
|
77
|
+
|
78
|
+
# Delete a conversation
|
79
|
+
# @param id [String] The ID of the conversation to delete
|
80
|
+
# @return [Boolean] True if the deletion was successful
|
81
|
+
def delete_conversation(id)
|
82
|
+
@repository.delete(id)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Update a conversation title
|
86
|
+
# @param id [String] The ID of the conversation to update
|
87
|
+
# @param title [String] The new title
|
88
|
+
# @return [Models::Conversation] The updated conversation
|
89
|
+
# @raise [Geminize::GeminizeError] If the conversation cannot be loaded or saved
|
90
|
+
def update_conversation_title(id, title)
|
91
|
+
conversation = get_conversation(id)
|
92
|
+
raise Geminize::GeminizeError.new("Conversation not found: #{id}", nil, nil) unless conversation
|
93
|
+
|
94
|
+
conversation.title = title
|
95
|
+
@repository.save(conversation)
|
96
|
+
conversation
|
97
|
+
end
|
98
|
+
|
99
|
+
# Clear a conversation's message history
|
100
|
+
# @param id [String] The ID of the conversation to clear
|
101
|
+
# @return [Models::Conversation] The updated conversation
|
102
|
+
# @raise [Geminize::GeminizeError] If the conversation cannot be loaded or saved
|
103
|
+
def clear_conversation(id)
|
104
|
+
conversation = get_conversation(id)
|
105
|
+
raise Geminize::GeminizeError.new("Conversation not found: #{id}", nil, nil) unless conversation
|
106
|
+
|
107
|
+
conversation.clear
|
108
|
+
@repository.save(conversation)
|
109
|
+
conversation
|
110
|
+
end
|
111
|
+
|
112
|
+
# Update a conversation's system instruction
|
113
|
+
# @param id [String] The ID of the conversation to update
|
114
|
+
# @param system_instruction [String] The new system instruction
|
115
|
+
# @return [Models::Conversation] The updated conversation
|
116
|
+
# @raise [Geminize::GeminizeError] If the conversation cannot be loaded or saved
|
117
|
+
def update_conversation_system_instruction(id, system_instruction)
|
118
|
+
conversation = get_conversation(id)
|
119
|
+
raise Geminize::GeminizeError.new("Conversation not found: #{id}", nil, nil) unless conversation
|
120
|
+
|
121
|
+
conversation.system_instruction = system_instruction
|
122
|
+
@repository.save(conversation)
|
123
|
+
conversation
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "models/embedding_request"
|
4
|
+
require_relative "models/embedding_response"
|
5
|
+
|
6
|
+
module Geminize
|
7
|
+
# Class for embedding generation functionality
|
8
|
+
class Embeddings
|
9
|
+
# Default maximum batch size
|
10
|
+
DEFAULT_MAX_BATCH_SIZE = 100
|
11
|
+
|
12
|
+
# @return [Geminize::Client] The client instance
|
13
|
+
attr_reader :client
|
14
|
+
|
15
|
+
# Initialize a new embeddings instance
|
16
|
+
# @param client [Geminize::Client, nil] The client to use (optional)
|
17
|
+
# @param options [Hash] Additional options
|
18
|
+
def initialize(client = nil, options = {})
|
19
|
+
@client = client || Client.new(options)
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generate embeddings based on an embedding request
|
24
|
+
# @param embedding_request [Geminize::Models::EmbeddingRequest] The embedding request
|
25
|
+
# @return [Geminize::Models::EmbeddingResponse] The embedding response
|
26
|
+
# @raise [Geminize::GeminizeError] If the request fails
|
27
|
+
def generate(embedding_request)
|
28
|
+
endpoint = build_embedding_endpoint(embedding_request.model_name, embedding_request.batch?)
|
29
|
+
payload = embedding_request.to_hash
|
30
|
+
|
31
|
+
response_data = @client.post(endpoint, payload)
|
32
|
+
Models::EmbeddingResponse.from_hash(response_data)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Generate embeddings from text with optional parameters
|
36
|
+
# @param text [String, Array<String>] The input text or array of texts
|
37
|
+
# @param model_name [String, nil] The model to use (optional)
|
38
|
+
# @param params [Hash] Additional generation parameters
|
39
|
+
# @option params [Integer] :dimensions Desired dimensionality of the embeddings
|
40
|
+
# @option params [String] :task_type The embedding task type
|
41
|
+
# @option params [Integer] :batch_size Maximum number of texts to process in one batch
|
42
|
+
# @return [Geminize::Models::EmbeddingResponse] The embedding response
|
43
|
+
# @raise [Geminize::GeminizeError] If the request fails
|
44
|
+
def generate_embedding(text, model_name = nil, params = {})
|
45
|
+
model = model_name || Geminize.configuration.default_embedding_model
|
46
|
+
|
47
|
+
# Check if we need to handle batching
|
48
|
+
if text.is_a?(Array) && text.length > 1
|
49
|
+
batch_size = params.delete(:batch_size) || DEFAULT_MAX_BATCH_SIZE
|
50
|
+
if text.length > batch_size
|
51
|
+
return batch_process_embeddings(text, model, params, batch_size)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Regular processing for a single text or a small batch
|
56
|
+
embedding_request = Models::EmbeddingRequest.new(
|
57
|
+
model_name: model,
|
58
|
+
text: text,
|
59
|
+
task_type: params[:task_type] || Geminize::Models::EmbeddingRequest::RETRIEVAL_DOCUMENT,
|
60
|
+
params: params
|
61
|
+
)
|
62
|
+
|
63
|
+
generate(embedding_request)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Process multiple texts in batches
|
67
|
+
# @param texts [Array<String>] List of texts to process
|
68
|
+
# @param model_name [String, nil] Model name
|
69
|
+
# @param params [Hash] Additional parameters
|
70
|
+
# @param batch_size [Integer] Maximum batch size
|
71
|
+
# @return [Geminize::Models::EmbeddingResponse] Combined embedding response
|
72
|
+
def batch_process_embeddings(texts, model_name, params, batch_size)
|
73
|
+
batches = texts.each_slice(batch_size).to_a
|
74
|
+
responses = []
|
75
|
+
|
76
|
+
batches.each do |batch|
|
77
|
+
request = Models::EmbeddingRequest.new(
|
78
|
+
model_name: model_name,
|
79
|
+
text: batch,
|
80
|
+
task_type: params[:task_type] || Geminize::Models::EmbeddingRequest::RETRIEVAL_DOCUMENT,
|
81
|
+
params: params
|
82
|
+
)
|
83
|
+
responses << generate(request)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Combine all responses into a single response object
|
87
|
+
combine_responses(responses)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Combine multiple embedding responses into a single response
|
93
|
+
# @param responses [Array<Geminize::Models::EmbeddingResponse>] List of responses
|
94
|
+
# @return [Geminize::Models::EmbeddingResponse] Combined response
|
95
|
+
def combine_responses(responses)
|
96
|
+
return responses.first if responses.length == 1
|
97
|
+
|
98
|
+
# Create a synthesized response hash
|
99
|
+
combined_hash = {
|
100
|
+
"embeddings" => [],
|
101
|
+
"usageMetadata" => {
|
102
|
+
"promptTokenCount" => 0,
|
103
|
+
"totalTokenCount" => 0
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
# Merge all embeddings and usage data
|
108
|
+
responses.each do |response|
|
109
|
+
# Add embeddings
|
110
|
+
if response.raw_response["embeddings"]
|
111
|
+
combined_hash["embeddings"].concat(response.raw_response["embeddings"])
|
112
|
+
end
|
113
|
+
|
114
|
+
# Aggregate usage data
|
115
|
+
if response.usage
|
116
|
+
combined_hash["usageMetadata"]["promptTokenCount"] += response.prompt_tokens || 0
|
117
|
+
combined_hash["usageMetadata"]["totalTokenCount"] += response.total_tokens || 0
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Create a new response object
|
122
|
+
Models::EmbeddingResponse.from_hash(combined_hash)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Build the endpoint for the embeddings API based on the model name
|
126
|
+
# @param model_name [String] The name of the model to use for embeddings
|
127
|
+
# @param batch [Boolean] Whether this is a batch request
|
128
|
+
# @return [String] The endpoint URL
|
129
|
+
def build_embedding_endpoint(model_name, batch = false)
|
130
|
+
# Clean model name - remove 'models/' prefix if present
|
131
|
+
clean_model_name = model_name.start_with?("models/") ? model_name[7..] : model_name
|
132
|
+
|
133
|
+
if batch
|
134
|
+
# For text-embedding models, use a different format
|
135
|
+
if clean_model_name.start_with?("text-embedding-")
|
136
|
+
"#{clean_model_name}:batchEmbedContents"
|
137
|
+
else
|
138
|
+
"models/#{clean_model_name}:batchEmbedContents"
|
139
|
+
end
|
140
|
+
else
|
141
|
+
"models/#{clean_model_name}:embedContent"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
# Maps API error responses to appropriate exception classes
|
5
|
+
class ErrorMapper
|
6
|
+
# Maps an error response to the appropriate exception
|
7
|
+
# @param error_info [Hash] Hash containing error information (http_status, code, message)
|
8
|
+
# @return [Geminize::GeminizeError] An instance of the appropriate exception class
|
9
|
+
def self.map(error_info)
|
10
|
+
new(error_info).map
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Hash] The error information hash
|
14
|
+
attr_reader :error_info
|
15
|
+
|
16
|
+
# Initialize a new mapper
|
17
|
+
# @param error_info [Hash] Hash containing error information (http_status, code, message)
|
18
|
+
def initialize(error_info)
|
19
|
+
@error_info = error_info
|
20
|
+
end
|
21
|
+
|
22
|
+
# Map the error to an appropriate exception
|
23
|
+
# @return [Geminize::GeminizeError] An instance of the appropriate exception class
|
24
|
+
def map
|
25
|
+
error_class = determine_error_class
|
26
|
+
error_class.new(
|
27
|
+
error_info[:message],
|
28
|
+
error_info[:code],
|
29
|
+
error_info[:http_status]
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Determine the appropriate error class based on status code and error type
|
36
|
+
# @return [Class] The exception class to instantiate
|
37
|
+
def determine_error_class
|
38
|
+
# First check for specific error codes from the API
|
39
|
+
api_error_class = map_api_error_code
|
40
|
+
return api_error_class if api_error_class
|
41
|
+
|
42
|
+
# Then fall back to HTTP status code mapping
|
43
|
+
http_status_class = map_http_status
|
44
|
+
return http_status_class if http_status_class
|
45
|
+
|
46
|
+
# Default to generic error
|
47
|
+
GeminizeError
|
48
|
+
end
|
49
|
+
|
50
|
+
# Map error based on API-specific error codes
|
51
|
+
# @return [Class, nil] The error class or nil if no mapping is found
|
52
|
+
def map_api_error_code
|
53
|
+
return nil unless error_info[:code]
|
54
|
+
|
55
|
+
code = error_info[:code].to_s.downcase
|
56
|
+
message = error_info[:message].to_s.downcase
|
57
|
+
|
58
|
+
if code.include?("permission") || code.include?("unauthorized") || code.include?("unauthenticated")
|
59
|
+
AuthenticationError
|
60
|
+
elsif code.include?("quota") || code.include?("rate") || code.include?("limit")
|
61
|
+
RateLimitError
|
62
|
+
elsif code.include?("not_found") || code.include?("notfound")
|
63
|
+
ResourceNotFoundError
|
64
|
+
elsif code.include?("invalid") && (code.include?("model") || message.include?("model"))
|
65
|
+
InvalidModelError
|
66
|
+
elsif code.include?("invalid") || code.include?("validation")
|
67
|
+
ValidationError
|
68
|
+
elsif code.include?("blocked") || message.include?("blocked") || message.include?("safety")
|
69
|
+
ContentBlockedError
|
70
|
+
elsif code.include?("server") || code.include?("internal")
|
71
|
+
ServerError
|
72
|
+
elsif code.include?("config")
|
73
|
+
ConfigurationError
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Map error based on HTTP status code
|
78
|
+
# @return [Class] The error class
|
79
|
+
def map_http_status
|
80
|
+
status = error_info[:http_status]
|
81
|
+
|
82
|
+
case status
|
83
|
+
when 400
|
84
|
+
BadRequestError
|
85
|
+
when 401, 403
|
86
|
+
AuthenticationError
|
87
|
+
when 404
|
88
|
+
ResourceNotFoundError
|
89
|
+
when 429
|
90
|
+
RateLimitError
|
91
|
+
when 500..599
|
92
|
+
ServerError
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Geminize
|
6
|
+
# Utility class for parsing error responses from the Gemini API
|
7
|
+
class ErrorParser
|
8
|
+
# Parse an error response and extract relevant information
|
9
|
+
# @param response [Faraday::Response] The response object
|
10
|
+
# @return [Hash] Hash containing extracted error information
|
11
|
+
def self.parse(response)
|
12
|
+
new(response).parse
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Faraday::Response] The response object
|
16
|
+
attr_reader :response
|
17
|
+
|
18
|
+
# Initialize a new parser
|
19
|
+
# @param response [Faraday::Response] The response object
|
20
|
+
def initialize(response)
|
21
|
+
@response = response
|
22
|
+
end
|
23
|
+
|
24
|
+
# Parse the response and extract error information
|
25
|
+
# @return [Hash] Hash containing error code, message, and status
|
26
|
+
def parse
|
27
|
+
{
|
28
|
+
http_status: response.status,
|
29
|
+
code: extract_error_code,
|
30
|
+
message: extract_error_message
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Extract the error code from the response
|
37
|
+
# @return [String, nil] The error code or nil if not present
|
38
|
+
def extract_error_code
|
39
|
+
return nil unless parsed_body && parsed_body["error"]
|
40
|
+
|
41
|
+
if parsed_body["error"].is_a?(Hash)
|
42
|
+
code = parsed_body["error"]["code"] || parsed_body["error"]["status"]
|
43
|
+
code&.to_s # Use safe navigation operator
|
44
|
+
elsif parsed_body["error"].is_a?(String)
|
45
|
+
# Extract error code from string if possible
|
46
|
+
parsed_body["error"].scan(/code[:\s]+([A-Z_]+)/i).flatten.first
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Extract the error message from the response
|
51
|
+
# @return [String] The error message
|
52
|
+
def extract_error_message
|
53
|
+
return default_error_message unless parsed_body
|
54
|
+
|
55
|
+
if parsed_body["error"].is_a?(Hash)
|
56
|
+
if parsed_body["error"]["message"]
|
57
|
+
parsed_body["error"]["message"]
|
58
|
+
else
|
59
|
+
detailed_message = extract_detailed_error_message
|
60
|
+
detailed_message || default_error_message
|
61
|
+
end
|
62
|
+
elsif parsed_body["error"].is_a?(String)
|
63
|
+
parsed_body["error"]
|
64
|
+
else
|
65
|
+
default_error_message
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Extract a detailed error message from nested error structures
|
70
|
+
# @return [String, nil] Detailed error message if present
|
71
|
+
def extract_detailed_error_message
|
72
|
+
return nil unless parsed_body["error"].is_a?(Hash)
|
73
|
+
|
74
|
+
details = parsed_body["error"]["details"]
|
75
|
+
return nil unless details.is_a?(Array) && !details.empty?
|
76
|
+
|
77
|
+
# Try to extract messages from details
|
78
|
+
messages = details.map do |detail|
|
79
|
+
if detail.is_a?(Hash) && detail["@type"] && detail["@type"].include?("type.googleapis.com")
|
80
|
+
detail["detail"] || detail["description"] || detail["message"]
|
81
|
+
end
|
82
|
+
end.compact
|
83
|
+
|
84
|
+
messages.join(". ") unless messages.empty?
|
85
|
+
end
|
86
|
+
|
87
|
+
# Parses the JSON response body
|
88
|
+
# @return [Hash, nil] The parsed JSON or nil if parsing fails
|
89
|
+
def parsed_body
|
90
|
+
@parsed_body ||= begin
|
91
|
+
return nil if response.body.to_s.empty?
|
92
|
+
|
93
|
+
JSON.parse(response.body)
|
94
|
+
rescue JSON::ParserError
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Generate a default error message based on HTTP status
|
100
|
+
# @return [String] A default error message
|
101
|
+
def default_error_message
|
102
|
+
case response.status
|
103
|
+
when 400
|
104
|
+
"Bad Request: The server could not process the request"
|
105
|
+
when 401
|
106
|
+
"Unauthorized: Authentication is required or has failed"
|
107
|
+
when 403
|
108
|
+
"Forbidden: You don't have permission to access this resource"
|
109
|
+
when 404
|
110
|
+
"Not Found: The requested resource could not be found"
|
111
|
+
when 429
|
112
|
+
"Too Many Requests: Rate limit exceeded"
|
113
|
+
when 500..599
|
114
|
+
"Server Error: The server encountered an error (#{response.status})"
|
115
|
+
else
|
116
|
+
"Error: An unexpected error occurred (HTTP #{response.status})"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
# Base error class for all Geminize errors
|
5
|
+
class GeminizeError < StandardError
|
6
|
+
# @return [String] The error message
|
7
|
+
attr_reader :message
|
8
|
+
|
9
|
+
# @return [String, nil] The error code from the API response
|
10
|
+
attr_reader :code
|
11
|
+
|
12
|
+
# @return [Integer, nil] The HTTP status code
|
13
|
+
attr_reader :http_status
|
14
|
+
|
15
|
+
# Initialize a new error
|
16
|
+
# @param message [String] The error message
|
17
|
+
# @param code [String, nil] The error code from the API response
|
18
|
+
# @param http_status [Integer, nil] The HTTP status code
|
19
|
+
def initialize(message = nil, code = nil, http_status = nil)
|
20
|
+
@message = message || "An error occurred with the Geminize API"
|
21
|
+
@code = code
|
22
|
+
@http_status = http_status
|
23
|
+
super(@message)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Error raised when there's an authentication issue
|
28
|
+
class AuthenticationError < GeminizeError
|
29
|
+
def initialize(message = nil, code = nil, http_status = 401)
|
30
|
+
super(
|
31
|
+
message || "Authentication failed. Please check your API key.",
|
32
|
+
code,
|
33
|
+
http_status
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Error raised when rate limits are exceeded
|
39
|
+
class RateLimitError < GeminizeError
|
40
|
+
def initialize(message = nil, code = nil, http_status = 429)
|
41
|
+
super(
|
42
|
+
message || "Rate limit exceeded. Please retry after some time.",
|
43
|
+
code,
|
44
|
+
http_status
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Error raised for client-side errors (4xx)
|
50
|
+
class BadRequestError < GeminizeError
|
51
|
+
def initialize(message = nil, code = nil, http_status = 400)
|
52
|
+
super(
|
53
|
+
message || "Invalid request. Please check your parameters.",
|
54
|
+
code,
|
55
|
+
http_status
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Error raised for server-side errors (5xx)
|
61
|
+
class ServerError < GeminizeError
|
62
|
+
def initialize(message = nil, code = nil, http_status = 500)
|
63
|
+
super(
|
64
|
+
message || "The Gemini API encountered a server error.",
|
65
|
+
code,
|
66
|
+
http_status
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Error raised for network/request issues
|
72
|
+
class RequestError < GeminizeError
|
73
|
+
def initialize(message = nil, code = nil, http_status = nil)
|
74
|
+
super(
|
75
|
+
message || "There was an error making the request to the Gemini API.",
|
76
|
+
code,
|
77
|
+
http_status
|
78
|
+
)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Error raised when a resource is not found
|
83
|
+
class ResourceNotFoundError < BadRequestError
|
84
|
+
def initialize(message = nil, code = nil, http_status = 404)
|
85
|
+
super(
|
86
|
+
message || "The requested resource was not found.",
|
87
|
+
code,
|
88
|
+
http_status
|
89
|
+
)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Error raised when a requested model is invalid or not available
|
94
|
+
class InvalidModelError < BadRequestError
|
95
|
+
def initialize(message = nil, code = nil, http_status = 400)
|
96
|
+
super(
|
97
|
+
message || "The specified model is invalid or not available.",
|
98
|
+
code,
|
99
|
+
http_status
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Error raised for validation errors
|
105
|
+
class ValidationError < BadRequestError
|
106
|
+
def initialize(message = nil, code = nil, http_status = 400)
|
107
|
+
super(
|
108
|
+
message || "Validation failed. Please check your input parameters.",
|
109
|
+
code,
|
110
|
+
http_status
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Error raised when the content is blocked by the safety filters
|
116
|
+
class ContentBlockedError < BadRequestError
|
117
|
+
def initialize(message = nil, code = nil, http_status = 400)
|
118
|
+
super(
|
119
|
+
message || "Content blocked by safety filters.",
|
120
|
+
code,
|
121
|
+
http_status
|
122
|
+
)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Error raised when there are configuration issues
|
127
|
+
class ConfigurationError < GeminizeError
|
128
|
+
def initialize(message = nil, code = nil, http_status = nil)
|
129
|
+
super(
|
130
|
+
message || "Configuration error. Please check your Geminize configuration.",
|
131
|
+
code,
|
132
|
+
http_status
|
133
|
+
)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Error specific to streaming operations
|
138
|
+
class StreamingError < GeminizeError
|
139
|
+
def initialize(message = nil, code = nil, http_status = nil)
|
140
|
+
super(
|
141
|
+
message || "Streaming operation failed",
|
142
|
+
code || "STREAMING_ERROR",
|
143
|
+
http_status
|
144
|
+
)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Error during connection interrupted during streaming
|
149
|
+
class StreamingInterruptedError < StreamingError
|
150
|
+
def initialize(message = nil, code = nil, http_status = nil)
|
151
|
+
super(
|
152
|
+
message || "Streaming connection was interrupted",
|
153
|
+
code || "STREAMING_INTERRUPTED",
|
154
|
+
http_status
|
155
|
+
)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Error when the stream format is invalid
|
160
|
+
class InvalidStreamFormatError < StreamingError
|
161
|
+
def initialize(message = nil, code = nil, http_status = 400)
|
162
|
+
super(
|
163
|
+
message || "Invalid stream format or response structure",
|
164
|
+
code || "INVALID_STREAM_FORMAT",
|
165
|
+
http_status
|
166
|
+
)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Error for timeout during streaming
|
171
|
+
class StreamingTimeoutError < StreamingError
|
172
|
+
def initialize(message = nil, code = nil, http_status = 408)
|
173
|
+
super(
|
174
|
+
message || "Streaming operation timed out",
|
175
|
+
code || "STREAMING_TIMEOUT",
|
176
|
+
http_status
|
177
|
+
)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Error raised when a requested resource is not found (alias for ResourceNotFoundError)
|
182
|
+
class NotFoundError < ResourceNotFoundError
|
183
|
+
# Uses the same implementation as ResourceNotFoundError
|
184
|
+
end
|
185
|
+
end
|