geminize 1.1.0 → 1.3.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 +35 -1
- data/.memory_bank/progress.md +27 -13
- data/.memory_bank/projectbrief.md +16 -0
- data/.memory_bank/tasks.md +59 -13
- data/CHANGELOG.md +36 -0
- data/README.md +225 -0
- data/examples/code_execution.rb +126 -0
- data/examples/function_calling.rb +218 -0
- data/examples/safety_settings.rb +82 -0
- data/lib/geminize/models/code_execution/code_execution_result.rb +72 -0
- data/lib/geminize/models/code_execution/executable_code.rb +72 -0
- data/lib/geminize/models/content_request_extensions.rb +227 -0
- data/lib/geminize/models/content_request_safety.rb +123 -0
- data/lib/geminize/models/content_response_extensions.rb +129 -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 +65 -0
- data/lib/geminize/models/tool_config.rb +52 -0
- data/lib/geminize/module_extensions.rb +283 -0
- data/lib/geminize/module_safety.rb +135 -0
- data/lib/geminize/version.rb +1 -1
- data/lib/geminize.rb +14 -0
- metadata +16 -1
@@ -0,0 +1,227 @@
|
|
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
|
+
# Enable code execution for the request
|
35
|
+
# @return [self] The request object for chaining
|
36
|
+
def enable_code_execution
|
37
|
+
@tools ||= []
|
38
|
+
@tools << Tool.new(nil, true)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Set the tool config for function execution
|
43
|
+
# @param execution_mode [String] The execution mode for functions ("AUTO", "MANUAL", or "NONE")
|
44
|
+
# @return [self] The request object for chaining
|
45
|
+
# @raise [Geminize::ValidationError] If the tool config is invalid
|
46
|
+
def set_tool_config(execution_mode = "AUTO")
|
47
|
+
@tool_config = ToolConfig.new(execution_mode)
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Enable JSON mode for structured output
|
52
|
+
# @return [self] The request object for chaining
|
53
|
+
def enable_json_mode
|
54
|
+
@response_mime_type = "application/json"
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# Disable JSON mode and return to regular text output
|
59
|
+
# @return [self] The request object for chaining
|
60
|
+
def disable_json_mode
|
61
|
+
@response_mime_type = nil
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Override the to_hash method to include additional function calling features
|
66
|
+
# @return [Hash] The request as a hash
|
67
|
+
def to_hash
|
68
|
+
# First get the base implementation's hash by calling the standard method
|
69
|
+
# Use the implementation from ContentRequest directly
|
70
|
+
request = {
|
71
|
+
contents: [
|
72
|
+
{
|
73
|
+
parts: @content_parts.map do |part|
|
74
|
+
if part[:type] == "text"
|
75
|
+
{text: part[:text]}
|
76
|
+
elsif part[:type] == "image"
|
77
|
+
{
|
78
|
+
inlineData: {
|
79
|
+
mimeType: part[:mime_type],
|
80
|
+
data: part[:data]
|
81
|
+
}
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end.compact
|
85
|
+
}
|
86
|
+
]
|
87
|
+
}
|
88
|
+
|
89
|
+
# Add generation config
|
90
|
+
if @temperature || @max_tokens || @top_p || @top_k || @stop_sequences
|
91
|
+
request[:generationConfig] = {}
|
92
|
+
request[:generationConfig][:temperature] = @temperature if @temperature
|
93
|
+
request[:generationConfig][:maxOutputTokens] = @max_tokens if @max_tokens
|
94
|
+
request[:generationConfig][:topP] = @top_p if @top_p
|
95
|
+
request[:generationConfig][:topK] = @top_k if @top_k
|
96
|
+
request[:generationConfig][:stopSequences] = @stop_sequences if @stop_sequences
|
97
|
+
end
|
98
|
+
|
99
|
+
# Add system instruction
|
100
|
+
if @system_instruction
|
101
|
+
request[:systemInstruction] = {
|
102
|
+
parts: [
|
103
|
+
{
|
104
|
+
text: @system_instruction
|
105
|
+
}
|
106
|
+
]
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
# Add tools if present
|
111
|
+
if @tools && !@tools.empty?
|
112
|
+
request[:tools] = @tools.map(&:to_hash)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Add tool config if present
|
116
|
+
if @tool_config
|
117
|
+
request[:toolConfig] = @tool_config.to_hash
|
118
|
+
end
|
119
|
+
|
120
|
+
# Add response format if JSON mode is enabled
|
121
|
+
if @response_mime_type
|
122
|
+
request[:generationConfig] ||= {}
|
123
|
+
request[:generationConfig][:responseSchema] = {
|
124
|
+
type: "object",
|
125
|
+
properties: {
|
126
|
+
# Add a sample property to satisfy the API requirement
|
127
|
+
# This is a generic structure that will be overridden by the model's understanding
|
128
|
+
# of what properties to include based on the prompt
|
129
|
+
result: {
|
130
|
+
type: "array",
|
131
|
+
items: {
|
132
|
+
type: "object",
|
133
|
+
properties: {
|
134
|
+
name: {type: "string"},
|
135
|
+
value: {type: "string"}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
}
|
140
|
+
}
|
141
|
+
request[:generationConfig][:responseMimeType] = @response_mime_type
|
142
|
+
end
|
143
|
+
|
144
|
+
request
|
145
|
+
end
|
146
|
+
|
147
|
+
# Original validate! method includes validation for tools and JSON mode
|
148
|
+
alias_method :original_validate!, :validate!
|
149
|
+
|
150
|
+
def validate!
|
151
|
+
# Don't call super, instead call the original specific validations directly
|
152
|
+
validate_prompt!
|
153
|
+
validate_system_instruction! if @system_instruction
|
154
|
+
validate_temperature! if @temperature
|
155
|
+
validate_max_tokens! if @max_tokens
|
156
|
+
validate_top_p! if @top_p
|
157
|
+
validate_top_k! if @top_k
|
158
|
+
validate_stop_sequences! if @stop_sequences
|
159
|
+
validate_content_parts!
|
160
|
+
|
161
|
+
# Then validate our extensions
|
162
|
+
validate_tools!
|
163
|
+
validate_tool_config!
|
164
|
+
validate_response_mime_type!
|
165
|
+
true
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
# Validate the tools
|
171
|
+
# @raise [Geminize::ValidationError] If the tools are invalid
|
172
|
+
def validate_tools!
|
173
|
+
return if @tools.nil? || @tools.empty?
|
174
|
+
|
175
|
+
unless @tools.is_a?(Array)
|
176
|
+
raise Geminize::ValidationError.new(
|
177
|
+
"Tools must be an array, got #{@tools.class}",
|
178
|
+
"INVALID_ARGUMENT"
|
179
|
+
)
|
180
|
+
end
|
181
|
+
|
182
|
+
@tools.each_with_index do |tool, index|
|
183
|
+
unless tool.is_a?(Tool)
|
184
|
+
raise Geminize::ValidationError.new(
|
185
|
+
"Tool at index #{index} must be a Tool, got #{tool.class}",
|
186
|
+
"INVALID_ARGUMENT"
|
187
|
+
)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Validate the tool config
|
193
|
+
# @raise [Geminize::ValidationError] If the tool config is invalid
|
194
|
+
def validate_tool_config!
|
195
|
+
return if @tool_config.nil?
|
196
|
+
|
197
|
+
unless @tool_config.is_a?(ToolConfig)
|
198
|
+
raise Geminize::ValidationError.new(
|
199
|
+
"Tool config must be a ToolConfig, got #{@tool_config.class}",
|
200
|
+
"INVALID_ARGUMENT"
|
201
|
+
)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Validate the response MIME type
|
206
|
+
# @raise [Geminize::ValidationError] If the response MIME type is invalid
|
207
|
+
def validate_response_mime_type!
|
208
|
+
return if @response_mime_type.nil?
|
209
|
+
|
210
|
+
unless @response_mime_type.is_a?(String)
|
211
|
+
raise Geminize::ValidationError.new(
|
212
|
+
"Response MIME type must be a string, got #{@response_mime_type.class}",
|
213
|
+
"INVALID_ARGUMENT"
|
214
|
+
)
|
215
|
+
end
|
216
|
+
|
217
|
+
# For now, only allow JSON
|
218
|
+
unless @response_mime_type == "application/json"
|
219
|
+
raise Geminize::ValidationError.new(
|
220
|
+
"Response MIME type must be 'application/json', got #{@response_mime_type}",
|
221
|
+
"INVALID_ARGUMENT"
|
222
|
+
)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
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
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Extends ContentResponse with function calling capabilities
|
6
|
+
class ContentResponse
|
7
|
+
# @return [Geminize::Models::FunctionResponse, nil] The function call in the response, if any
|
8
|
+
attr_reader :function_call
|
9
|
+
|
10
|
+
# @return [String, nil] The raw JSON response content, if this is a JSON response
|
11
|
+
attr_reader :json_response
|
12
|
+
|
13
|
+
# @return [Geminize::Models::CodeExecution::ExecutableCode, nil] The executable code in the response, if any
|
14
|
+
attr_reader :executable_code
|
15
|
+
|
16
|
+
# @return [Geminize::Models::CodeExecution::CodeExecutionResult, nil] The code execution result in the response, if any
|
17
|
+
attr_reader :code_execution_result
|
18
|
+
|
19
|
+
# Store the response data for extensions
|
20
|
+
alias_method :original_initialize, :initialize
|
21
|
+
def initialize(response_data)
|
22
|
+
@response_data = response_data
|
23
|
+
original_initialize(response_data)
|
24
|
+
parse_function_call
|
25
|
+
parse_json_response
|
26
|
+
parse_code_execution
|
27
|
+
end
|
28
|
+
|
29
|
+
# Determine if the response contains a function call
|
30
|
+
# @return [Boolean] true if the response contains a function call
|
31
|
+
def has_function_call?
|
32
|
+
!@function_call.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Determine if the response contains a JSON response
|
36
|
+
# @return [Boolean] true if the response contains a JSON response
|
37
|
+
def has_json_response?
|
38
|
+
!@json_response.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Determine if the response contains executable code
|
42
|
+
# @return [Boolean] true if the response contains executable code
|
43
|
+
def has_executable_code?
|
44
|
+
!@executable_code.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Determine if the response contains a code execution result
|
48
|
+
# @return [Boolean] true if the response contains a code execution result
|
49
|
+
def has_code_execution_result?
|
50
|
+
!@code_execution_result.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Parse the function call from the response
|
56
|
+
def parse_function_call
|
57
|
+
candidates = @response_data.dig("candidates") || []
|
58
|
+
return if candidates.empty?
|
59
|
+
|
60
|
+
content = candidates[0].dig("content") || {}
|
61
|
+
parts = content.dig("parts") || []
|
62
|
+
function_call_part = parts.find { |part| part.dig("functionCall") }
|
63
|
+
|
64
|
+
if function_call_part
|
65
|
+
function_call_data = function_call_part["functionCall"]
|
66
|
+
function_name = function_call_data["name"]
|
67
|
+
function_args = function_call_data["args"] || {}
|
68
|
+
|
69
|
+
@function_call = FunctionResponse.new(function_name, function_args)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Parse JSON response if available
|
74
|
+
def parse_json_response
|
75
|
+
# First try to check if it's explicitly returned as JSON
|
76
|
+
if @response_data.dig("candidates", 0, "content", "parts", 0, "text")
|
77
|
+
text = @response_data.dig("candidates", 0, "content", "parts", 0, "text")
|
78
|
+
begin
|
79
|
+
@json_response = JSON.parse(text)
|
80
|
+
return
|
81
|
+
rescue JSON::ParserError
|
82
|
+
# Not valid JSON, continue checking other methods
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Next check if it's returned in structured format
|
87
|
+
candidates = @response_data.dig("candidates") || []
|
88
|
+
return if candidates.empty?
|
89
|
+
|
90
|
+
content = candidates[0].dig("content") || {}
|
91
|
+
parts = content.dig("parts") || []
|
92
|
+
json_part = parts.find { |part| part.key?("structuredValue") }
|
93
|
+
|
94
|
+
if json_part && json_part["structuredValue"]
|
95
|
+
@json_response = json_part["structuredValue"]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Parse code execution data from the response
|
100
|
+
def parse_code_execution
|
101
|
+
candidates = @response_data.dig("candidates") || []
|
102
|
+
return if candidates.empty?
|
103
|
+
|
104
|
+
content = candidates[0].dig("content") || {}
|
105
|
+
parts = content.dig("parts") || []
|
106
|
+
|
107
|
+
# Find executable code
|
108
|
+
executable_code_part = parts.find { |part| part.dig("executableCode") }
|
109
|
+
if executable_code_part && executable_code_part["executableCode"]
|
110
|
+
code_data = executable_code_part["executableCode"]
|
111
|
+
language = code_data["language"] || "PYTHON"
|
112
|
+
code = code_data["code"] || ""
|
113
|
+
|
114
|
+
@executable_code = Geminize::Models::CodeExecution::ExecutableCode.new(language, code)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Find code execution result
|
118
|
+
result_part = parts.find { |part| part.dig("codeExecutionResult") }
|
119
|
+
if result_part && result_part["codeExecutionResult"]
|
120
|
+
result_data = result_part["codeExecutionResult"]
|
121
|
+
outcome = result_data["outcome"] || "OUTCOME_OK"
|
122
|
+
output = result_data["output"] || ""
|
123
|
+
|
124
|
+
@code_execution_result = Geminize::Models::CodeExecution::CodeExecutionResult.new(outcome, output)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Represents a function declaration for function calling in Gemini API
|
6
|
+
class FunctionDeclaration
|
7
|
+
# @return [String] Name of the function
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
# @return [String] Description of what the function does
|
11
|
+
attr_reader :description
|
12
|
+
|
13
|
+
# @return [Hash] JSON schema for function parameters
|
14
|
+
attr_reader :parameters
|
15
|
+
|
16
|
+
# Initialize a new function declaration
|
17
|
+
# @param name [String] The name of the function
|
18
|
+
# @param description [String] A description of what the function does
|
19
|
+
# @param parameters [Hash] JSON schema for function parameters
|
20
|
+
# @raise [Geminize::ValidationError] If the function declaration is invalid
|
21
|
+
def initialize(name, description, parameters)
|
22
|
+
@name = name
|
23
|
+
@description = description
|
24
|
+
@parameters = parameters
|
25
|
+
validate!
|
26
|
+
end
|
27
|
+
|
28
|
+
# Validate the function declaration
|
29
|
+
# @raise [Geminize::ValidationError] If the function declaration is invalid
|
30
|
+
# @return [Boolean] true if validation passes
|
31
|
+
def validate!
|
32
|
+
validate_name!
|
33
|
+
validate_description!
|
34
|
+
validate_parameters!
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convert the function declaration to a hash for API requests
|
39
|
+
# @return [Hash] The function declaration as a hash
|
40
|
+
def to_hash
|
41
|
+
{
|
42
|
+
name: @name,
|
43
|
+
description: @description,
|
44
|
+
parameters: @parameters
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Alias for to_hash
|
49
|
+
# @return [Hash] The function declaration as a hash
|
50
|
+
def to_h
|
51
|
+
to_hash
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Validate the function name
|
57
|
+
# @raise [Geminize::ValidationError] If the name is invalid
|
58
|
+
def validate_name!
|
59
|
+
unless @name.is_a?(String)
|
60
|
+
raise Geminize::ValidationError.new(
|
61
|
+
"Function name must be a string, got #{@name.class}",
|
62
|
+
"INVALID_ARGUMENT"
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
if @name.empty?
|
67
|
+
raise Geminize::ValidationError.new(
|
68
|
+
"Function name cannot be empty",
|
69
|
+
"INVALID_ARGUMENT"
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Validate the function description
|
75
|
+
# @raise [Geminize::ValidationError] If the description is invalid
|
76
|
+
def validate_description!
|
77
|
+
unless @description.is_a?(String)
|
78
|
+
raise Geminize::ValidationError.new(
|
79
|
+
"Function description must be a string, got #{@description.class}",
|
80
|
+
"INVALID_ARGUMENT"
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
if @description.empty?
|
85
|
+
raise Geminize::ValidationError.new(
|
86
|
+
"Function description cannot be empty",
|
87
|
+
"INVALID_ARGUMENT"
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Validate the function parameters
|
93
|
+
# @raise [Geminize::ValidationError] If the parameters are invalid
|
94
|
+
def validate_parameters!
|
95
|
+
unless @parameters.is_a?(Hash)
|
96
|
+
raise Geminize::ValidationError.new(
|
97
|
+
"Function parameters must be a hash, got #{@parameters.class}",
|
98
|
+
"INVALID_ARGUMENT"
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Validate that the parameters follow JSON Schema format
|
103
|
+
unless @parameters.key?(:type) || @parameters.key?("type")
|
104
|
+
raise Geminize::ValidationError.new(
|
105
|
+
"Function parameters must include a 'type' field",
|
106
|
+
"INVALID_ARGUMENT"
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Represents a function response from Gemini API
|
6
|
+
class FunctionResponse
|
7
|
+
# @return [String] The name of the function that was called
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
# @return [Hash, Array, String, Numeric, Boolean, nil] The response from the function
|
11
|
+
attr_reader :response
|
12
|
+
|
13
|
+
# Initialize a new function response
|
14
|
+
# @param name [String] The name of the function that was called
|
15
|
+
# @param response [Hash, Array, String, Numeric, Boolean, nil] The response from the function
|
16
|
+
def initialize(name, response)
|
17
|
+
@name = name
|
18
|
+
@response = response
|
19
|
+
validate!
|
20
|
+
end
|
21
|
+
|
22
|
+
# Validate the function response
|
23
|
+
# @raise [Geminize::ValidationError] If the function response is invalid
|
24
|
+
# @return [Boolean] true if validation passes
|
25
|
+
def validate!
|
26
|
+
if @name.nil? || @name.empty?
|
27
|
+
raise Geminize::ValidationError.new(
|
28
|
+
"Function name cannot be empty",
|
29
|
+
"INVALID_ARGUMENT"
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a FunctionResponse from a hash
|
37
|
+
# @param hash [Hash] The hash representation of a function response
|
38
|
+
# @return [Geminize::Models::FunctionResponse] The function response
|
39
|
+
# @raise [Geminize::ValidationError] If the hash is invalid
|
40
|
+
def self.from_hash(hash)
|
41
|
+
unless hash.is_a?(Hash)
|
42
|
+
raise Geminize::ValidationError.new(
|
43
|
+
"Expected a Hash, got #{hash.class}",
|
44
|
+
"INVALID_ARGUMENT"
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
name = hash["name"] || hash[:name]
|
49
|
+
response = hash["response"] || hash[:response]
|
50
|
+
|
51
|
+
new(name, response)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Convert the function response to a hash
|
55
|
+
# @return [Hash] The function response as a hash
|
56
|
+
def to_hash
|
57
|
+
{
|
58
|
+
name: @name,
|
59
|
+
response: @response
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Alias for to_hash
|
64
|
+
# @return [Hash] The function response as a hash
|
65
|
+
def to_h
|
66
|
+
to_hash
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|