geminize 1.2.0 → 1.3.1
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 +11 -1
- data/.memory_bank/progress.md +3 -2
- data/.memory_bank/tasks.md +6 -4
- data/CHANGELOG.md +20 -0
- data/README.md +50 -6
- data/examples/code_execution.rb +126 -0
- data/examples/function_calling.rb +1 -1
- data/examples/multimodal.rb +2 -2
- data/examples/safety_settings.rb +1 -1
- data/lib/geminize/model_info.rb +1 -1
- 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 +8 -0
- data/lib/geminize/models/content_response_extensions.rb +99 -90
- data/lib/geminize/models/tool.rb +26 -8
- data/lib/geminize/request_builder.rb +1 -1
- data/lib/geminize/version.rb +1 -1
- data/lib/geminize.rb +412 -4
- metadata +4 -3
- data/lib/geminize/module_extensions.rb +0 -228
- data/lib/geminize/module_safety.rb +0 -135
@@ -2,117 +2,126 @@
|
|
2
2
|
|
3
3
|
module Geminize
|
4
4
|
module Models
|
5
|
-
# Extends ContentResponse with function calling
|
5
|
+
# Extends ContentResponse with function calling capabilities
|
6
6
|
class ContentResponse
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
if candidates && !candidates.empty?
|
16
|
-
content = candidates.first["content"]
|
17
|
-
if content && content["parts"] && !content["parts"].empty?
|
18
|
-
function_call_part = content["parts"].find { |part| part["functionCall"] || part["function_call"] }
|
19
|
-
|
20
|
-
if function_call_part
|
21
|
-
# Handle both "functionCall" and "function_call" formats (API may vary)
|
22
|
-
function_data = function_call_part["functionCall"] || function_call_part["function_call"]
|
23
|
-
|
24
|
-
if function_data
|
25
|
-
# Extract name and args - handle different API response formats
|
26
|
-
name = function_data["name"]
|
27
|
-
args = function_data["args"] || function_data["arguments"]
|
28
|
-
|
29
|
-
if name
|
30
|
-
@function_call = FunctionResponse.new(name, args || {})
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
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
|
36
15
|
|
37
|
-
|
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
|
38
27
|
end
|
39
28
|
|
40
|
-
#
|
41
|
-
# @return [Boolean]
|
29
|
+
# Determine if the response contains a function call
|
30
|
+
# @return [Boolean] true if the response contains a function call
|
42
31
|
def has_function_call?
|
43
|
-
|
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?
|
44
51
|
end
|
45
52
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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"] || {}
|
50
68
|
|
51
|
-
|
69
|
+
@function_call = FunctionResponse.new(function_name, function_args)
|
70
|
+
end
|
71
|
+
end
|
52
72
|
|
53
|
-
|
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")
|
54
78
|
begin
|
55
79
|
@json_response = JSON.parse(text)
|
80
|
+
return
|
56
81
|
rescue JSON::ParserError
|
57
|
-
#
|
58
|
-
json_match = text.match(/```json\s*(.*?)\s*```/m) || text.match(/\{.*\}/m) || text.match(/\[.*\]/m)
|
59
|
-
if json_match
|
60
|
-
begin
|
61
|
-
@json_response = JSON.parse(json_match[1] || json_match[0])
|
62
|
-
rescue JSON::ParserError
|
63
|
-
# Still not valid JSON
|
64
|
-
@json_response = nil
|
65
|
-
end
|
66
|
-
end
|
82
|
+
# Not valid JSON, continue checking other methods
|
67
83
|
end
|
68
84
|
end
|
69
85
|
|
70
|
-
|
71
|
-
|
86
|
+
# Next check if it's returned in structured format
|
87
|
+
candidates = @response_data.dig("candidates") || []
|
88
|
+
return if candidates.empty?
|
72
89
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
77
97
|
end
|
78
98
|
|
79
|
-
#
|
80
|
-
|
99
|
+
# Parse code execution data from the response
|
100
|
+
def parse_code_execution
|
101
|
+
candidates = @response_data.dig("candidates") || []
|
102
|
+
return if candidates.empty?
|
81
103
|
|
82
|
-
|
104
|
+
content = candidates[0].dig("content") || {}
|
105
|
+
parts = content.dig("parts") || []
|
83
106
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
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"] || ""
|
91
113
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
function_data = function_call_part["functionCall"] || function_call_part["function_call"]
|
104
|
-
|
105
|
-
if function_data
|
106
|
-
# Extract name and args - handle different API response formats
|
107
|
-
name = function_data["name"]
|
108
|
-
args = function_data["args"] || function_data["arguments"]
|
109
|
-
|
110
|
-
if name
|
111
|
-
@function_call = FunctionResponse.new(name, args || {})
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
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)
|
116
125
|
end
|
117
126
|
end
|
118
127
|
end
|
data/lib/geminize/models/tool.rb
CHANGED
@@ -2,16 +2,21 @@
|
|
2
2
|
|
3
3
|
module Geminize
|
4
4
|
module Models
|
5
|
-
# Represents a tool for function calling in Gemini API
|
5
|
+
# Represents a tool for function calling or code execution in Gemini API
|
6
6
|
class Tool
|
7
|
-
# @return [Geminize::Models::FunctionDeclaration] The function declaration for this tool
|
7
|
+
# @return [Geminize::Models::FunctionDeclaration, nil] The function declaration for this tool
|
8
8
|
attr_reader :function_declaration
|
9
9
|
|
10
|
+
# @return [Boolean] Whether this tool is a code execution tool
|
11
|
+
attr_reader :code_execution
|
12
|
+
|
10
13
|
# Initialize a new tool
|
11
|
-
# @param function_declaration [Geminize::Models::FunctionDeclaration] The function declaration
|
14
|
+
# @param function_declaration [Geminize::Models::FunctionDeclaration, nil] The function declaration
|
15
|
+
# @param code_execution [Boolean] Whether this is a code execution tool
|
12
16
|
# @raise [Geminize::ValidationError] If the tool is invalid
|
13
|
-
def initialize(function_declaration)
|
17
|
+
def initialize(function_declaration = nil, code_execution = false)
|
14
18
|
@function_declaration = function_declaration
|
19
|
+
@code_execution = code_execution
|
15
20
|
validate!
|
16
21
|
end
|
17
22
|
|
@@ -19,7 +24,14 @@ module Geminize
|
|
19
24
|
# @raise [Geminize::ValidationError] If the tool is invalid
|
20
25
|
# @return [Boolean] true if validation passes
|
21
26
|
def validate!
|
22
|
-
|
27
|
+
if !@code_execution && @function_declaration.nil?
|
28
|
+
raise Geminize::ValidationError.new(
|
29
|
+
"Either function_declaration or code_execution must be provided",
|
30
|
+
"INVALID_ARGUMENT"
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
if @function_declaration && !@function_declaration.is_a?(Geminize::Models::FunctionDeclaration)
|
23
35
|
raise Geminize::ValidationError.new(
|
24
36
|
"Function declaration must be a FunctionDeclaration, got #{@function_declaration.class}",
|
25
37
|
"INVALID_ARGUMENT"
|
@@ -32,9 +44,15 @@ module Geminize
|
|
32
44
|
# Convert the tool to a hash for API requests
|
33
45
|
# @return [Hash] The tool as a hash
|
34
46
|
def to_hash
|
35
|
-
|
36
|
-
|
37
|
-
|
47
|
+
if @code_execution
|
48
|
+
{
|
49
|
+
code_execution: {}
|
50
|
+
}
|
51
|
+
else
|
52
|
+
{
|
53
|
+
functionDeclarations: @function_declaration.to_hash
|
54
|
+
}
|
55
|
+
end
|
38
56
|
end
|
39
57
|
|
40
58
|
# Alias for to_hash
|
@@ -68,7 +68,7 @@ module Geminize
|
|
68
68
|
# @param model_name [String] The model name or ID to get info for
|
69
69
|
# @return [String] The complete API endpoint path for getting model info
|
70
70
|
def build_get_model_endpoint(model_name)
|
71
|
-
# Handle both formats: "models/gemini-
|
71
|
+
# Handle both formats: "models/gemini-2.0-flash" or just "gemini-2.0-flash"
|
72
72
|
unless model_name.start_with?("models/")
|
73
73
|
model_name = "models/#{model_name}"
|
74
74
|
end
|
data/lib/geminize/version.rb
CHANGED