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.
@@ -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