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,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Extends ContentResponse with function calling response handling
|
6
|
+
class ContentResponse
|
7
|
+
# Get the function call information from the response
|
8
|
+
# @return [Geminize::Models::FunctionResponse, nil] Function call information or nil if not present
|
9
|
+
def function_call
|
10
|
+
return @function_call if defined?(@function_call)
|
11
|
+
|
12
|
+
@function_call = nil
|
13
|
+
candidates = @raw_response["candidates"]
|
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
|
36
|
+
|
37
|
+
@function_call
|
38
|
+
end
|
39
|
+
|
40
|
+
# Check if the response contains a function call
|
41
|
+
# @return [Boolean] True if the response contains a function call
|
42
|
+
def has_function_call?
|
43
|
+
!function_call.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get structured JSON response if available
|
47
|
+
# @return [Hash, nil] Parsed JSON response or nil if not a JSON response
|
48
|
+
def json_response
|
49
|
+
return @json_response if defined?(@json_response)
|
50
|
+
|
51
|
+
@json_response = nil
|
52
|
+
|
53
|
+
if has_text?
|
54
|
+
begin
|
55
|
+
@json_response = JSON.parse(text)
|
56
|
+
rescue JSON::ParserError
|
57
|
+
# Try to parse any JSON-like content from the text
|
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
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@json_response
|
71
|
+
end
|
72
|
+
|
73
|
+
# Check if the response contains valid JSON
|
74
|
+
# @return [Boolean] True if the response contains valid JSON
|
75
|
+
def has_json_response?
|
76
|
+
!json_response.nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
# Enhanced parse_response method to handle function calls
|
80
|
+
alias_method :original_parse_response, :parse_response
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Parse the response data and extract relevant information
|
85
|
+
def parse_response
|
86
|
+
original_parse_response
|
87
|
+
# Clear any cached function call before parsing again
|
88
|
+
remove_instance_variable(:@function_call) if defined?(@function_call)
|
89
|
+
parse_function_call
|
90
|
+
end
|
91
|
+
|
92
|
+
# Parse function call information from the response
|
93
|
+
def parse_function_call
|
94
|
+
candidates = @raw_response["candidates"]
|
95
|
+
|
96
|
+
if candidates && !candidates.empty?
|
97
|
+
content = candidates.first["content"]
|
98
|
+
if content && content["parts"] && !content["parts"].empty?
|
99
|
+
function_call_part = content["parts"].find { |part| part["functionCall"] || part["function_call"] }
|
100
|
+
|
101
|
+
if function_call_part
|
102
|
+
# Handle both "functionCall" and "function_call" formats (API may vary)
|
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
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
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
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Represents a safety setting for content filtering in Gemini API
|
6
|
+
class SafetySetting
|
7
|
+
# Valid harm categories for safety settings
|
8
|
+
HARM_CATEGORIES = [
|
9
|
+
"HARM_CATEGORY_HARASSMENT",
|
10
|
+
"HARM_CATEGORY_HATE_SPEECH",
|
11
|
+
"HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
12
|
+
"HARM_CATEGORY_DANGEROUS_CONTENT"
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
# Valid threshold levels for safety settings
|
16
|
+
THRESHOLD_LEVELS = [
|
17
|
+
"BLOCK_NONE",
|
18
|
+
"BLOCK_LOW_AND_ABOVE",
|
19
|
+
"BLOCK_MEDIUM_AND_ABOVE",
|
20
|
+
"BLOCK_ONLY_HIGH"
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
# @return [String] The harm category this setting applies to
|
24
|
+
attr_reader :category
|
25
|
+
|
26
|
+
# @return [String] The threshold level for filtering
|
27
|
+
attr_reader :threshold
|
28
|
+
|
29
|
+
# Initialize a new safety setting
|
30
|
+
# @param category [String] The harm category this setting applies to
|
31
|
+
# @param threshold [String] The threshold level for filtering
|
32
|
+
# @raise [Geminize::ValidationError] If the safety setting is invalid
|
33
|
+
def initialize(category, threshold)
|
34
|
+
@category = category
|
35
|
+
@threshold = threshold
|
36
|
+
validate!
|
37
|
+
end
|
38
|
+
|
39
|
+
# Validate the safety setting
|
40
|
+
# @raise [Geminize::ValidationError] If the safety setting is invalid
|
41
|
+
# @return [Boolean] true if validation passes
|
42
|
+
def validate!
|
43
|
+
validate_category!
|
44
|
+
validate_threshold!
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
# Convert the safety setting to a hash for API requests
|
49
|
+
# @return [Hash] The safety setting as a hash
|
50
|
+
def to_hash
|
51
|
+
{
|
52
|
+
category: @category,
|
53
|
+
threshold: @threshold
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
# Alias for to_hash
|
58
|
+
# @return [Hash] The safety setting as a hash
|
59
|
+
def to_h
|
60
|
+
to_hash
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Validate the harm category
|
66
|
+
# @raise [Geminize::ValidationError] If the category is invalid
|
67
|
+
def validate_category!
|
68
|
+
unless @category.is_a?(String)
|
69
|
+
raise Geminize::ValidationError.new(
|
70
|
+
"Category must be a string, got #{@category.class}",
|
71
|
+
"INVALID_ARGUMENT"
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
unless HARM_CATEGORIES.include?(@category)
|
76
|
+
raise Geminize::ValidationError.new(
|
77
|
+
"Invalid harm category: #{@category}. Must be one of: #{HARM_CATEGORIES.join(", ")}",
|
78
|
+
"INVALID_ARGUMENT"
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Validate the threshold level
|
84
|
+
# @raise [Geminize::ValidationError] If the threshold is invalid
|
85
|
+
def validate_threshold!
|
86
|
+
unless @threshold.is_a?(String)
|
87
|
+
raise Geminize::ValidationError.new(
|
88
|
+
"Threshold must be a string, got #{@threshold.class}",
|
89
|
+
"INVALID_ARGUMENT"
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
unless THRESHOLD_LEVELS.include?(@threshold)
|
94
|
+
raise Geminize::ValidationError.new(
|
95
|
+
"Invalid threshold level: #{@threshold}. Must be one of: #{THRESHOLD_LEVELS.join(", ")}",
|
96
|
+
"INVALID_ARGUMENT"
|
97
|
+
)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Represents a tool for function calling in Gemini API
|
6
|
+
class Tool
|
7
|
+
# @return [Geminize::Models::FunctionDeclaration] The function declaration for this tool
|
8
|
+
attr_reader :function_declaration
|
9
|
+
|
10
|
+
# Initialize a new tool
|
11
|
+
# @param function_declaration [Geminize::Models::FunctionDeclaration] The function declaration
|
12
|
+
# @raise [Geminize::ValidationError] If the tool is invalid
|
13
|
+
def initialize(function_declaration)
|
14
|
+
@function_declaration = function_declaration
|
15
|
+
validate!
|
16
|
+
end
|
17
|
+
|
18
|
+
# Validate the tool
|
19
|
+
# @raise [Geminize::ValidationError] If the tool is invalid
|
20
|
+
# @return [Boolean] true if validation passes
|
21
|
+
def validate!
|
22
|
+
unless @function_declaration.is_a?(Geminize::Models::FunctionDeclaration)
|
23
|
+
raise Geminize::ValidationError.new(
|
24
|
+
"Function declaration must be a FunctionDeclaration, got #{@function_declaration.class}",
|
25
|
+
"INVALID_ARGUMENT"
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Convert the tool to a hash for API requests
|
33
|
+
# @return [Hash] The tool as a hash
|
34
|
+
def to_hash
|
35
|
+
{
|
36
|
+
functionDeclarations: @function_declaration.to_hash
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
# Alias for to_hash
|
41
|
+
# @return [Hash] The tool as a hash
|
42
|
+
def to_h
|
43
|
+
to_hash
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Geminize
|
4
|
+
module Models
|
5
|
+
# Represents configuration for tool execution in Gemini API
|
6
|
+
class ToolConfig
|
7
|
+
# Valid execution modes for tools
|
8
|
+
EXECUTION_MODES = ["AUTO", "MANUAL", "NONE"].freeze
|
9
|
+
|
10
|
+
# @return [String] The execution mode for tools
|
11
|
+
attr_reader :execution_mode
|
12
|
+
|
13
|
+
# Initialize a new tool configuration
|
14
|
+
# @param execution_mode [String] The execution mode for tools
|
15
|
+
# @raise [Geminize::ValidationError] If the tool configuration is invalid
|
16
|
+
def initialize(execution_mode = "AUTO")
|
17
|
+
@execution_mode = execution_mode
|
18
|
+
validate!
|
19
|
+
end
|
20
|
+
|
21
|
+
# Validate the tool configuration
|
22
|
+
# @raise [Geminize::ValidationError] If the tool configuration is invalid
|
23
|
+
# @return [Boolean] true if validation passes
|
24
|
+
def validate!
|
25
|
+
unless EXECUTION_MODES.include?(@execution_mode)
|
26
|
+
raise Geminize::ValidationError.new(
|
27
|
+
"Invalid execution mode: #{@execution_mode}. Must be one of: #{EXECUTION_MODES.join(", ")}",
|
28
|
+
"INVALID_ARGUMENT"
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
# Convert the tool configuration to a hash for API requests
|
36
|
+
# @return [Hash] The tool configuration as a hash
|
37
|
+
def to_hash
|
38
|
+
{
|
39
|
+
function_calling_config: {
|
40
|
+
mode: @execution_mode
|
41
|
+
}
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Alias for to_hash
|
46
|
+
# @return [Hash] The tool configuration as a hash
|
47
|
+
def to_h
|
48
|
+
to_hash
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|