geminize 1.1.0 → 1.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.
- checksums.yaml +4 -4
- data/.memory_bank/activeContext.md +25 -1
- data/.memory_bank/progress.md +26 -13
- data/.memory_bank/projectbrief.md +16 -0
- data/.memory_bank/tasks.md +57 -13
- data/CHANGELOG.md +22 -0
- data/README.md +181 -0
- data/examples/function_calling.rb +218 -0
- data/examples/safety_settings.rb +82 -0
- data/lib/geminize/models/content_request_extensions.rb +219 -0
- data/lib/geminize/models/content_request_safety.rb +123 -0
- data/lib/geminize/models/content_response_extensions.rb +120 -0
- data/lib/geminize/models/function_declaration.rb +112 -0
- data/lib/geminize/models/function_response.rb +70 -0
- data/lib/geminize/models/safety_setting.rb +102 -0
- data/lib/geminize/models/tool.rb +47 -0
- data/lib/geminize/models/tool_config.rb +52 -0
- data/lib/geminize/module_extensions.rb +228 -0
- data/lib/geminize/module_safety.rb +135 -0
- data/lib/geminize/version.rb +1 -1
- data/lib/geminize.rb +12 -0
- metadata +13 -1
@@ -0,0 +1,218 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "geminize"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
# Configure the API key
|
9
|
+
Geminize.configure do |config|
|
10
|
+
config.api_key = ENV["GEMINI_API_KEY"] # Make sure to set your API key in the environment
|
11
|
+
config.default_model = "gemini-1.5-pro-latest" # Use the latest model that supports function calling
|
12
|
+
end
|
13
|
+
|
14
|
+
# Define a weather function that can handle a location and unit
|
15
|
+
def get_weather(location, unit = "celsius")
|
16
|
+
puts "Getting weather for #{location} in #{unit}..."
|
17
|
+
|
18
|
+
# In a real implementation, this would call a weather API
|
19
|
+
# For this example, we'll return mock data
|
20
|
+
case location.downcase
|
21
|
+
when /new york/
|
22
|
+
{temperature: (unit == "celsius") ? 22 : 72, conditions: "Sunny", humidity: 45}
|
23
|
+
when /london/
|
24
|
+
{temperature: (unit == "celsius") ? 15 : 59, conditions: "Rainy", humidity: 80}
|
25
|
+
when /tokyo/
|
26
|
+
{temperature: (unit == "celsius") ? 26 : 79, conditions: "Partly Cloudy", humidity: 65}
|
27
|
+
else
|
28
|
+
{temperature: (unit == "celsius") ? 20 : 68, conditions: "Unknown", humidity: 50}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Enhanced weather function that can handle a batch of locations
|
33
|
+
def get_weather_batch(locations, unit = "celsius")
|
34
|
+
if locations.is_a?(Array)
|
35
|
+
results = {}
|
36
|
+
locations.each do |location|
|
37
|
+
results[location] = get_weather(location, unit)
|
38
|
+
end
|
39
|
+
results
|
40
|
+
else
|
41
|
+
{locations => get_weather(locations, unit)}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Define a function schema for get_weather
|
46
|
+
weather_function = {
|
47
|
+
name: "get_weather",
|
48
|
+
description: "Get the current weather in a location",
|
49
|
+
parameters: {
|
50
|
+
type: "object",
|
51
|
+
properties: {
|
52
|
+
location: {
|
53
|
+
type: "string",
|
54
|
+
description: "The city and state or country, e.g., 'New York, NY' or 'London, UK'"
|
55
|
+
},
|
56
|
+
unit: {
|
57
|
+
type: "string",
|
58
|
+
enum: ["celsius", "fahrenheit"],
|
59
|
+
description: "The unit of temperature"
|
60
|
+
}
|
61
|
+
},
|
62
|
+
required: ["location"]
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
# Define a batch weather function schema that can handle multiple locations
|
67
|
+
batch_weather_function = {
|
68
|
+
name: "get_weather_batch",
|
69
|
+
description: "Get the current weather for multiple locations at once",
|
70
|
+
parameters: {
|
71
|
+
type: "object",
|
72
|
+
properties: {
|
73
|
+
locations: {
|
74
|
+
type: "array",
|
75
|
+
items: {
|
76
|
+
type: "string"
|
77
|
+
},
|
78
|
+
description: "List of cities, e.g., ['New York, NY', 'London, UK', 'Tokyo, Japan']"
|
79
|
+
},
|
80
|
+
unit: {
|
81
|
+
type: "string",
|
82
|
+
enum: ["celsius", "fahrenheit"],
|
83
|
+
description: "The unit of temperature"
|
84
|
+
}
|
85
|
+
},
|
86
|
+
required: ["locations"]
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
puts "==========================================="
|
91
|
+
puts "= GEMINIZE FUNCTION CALLING & JSON EXAMPLES ="
|
92
|
+
puts "==========================================="
|
93
|
+
|
94
|
+
puts "\n=== FUNCTION CALLING EXAMPLE ==="
|
95
|
+
puts "Asking Gemini about the weather in New York..."
|
96
|
+
|
97
|
+
begin
|
98
|
+
# Generate a response with the function definition
|
99
|
+
response = Geminize.generate_with_functions(
|
100
|
+
"What's the weather like in New York?",
|
101
|
+
[weather_function],
|
102
|
+
nil,
|
103
|
+
{temperature: 0.2}
|
104
|
+
)
|
105
|
+
|
106
|
+
# Check if the model wants to call a function
|
107
|
+
if response.has_function_call?
|
108
|
+
function_call = response.function_call
|
109
|
+
puts "Model wants to call function: #{function_call.name}"
|
110
|
+
puts "With arguments: #{function_call.response.inspect}"
|
111
|
+
|
112
|
+
function_name = function_call.name
|
113
|
+
args = function_call.response
|
114
|
+
|
115
|
+
if function_name == "get_weather"
|
116
|
+
location = args["location"]
|
117
|
+
unit = args["unit"] || "celsius"
|
118
|
+
|
119
|
+
# Call our weather function
|
120
|
+
weather_data = get_weather(location, unit)
|
121
|
+
puts "Weather data for #{location}: #{weather_data.inspect}"
|
122
|
+
|
123
|
+
# Process the function result
|
124
|
+
final_response = Geminize.process_function_call(response) do |name, arguments|
|
125
|
+
get_weather(arguments["location"], arguments["unit"])
|
126
|
+
end
|
127
|
+
|
128
|
+
puts "\nFinal response from Gemini:"
|
129
|
+
puts final_response.text
|
130
|
+
else
|
131
|
+
puts "Unexpected function call: #{function_name}"
|
132
|
+
end
|
133
|
+
else
|
134
|
+
puts "Model did not request to call a function."
|
135
|
+
puts "Response: #{response.text}"
|
136
|
+
end
|
137
|
+
rescue => e
|
138
|
+
puts "Error during function calling: #{e.message}"
|
139
|
+
end
|
140
|
+
|
141
|
+
# Example of using JSON mode
|
142
|
+
puts "\n\n=== JSON MODE EXAMPLE ==="
|
143
|
+
puts "Using JSON mode to get weather data in structured format..."
|
144
|
+
|
145
|
+
begin
|
146
|
+
json_response = Geminize.generate_json(
|
147
|
+
"Get the current temperature and weather conditions for New York.",
|
148
|
+
nil,
|
149
|
+
{
|
150
|
+
system_instruction: "Return a JSON object with temperature in celsius and conditions."
|
151
|
+
}
|
152
|
+
)
|
153
|
+
|
154
|
+
if json_response.has_json_response?
|
155
|
+
puts "Structured JSON response:"
|
156
|
+
puts JSON.pretty_generate(json_response.json_response)
|
157
|
+
else
|
158
|
+
puts "Raw text response (not valid JSON):"
|
159
|
+
puts json_response.text
|
160
|
+
end
|
161
|
+
rescue => e
|
162
|
+
puts "Error during JSON mode: #{e.message}"
|
163
|
+
end
|
164
|
+
|
165
|
+
puts "\n\n=== BATCH FUNCTION CALL EXAMPLE ==="
|
166
|
+
puts "Using batch function to efficiently get weather for multiple cities at once..."
|
167
|
+
|
168
|
+
begin
|
169
|
+
# Use a batch function to get all cities at once (more efficient)
|
170
|
+
response = Geminize.generate_with_functions(
|
171
|
+
"I need weather information for New York, Tokyo, and London. Please get all this information in a single function call.",
|
172
|
+
[batch_weather_function],
|
173
|
+
nil,
|
174
|
+
{temperature: 0.2}
|
175
|
+
)
|
176
|
+
|
177
|
+
# Check if the model wants to call the batch function
|
178
|
+
if response.has_function_call?
|
179
|
+
function_call = response.function_call
|
180
|
+
puts "Model wants to call function: #{function_call.name}"
|
181
|
+
puts "With arguments: #{function_call.response.inspect}"
|
182
|
+
|
183
|
+
function_name = function_call.name
|
184
|
+
args = function_call.response
|
185
|
+
|
186
|
+
if function_name == "get_weather_batch"
|
187
|
+
locations = args["locations"]
|
188
|
+
unit = args["unit"] || "celsius"
|
189
|
+
|
190
|
+
# Get weather for all locations at once
|
191
|
+
weather_data = get_weather_batch(locations, unit)
|
192
|
+
puts "Weather data for multiple locations: #{weather_data.inspect}"
|
193
|
+
|
194
|
+
# Process the function result with a single API call
|
195
|
+
final_response = Geminize.process_function_call(response) do |name, arguments|
|
196
|
+
get_weather_batch(arguments["locations"], arguments["unit"])
|
197
|
+
end
|
198
|
+
|
199
|
+
puts "\nFinal response from Gemini:"
|
200
|
+
puts final_response.text
|
201
|
+
else
|
202
|
+
puts "Unexpected function call: #{function_name}"
|
203
|
+
end
|
204
|
+
else
|
205
|
+
puts "Model did not request to call a function."
|
206
|
+
puts "Response: #{response.text}"
|
207
|
+
end
|
208
|
+
rescue => e
|
209
|
+
puts "Error during batch function calling: #{e.message}"
|
210
|
+
puts "\nNOTE: If you hit a quota limit, try:"
|
211
|
+
puts "1. Using a paid API key with higher quotas"
|
212
|
+
puts "2. Reducing the number of examples you run"
|
213
|
+
puts "3. Adding delays between API calls"
|
214
|
+
end
|
215
|
+
|
216
|
+
puts "\n==========================================="
|
217
|
+
puts "= END OF EXAMPLES ="
|
218
|
+
puts "==========================================="
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "geminize"
|
6
|
+
|
7
|
+
# Configure the API key
|
8
|
+
Geminize.configure do |config|
|
9
|
+
config.api_key = ENV["GEMINI_API_KEY"] # Make sure to set your API key in the environment
|
10
|
+
config.default_model = "gemini-1.5-pro-latest" # Use the latest model
|
11
|
+
end
|
12
|
+
|
13
|
+
# A prompt that might trigger safety filters
|
14
|
+
POTENTIALLY_SENSITIVE_PROMPT = "Describe a violent conflict scene from a movie"
|
15
|
+
|
16
|
+
puts "1. Generating with default safety settings:"
|
17
|
+
begin
|
18
|
+
response = Geminize.generate_text(POTENTIALLY_SENSITIVE_PROMPT, nil, temperature: 0.2)
|
19
|
+
puts "Default response:\n#{response.text}\n\n"
|
20
|
+
rescue Geminize::GeminizeError => e
|
21
|
+
puts "Error with default settings: #{e.message}\n\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "2. Generating with maximum safety (blocking most potentially harmful content):"
|
25
|
+
begin
|
26
|
+
response = Geminize.generate_text_safe(POTENTIALLY_SENSITIVE_PROMPT, nil, temperature: 0.2)
|
27
|
+
puts "Maximum safety response:\n#{response.text}\n\n"
|
28
|
+
rescue Geminize::GeminizeError => e
|
29
|
+
puts "Error with maximum safety: #{e.message}\n\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
puts "3. Generating with minimum safety (blocking only high-risk content):"
|
33
|
+
begin
|
34
|
+
response = Geminize.generate_text_permissive(POTENTIALLY_SENSITIVE_PROMPT, nil, temperature: 0.2)
|
35
|
+
puts "Minimum safety response:\n#{response.text}\n\n"
|
36
|
+
rescue Geminize::GeminizeError => e
|
37
|
+
puts "Error with minimum safety: #{e.message}\n\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
puts "4. Generating with custom safety settings:"
|
41
|
+
begin
|
42
|
+
# Custom safety settings:
|
43
|
+
# - Block medium and above for dangerous content
|
44
|
+
# - Block low and above for hate speech
|
45
|
+
# - Block only high for sexually explicit content
|
46
|
+
# - No blocks for harassment
|
47
|
+
custom_safety_settings = [
|
48
|
+
{category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_MEDIUM_AND_ABOVE"},
|
49
|
+
{category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_LOW_AND_ABOVE"},
|
50
|
+
{category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_ONLY_HIGH"},
|
51
|
+
{category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE"}
|
52
|
+
]
|
53
|
+
|
54
|
+
response = Geminize.generate_with_safety_settings(
|
55
|
+
POTENTIALLY_SENSITIVE_PROMPT,
|
56
|
+
custom_safety_settings,
|
57
|
+
nil,
|
58
|
+
temperature: 0.2
|
59
|
+
)
|
60
|
+
puts "Custom safety response:\n#{response.text}\n\n"
|
61
|
+
rescue Geminize::GeminizeError => e
|
62
|
+
puts "Error with custom safety: #{e.message}\n\n"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Demonstrate direct usage of safety settings in a ContentRequest
|
66
|
+
puts "5. Using safety settings directly in a ContentRequest:"
|
67
|
+
begin
|
68
|
+
generator = Geminize::TextGeneration.new
|
69
|
+
content_request = Geminize::Models::ContentRequest.new(
|
70
|
+
POTENTIALLY_SENSITIVE_PROMPT,
|
71
|
+
nil,
|
72
|
+
temperature: 0.2
|
73
|
+
)
|
74
|
+
|
75
|
+
# Add specific safety settings
|
76
|
+
content_request.add_safety_setting("HARM_CATEGORY_DANGEROUS_CONTENT", "BLOCK_MEDIUM_AND_ABOVE")
|
77
|
+
|
78
|
+
response = generator.generate(content_request)
|
79
|
+
puts "ContentRequest safety response:\n#{response.text}\n\n"
|
80
|
+
rescue Geminize::GeminizeError => e
|
81
|
+
puts "Error with ContentRequest safety: #{e.message}\n\n"
|
82
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Extends ContentRequest with function calling and JSON mode support
|
6
|
+
class ContentRequest
|
7
|
+
# Additional attributes for function calling and JSON mode
|
8
|
+
|
9
|
+
# @return [Array<Geminize::Models::Tool>] The tools for this request
|
10
|
+
attr_reader :tools
|
11
|
+
|
12
|
+
# @return [Geminize::Models::ToolConfig, nil] The tool configuration
|
13
|
+
attr_reader :tool_config
|
14
|
+
|
15
|
+
# @return [String, nil] The MIME type for the response format
|
16
|
+
attr_accessor :response_mime_type
|
17
|
+
|
18
|
+
# Add a function to the request
|
19
|
+
# @param name [String] The name of the function
|
20
|
+
# @param description [String] A description of what the function does
|
21
|
+
# @param parameters [Hash] JSON schema for function parameters
|
22
|
+
# @return [self] The request object for chaining
|
23
|
+
# @raise [Geminize::ValidationError] If the function is invalid
|
24
|
+
def add_function(name, description, parameters)
|
25
|
+
@tools ||= []
|
26
|
+
|
27
|
+
function_declaration = FunctionDeclaration.new(name, description, parameters)
|
28
|
+
tool = Tool.new(function_declaration)
|
29
|
+
|
30
|
+
@tools << tool
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set the tool config for function execution
|
35
|
+
# @param execution_mode [String] The execution mode for functions ("AUTO", "MANUAL", or "NONE")
|
36
|
+
# @return [self] The request object for chaining
|
37
|
+
# @raise [Geminize::ValidationError] If the tool config is invalid
|
38
|
+
def set_tool_config(execution_mode = "AUTO")
|
39
|
+
@tool_config = ToolConfig.new(execution_mode)
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
# Enable JSON mode for structured output
|
44
|
+
# @return [self] The request object for chaining
|
45
|
+
def enable_json_mode
|
46
|
+
@response_mime_type = "application/json"
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
# Disable JSON mode and return to regular text output
|
51
|
+
# @return [self] The request object for chaining
|
52
|
+
def disable_json_mode
|
53
|
+
@response_mime_type = nil
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
# Override the to_hash method to include additional function calling features
|
58
|
+
# @return [Hash] The request as a hash
|
59
|
+
def to_hash
|
60
|
+
# First get the base implementation's hash by calling the standard method
|
61
|
+
# Use the implementation from ContentRequest directly
|
62
|
+
request = {
|
63
|
+
contents: [
|
64
|
+
{
|
65
|
+
parts: @content_parts.map do |part|
|
66
|
+
if part[:type] == "text"
|
67
|
+
{text: part[:text]}
|
68
|
+
elsif part[:type] == "image"
|
69
|
+
{
|
70
|
+
inlineData: {
|
71
|
+
mimeType: part[:mime_type],
|
72
|
+
data: part[:data]
|
73
|
+
}
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end.compact
|
77
|
+
}
|
78
|
+
]
|
79
|
+
}
|
80
|
+
|
81
|
+
# Add generation config
|
82
|
+
if @temperature || @max_tokens || @top_p || @top_k || @stop_sequences
|
83
|
+
request[:generationConfig] = {}
|
84
|
+
request[:generationConfig][:temperature] = @temperature if @temperature
|
85
|
+
request[:generationConfig][:maxOutputTokens] = @max_tokens if @max_tokens
|
86
|
+
request[:generationConfig][:topP] = @top_p if @top_p
|
87
|
+
request[:generationConfig][:topK] = @top_k if @top_k
|
88
|
+
request[:generationConfig][:stopSequences] = @stop_sequences if @stop_sequences
|
89
|
+
end
|
90
|
+
|
91
|
+
# Add system instruction
|
92
|
+
if @system_instruction
|
93
|
+
request[:systemInstruction] = {
|
94
|
+
parts: [
|
95
|
+
{
|
96
|
+
text: @system_instruction
|
97
|
+
}
|
98
|
+
]
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
# Add tools if present
|
103
|
+
if @tools && !@tools.empty?
|
104
|
+
request[:tools] = @tools.map(&:to_hash)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Add tool config if present
|
108
|
+
if @tool_config
|
109
|
+
request[:toolConfig] = @tool_config.to_hash
|
110
|
+
end
|
111
|
+
|
112
|
+
# Add response format if JSON mode is enabled
|
113
|
+
if @response_mime_type
|
114
|
+
request[:generationConfig] ||= {}
|
115
|
+
request[:generationConfig][:responseSchema] = {
|
116
|
+
type: "object",
|
117
|
+
properties: {
|
118
|
+
# Add a sample property to satisfy the API requirement
|
119
|
+
# This is a generic structure that will be overridden by the model's understanding
|
120
|
+
# of what properties to include based on the prompt
|
121
|
+
result: {
|
122
|
+
type: "array",
|
123
|
+
items: {
|
124
|
+
type: "object",
|
125
|
+
properties: {
|
126
|
+
name: {type: "string"},
|
127
|
+
value: {type: "string"}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
request[:generationConfig][:responseMimeType] = @response_mime_type
|
134
|
+
end
|
135
|
+
|
136
|
+
request
|
137
|
+
end
|
138
|
+
|
139
|
+
# Original validate! method includes validation for tools and JSON mode
|
140
|
+
alias_method :original_validate!, :validate!
|
141
|
+
|
142
|
+
def validate!
|
143
|
+
# Don't call super, instead call the original specific validations directly
|
144
|
+
validate_prompt!
|
145
|
+
validate_system_instruction! if @system_instruction
|
146
|
+
validate_temperature! if @temperature
|
147
|
+
validate_max_tokens! if @max_tokens
|
148
|
+
validate_top_p! if @top_p
|
149
|
+
validate_top_k! if @top_k
|
150
|
+
validate_stop_sequences! if @stop_sequences
|
151
|
+
validate_content_parts!
|
152
|
+
|
153
|
+
# Then validate our extensions
|
154
|
+
validate_tools!
|
155
|
+
validate_tool_config!
|
156
|
+
validate_response_mime_type!
|
157
|
+
true
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
# Validate the tools
|
163
|
+
# @raise [Geminize::ValidationError] If the tools are invalid
|
164
|
+
def validate_tools!
|
165
|
+
return if @tools.nil? || @tools.empty?
|
166
|
+
|
167
|
+
unless @tools.is_a?(Array)
|
168
|
+
raise Geminize::ValidationError.new(
|
169
|
+
"Tools must be an array, got #{@tools.class}",
|
170
|
+
"INVALID_ARGUMENT"
|
171
|
+
)
|
172
|
+
end
|
173
|
+
|
174
|
+
@tools.each_with_index do |tool, index|
|
175
|
+
unless tool.is_a?(Tool)
|
176
|
+
raise Geminize::ValidationError.new(
|
177
|
+
"Tool at index #{index} must be a Tool, got #{tool.class}",
|
178
|
+
"INVALID_ARGUMENT"
|
179
|
+
)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Validate the tool config
|
185
|
+
# @raise [Geminize::ValidationError] If the tool config is invalid
|
186
|
+
def validate_tool_config!
|
187
|
+
return if @tool_config.nil?
|
188
|
+
|
189
|
+
unless @tool_config.is_a?(ToolConfig)
|
190
|
+
raise Geminize::ValidationError.new(
|
191
|
+
"Tool config must be a ToolConfig, got #{@tool_config.class}",
|
192
|
+
"INVALID_ARGUMENT"
|
193
|
+
)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Validate the response MIME type
|
198
|
+
# @raise [Geminize::ValidationError] If the response MIME type is invalid
|
199
|
+
def validate_response_mime_type!
|
200
|
+
return if @response_mime_type.nil?
|
201
|
+
|
202
|
+
unless @response_mime_type.is_a?(String)
|
203
|
+
raise Geminize::ValidationError.new(
|
204
|
+
"Response MIME type must be a string, got #{@response_mime_type.class}",
|
205
|
+
"INVALID_ARGUMENT"
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
209
|
+
# For now, only allow JSON
|
210
|
+
unless @response_mime_type == "application/json"
|
211
|
+
raise Geminize::ValidationError.new(
|
212
|
+
"Response MIME type must be 'application/json', got #{@response_mime_type}",
|
213
|
+
"INVALID_ARGUMENT"
|
214
|
+
)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Extends ContentRequest with safety settings support
|
6
|
+
class ContentRequest
|
7
|
+
# @return [Array<Geminize::Models::SafetySetting>] The safety settings for this request
|
8
|
+
attr_reader :safety_settings
|
9
|
+
|
10
|
+
# Add a safety setting to the request
|
11
|
+
# @param category [String] The harm category this setting applies to
|
12
|
+
# @param threshold [String] The threshold level for filtering
|
13
|
+
# @return [self] The request object for chaining
|
14
|
+
# @raise [Geminize::ValidationError] If the safety setting is invalid
|
15
|
+
def add_safety_setting(category, threshold)
|
16
|
+
@safety_settings ||= []
|
17
|
+
|
18
|
+
safety_setting = SafetySetting.new(category, threshold)
|
19
|
+
@safety_settings << safety_setting
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# Set default safety settings for all harm categories
|
25
|
+
# @param threshold [String] The threshold level to apply to all categories
|
26
|
+
# @return [self] The request object for chaining
|
27
|
+
# @raise [Geminize::ValidationError] If the threshold is invalid
|
28
|
+
def set_default_safety_settings(threshold)
|
29
|
+
@safety_settings = []
|
30
|
+
|
31
|
+
SafetySetting::HARM_CATEGORIES.each do |category|
|
32
|
+
add_safety_setting(category, threshold)
|
33
|
+
end
|
34
|
+
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
# Block all harmful content (most conservative setting)
|
39
|
+
# @return [self] The request object for chaining
|
40
|
+
def block_all_harmful_content
|
41
|
+
set_default_safety_settings("BLOCK_LOW_AND_ABOVE")
|
42
|
+
end
|
43
|
+
|
44
|
+
# Block only high-risk content (least conservative setting)
|
45
|
+
# @return [self] The request object for chaining
|
46
|
+
def block_only_high_risk_content
|
47
|
+
set_default_safety_settings("BLOCK_ONLY_HIGH")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Remove all safety settings (use with caution)
|
51
|
+
# @return [self] The request object for chaining
|
52
|
+
def remove_safety_settings
|
53
|
+
@safety_settings = []
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get the base to_hash method - this will use the one defined in content_request_extensions.rb if available
|
58
|
+
alias_method :safety_original_to_hash, :to_hash unless method_defined?(:safety_original_to_hash)
|
59
|
+
|
60
|
+
# Override the to_hash method to include safety settings
|
61
|
+
# @return [Hash] The request as a hash
|
62
|
+
def to_hash
|
63
|
+
# Get the base hash (will include tools if that extension is loaded)
|
64
|
+
request = defined?(original_to_hash) ? original_to_hash : safety_original_to_hash
|
65
|
+
|
66
|
+
# Add safety settings if present
|
67
|
+
if @safety_settings && !@safety_settings.empty?
|
68
|
+
request[:safetySettings] = @safety_settings.map(&:to_hash)
|
69
|
+
end
|
70
|
+
|
71
|
+
request
|
72
|
+
end
|
73
|
+
|
74
|
+
# Validate method for safety settings - should only be called if not overridden by content_request_extensions.rb
|
75
|
+
# If that file's validate! is called, it should also call validate_safety_settings!
|
76
|
+
def validate!
|
77
|
+
# Don't call super, instead call the necessary validations directly
|
78
|
+
# Check if original_validate! is defined from the extensions
|
79
|
+
if defined?(original_validate!)
|
80
|
+
original_validate!
|
81
|
+
else
|
82
|
+
# Call the original internal validation methods
|
83
|
+
validate_prompt!
|
84
|
+
validate_system_instruction! if @system_instruction
|
85
|
+
validate_temperature! if @temperature
|
86
|
+
validate_max_tokens! if @max_tokens
|
87
|
+
validate_top_p! if @top_p
|
88
|
+
validate_top_k! if @top_k
|
89
|
+
validate_stop_sequences! if @stop_sequences
|
90
|
+
validate_content_parts!
|
91
|
+
end
|
92
|
+
|
93
|
+
# Add our safety validation
|
94
|
+
validate_safety_settings!
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Validate the safety settings
|
101
|
+
# @raise [Geminize::ValidationError] If the safety settings are invalid
|
102
|
+
def validate_safety_settings!
|
103
|
+
return if @safety_settings.nil? || @safety_settings.empty?
|
104
|
+
|
105
|
+
unless @safety_settings.is_a?(Array)
|
106
|
+
raise Geminize::ValidationError.new(
|
107
|
+
"Safety settings must be an array, got #{@safety_settings.class}",
|
108
|
+
"INVALID_ARGUMENT"
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
@safety_settings.each_with_index do |setting, index|
|
113
|
+
unless setting.is_a?(SafetySetting)
|
114
|
+
raise Geminize::ValidationError.new(
|
115
|
+
"Safety setting at index #{index} must be a SafetySetting, got #{setting.class}",
|
116
|
+
"INVALID_ARGUMENT"
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|