ruby-gemini-api 0.1.1 → 0.1.3
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/CHANGELOG.md +6 -0
- data/README.md +145 -4
- data/lib/gemini/client.rb +8 -12
- data/lib/gemini/images.rb +263 -27
- data/lib/gemini/response.rb +2 -5
- data/lib/gemini/tool_definition.rb +68 -0
- data/lib/gemini/version.rb +1 -1
- data/lib/gemini.rb +2 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42b86546e154705767e40dad92410df0e1be21e335ae95630d8c2213f42a8a04
|
4
|
+
data.tar.gz: ea10d1a8c13bf0c49ccd720c9e1c25494b9c9a4955879b15e20c2722d5f8f712
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 778b3de54c33818cb7d8fc6f2adc6e3876b5db4ef6dae3c9c1e1ad0db429c4b9c3225ae0518545feaba441cea212fbff0843ee72fe095c73f5fbc3b0c3a37862
|
7
|
+
data.tar.gz: 07cfd038ac791757e11ea1784ba48760fd3b292b6cb5edf8434930dcd4815da89431259ca9359718dc665117630d82d70852f934ef1644130d92666d0c504e12
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[README ‐ 日本語](https://github.com/rira100000000/ruby-gemini-api/
|
1
|
+
[README ‐ 日本語](https://github.com/rira100000000/ruby-gemini-api/blob/main/README_ja.md)
|
2
2
|
# Ruby-Gemini-API
|
3
3
|
|
4
4
|
A Ruby client library for Google's Gemini API. This gem provides a simple, intuitive interface for interacting with Gemini's generative AI capabilities, following patterns similar to other AI client libraries.
|
@@ -18,6 +18,81 @@ This project is inspired by and pays homage to [ruby-openai](https://github.com/
|
|
18
18
|
- Document processing (PDFs and other formats)
|
19
19
|
- Context caching for efficient processing
|
20
20
|
|
21
|
+
### Function Calling
|
22
|
+
|
23
|
+
This library provides an intuitive DSL to define tools for function calling, making it easy to describe your functions to the Gemini model.
|
24
|
+
|
25
|
+
#### Basic Usage
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'gemini'
|
29
|
+
|
30
|
+
# Initialize Gemini client
|
31
|
+
client = Gemini::Client.new(ENV['GEMINI_API_KEY'])
|
32
|
+
|
33
|
+
# Define tools using the ToolDefinition DSL
|
34
|
+
tools = Gemini::ToolDefinition.new do
|
35
|
+
function :get_current_weather, description: "Get the current weather information" do
|
36
|
+
property :location, type: :string, description: "City name, e.g., Tokyo", required: true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# User prompt
|
41
|
+
user_prompt = "Tell me the current weather in Tokyo."
|
42
|
+
|
43
|
+
# Send request with the defined tools
|
44
|
+
response = client.generate_content(
|
45
|
+
user_prompt,
|
46
|
+
model: "gemini-1.5-flash", # Or any model that supports function calling
|
47
|
+
tools: tools
|
48
|
+
)
|
49
|
+
|
50
|
+
# Parse function call from the response
|
51
|
+
unless response.function_calls.empty?
|
52
|
+
function_call = response.function_calls.first
|
53
|
+
puts "Function to call: #{function_call['name']}"
|
54
|
+
puts "Arguments: #{function_call['args']}"
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
#### Advanced Tool Management
|
59
|
+
|
60
|
+
You can define multiple functions, add them dynamically, combine tool sets, and manage them easily.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# Define a set of weather tools
|
64
|
+
weather_tools = Gemini::ToolDefinition.new do
|
65
|
+
function :get_current_weather, description: "Get the current weather" do
|
66
|
+
property :location, type: :string, description: "City name", required: true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Define another set of stock-related tools
|
71
|
+
stock_tools = Gemini::ToolDefinition.new do
|
72
|
+
function :get_stock_price, description: "Get the stock price for a symbol" do
|
73
|
+
property :ticker, type: :string, description: "Stock ticker symbol", required: true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Combine tool sets using the + operator
|
78
|
+
all_tools = weather_tools + stock_tools
|
79
|
+
puts "Combined functions: #{all_tools.list_functions}"
|
80
|
+
# => Combined functions: [:get_current_weather, :get_stock_price]
|
81
|
+
|
82
|
+
# Add a new function later
|
83
|
+
all_tools.add_function :send_email, description: "Send an email" do
|
84
|
+
property :to, type: :string, required: true
|
85
|
+
property :body, type: :string, required: true
|
86
|
+
end
|
87
|
+
puts "After adding a function: #{all_tools.list_functions}"
|
88
|
+
# => After adding a function: [:get_current_weather, :get_stock_price, :send_email]
|
89
|
+
|
90
|
+
# Delete a function
|
91
|
+
all_tools.delete_function(:get_stock_price)
|
92
|
+
puts "After deleting a function: #{all_tools.list_functions}"
|
93
|
+
# => After deleting a function: [:get_current_weather, :send_email]
|
94
|
+
```
|
95
|
+
|
21
96
|
## Installation
|
22
97
|
|
23
98
|
Add this line to your application's Gemfile:
|
@@ -204,11 +279,11 @@ require 'gemini'
|
|
204
279
|
|
205
280
|
client = Gemini::Client.new(ENV['GEMINI_API_KEY'])
|
206
281
|
|
207
|
-
# Generate an image using Gemini 2.
|
282
|
+
# Generate an image using Gemini 2.5
|
208
283
|
response = client.images.generate(
|
209
284
|
parameters: {
|
210
285
|
prompt: "A beautiful sunset over the ocean with sailing boats",
|
211
|
-
model: "gemini-2.
|
286
|
+
model: "gemini-2.5-flash-image-preview",
|
212
287
|
size: "16:9"
|
213
288
|
}
|
214
289
|
)
|
@@ -223,6 +298,72 @@ else
|
|
223
298
|
end
|
224
299
|
```
|
225
300
|
|
301
|
+
#### Image Generation with Multiple Input Images
|
302
|
+
|
303
|
+
You can generate new images by combining or editing multiple input images:
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
require 'gemini'
|
307
|
+
|
308
|
+
client = Gemini::Client.new(ENV['GEMINI_API_KEY'])
|
309
|
+
|
310
|
+
# Generate a new image using multiple input images
|
311
|
+
response = client.images.generate(
|
312
|
+
parameters: {
|
313
|
+
prompt: "Combine these two images to create a single artistic composition",
|
314
|
+
image_paths: ["path/to/image1.jpg", "path/to/image2.png"],
|
315
|
+
model: "gemini-2.5-flash-image-preview",
|
316
|
+
temperature: 0.7
|
317
|
+
}
|
318
|
+
)
|
319
|
+
|
320
|
+
# Save the generated image
|
321
|
+
if response.success? && response.images.any?
|
322
|
+
response.save_image("combined_image.png")
|
323
|
+
puts "Combined image saved"
|
324
|
+
end
|
325
|
+
```
|
326
|
+
|
327
|
+
You can also use file objects:
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
# Using file objects
|
331
|
+
File.open("image1.jpg", "rb") do |file1|
|
332
|
+
File.open("image2.png", "rb") do |file2|
|
333
|
+
response = client.images.generate(
|
334
|
+
parameters: {
|
335
|
+
prompt: "Combine these images together",
|
336
|
+
images: [file1, file2],
|
337
|
+
model: "gemini-2.5-flash-image-preview"
|
338
|
+
}
|
339
|
+
)
|
340
|
+
|
341
|
+
if response.success? && response.images.any?
|
342
|
+
response.save_image("result.png")
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
```
|
347
|
+
|
348
|
+
Base64-encoded image data is also supported:
|
349
|
+
|
350
|
+
```ruby
|
351
|
+
require 'base64'
|
352
|
+
|
353
|
+
# Base64-encoded image data
|
354
|
+
base64_data1 = Base64.strict_encode64(File.binread("image1.jpg"))
|
355
|
+
base64_data2 = Base64.strict_encode64(File.binread("image2.png"))
|
356
|
+
|
357
|
+
response = client.images.generate(
|
358
|
+
parameters: {
|
359
|
+
prompt: "Merge these images together",
|
360
|
+
image_base64s: [base64_data1, base64_data2],
|
361
|
+
mime_types: ["image/jpeg", "image/png"],
|
362
|
+
model: "gemini-2.5-flash-image-preview"
|
363
|
+
}
|
364
|
+
)
|
365
|
+
```
|
366
|
+
|
226
367
|
You can also use Imagen 3 model (Note: This feature is not fully tested yet):
|
227
368
|
|
228
369
|
```ruby
|
@@ -244,7 +385,7 @@ if response.success? && !response.images.empty?
|
|
244
385
|
end
|
245
386
|
```
|
246
387
|
|
247
|
-
For
|
388
|
+
For complete examples, check out the `demo/image_generation_demo.rb` and `demo/multi_image_generation_demo.rb` files included with the gem.
|
248
389
|
|
249
390
|
### Audio Transcription
|
250
391
|
|
data/lib/gemini/client.rb
CHANGED
@@ -116,34 +116,30 @@ module Gemini
|
|
116
116
|
|
117
117
|
# Helper methods for convenience
|
118
118
|
|
119
|
-
|
119
|
+
# Method with usage similar to OpenAI's chat
|
120
120
|
def generate_content(prompt, model: "gemini-2.0-flash-lite", system_instruction: nil,
|
121
|
-
response_mime_type: nil, response_schema: nil, temperature: 0.5, **parameters, &stream_callback)
|
122
|
-
# For image/text combinations, the prompt is passed as an array
|
123
|
-
# example: [{type: "text", text: "What is this?"}, {type: "image_url", image_url: {url: "https://example.com/image.jpg"}}]
|
121
|
+
response_mime_type: nil, response_schema: nil, temperature: 0.5, tools: nil, **parameters, &stream_callback)
|
124
122
|
content = format_content(prompt)
|
125
123
|
params = {
|
126
124
|
contents: [content],
|
127
125
|
model: model
|
128
126
|
}
|
129
|
-
|
127
|
+
|
130
128
|
if system_instruction
|
131
129
|
params[:system_instruction] = format_content(system_instruction)
|
132
130
|
end
|
133
|
-
|
134
|
-
|
135
|
-
|
131
|
+
params[:generation_config] ||= {}
|
132
|
+
params[:generation_config]["temperature"] = temperature
|
136
133
|
if response_mime_type
|
137
134
|
params[:generation_config]["response_mime_type"] = response_mime_type
|
138
135
|
end
|
139
|
-
|
136
|
+
|
140
137
|
if response_schema
|
141
138
|
params[:generation_config]["response_schema"] = response_schema
|
142
139
|
end
|
143
|
-
params[:
|
144
|
-
# Merge other parameters
|
140
|
+
params[:tools] = tools if tools
|
145
141
|
params.merge!(parameters)
|
146
|
-
|
142
|
+
|
147
143
|
if block_given?
|
148
144
|
chat(parameters: params, &stream_callback)
|
149
145
|
else
|
data/lib/gemini/images.rb
CHANGED
@@ -4,43 +4,279 @@ module Gemini
|
|
4
4
|
@client = client
|
5
5
|
end
|
6
6
|
|
7
|
-
#
|
7
|
+
# Main method to generate images
|
8
8
|
def generate(parameters: {})
|
9
9
|
prompt = parameters[:prompt]
|
10
10
|
raise ArgumentError, "prompt parameter is required" unless prompt
|
11
11
|
|
12
|
-
|
13
|
-
model = parameters[:model] || "gemini-2.0-flash-exp-image-generation"
|
12
|
+
model = parameters[:model] || "gemini-2.5-flash-image-preview"
|
14
13
|
|
15
|
-
#
|
14
|
+
# Image editing mode if input images are provided (supports single/multiple images)
|
15
|
+
if has_input_images?(parameters)
|
16
|
+
return generate_with_images(prompt, model, parameters)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Image generation process based on model
|
16
20
|
if model.start_with?("imagen")
|
17
|
-
# Imagen 3
|
21
|
+
# Use Imagen 3
|
18
22
|
response = imagen_generate(prompt, parameters)
|
19
23
|
else
|
20
|
-
# Gemini 2.0
|
24
|
+
# Use Gemini 2.0
|
21
25
|
response = gemini_generate(prompt, parameters)
|
22
26
|
end
|
23
27
|
|
24
|
-
#
|
28
|
+
# Wrap and return response
|
25
29
|
Gemini::Response.new(response)
|
26
30
|
end
|
27
31
|
|
28
32
|
private
|
33
|
+
|
34
|
+
# Check if input images exist (supports single/multiple images)
|
35
|
+
def has_input_images?(parameters)
|
36
|
+
# Single image parameters
|
37
|
+
single_image = parameters[:image] || parameters[:image_path] || parameters[:image_base64]
|
38
|
+
# Multiple image parameters
|
39
|
+
multiple_images = parameters[:images] || parameters[:image_paths] || parameters[:image_base64s]
|
40
|
+
|
41
|
+
single_image || multiple_images
|
42
|
+
end
|
43
|
+
|
44
|
+
# Image generation with image+text (supports single/multiple images)
|
45
|
+
def generate_with_images(prompt, model, parameters)
|
46
|
+
# Process image data (supports single/multiple images)
|
47
|
+
image_parts = process_input_images(parameters)
|
48
|
+
|
49
|
+
# Build content parts (place text first, then images)
|
50
|
+
parts = [{ "text" => prompt }] + image_parts
|
51
|
+
|
52
|
+
# Build generation config
|
53
|
+
generation_config = {
|
54
|
+
"responseModalities" => ["Image"]
|
55
|
+
}
|
56
|
+
|
57
|
+
# Add temperature setting if provided
|
58
|
+
if parameters[:temperature]
|
59
|
+
generation_config["temperature"] = parameters[:temperature]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Build request parameters
|
63
|
+
request_params = {
|
64
|
+
"contents" => [{
|
65
|
+
"parts" => parts
|
66
|
+
}],
|
67
|
+
"generationConfig" => generation_config
|
68
|
+
}
|
69
|
+
|
70
|
+
# Merge other parameters (specify keys to exclude)
|
71
|
+
excluded_keys = [:prompt, :image, :image_path, :image_base64, :images, :image_paths, :image_base64s, :model, :temperature]
|
72
|
+
parameters.each do |key, value|
|
73
|
+
next if excluded_keys.include?(key)
|
74
|
+
request_params[key.to_s] = value
|
75
|
+
end
|
76
|
+
|
77
|
+
# API call
|
78
|
+
response = @client.json_post(
|
79
|
+
path: "models/#{model}:generateContent",
|
80
|
+
parameters: request_params
|
81
|
+
)
|
82
|
+
|
83
|
+
Gemini::Response.new(response)
|
84
|
+
end
|
29
85
|
|
30
|
-
#
|
86
|
+
# Image generation with image+text (kept for backward compatibility)
|
87
|
+
def generate_with_image(prompt, model, parameters)
|
88
|
+
generate_with_images(prompt, model, parameters)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Process input images (supports single/multiple images)
|
92
|
+
def process_input_images(parameters)
|
93
|
+
image_parts = []
|
94
|
+
|
95
|
+
# Process multiple images
|
96
|
+
if parameters[:images] || parameters[:image_paths] || parameters[:image_base64s]
|
97
|
+
# Multiple file objects
|
98
|
+
if parameters[:images]
|
99
|
+
parameters[:images].each_with_index do |image, index|
|
100
|
+
if image.respond_to?(:read)
|
101
|
+
image_data = process_image_io(image)
|
102
|
+
image_parts << create_image_part(image_data)
|
103
|
+
else
|
104
|
+
raise ArgumentError, "Invalid image at index #{index}. Expected file object."
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Multiple file paths
|
110
|
+
if parameters[:image_paths]
|
111
|
+
parameters[:image_paths].each_with_index do |path, index|
|
112
|
+
image_data = process_image_file(path)
|
113
|
+
image_parts << create_image_part(image_data)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Multiple Base64 data
|
118
|
+
if parameters[:image_base64s]
|
119
|
+
mime_types = parameters[:mime_types] || Array.new(parameters[:image_base64s].size, "image/jpeg")
|
120
|
+
parameters[:image_base64s].each_with_index do |base64_data, index|
|
121
|
+
image_data = {
|
122
|
+
data: base64_data,
|
123
|
+
mime_type: mime_types[index] || "image/jpeg"
|
124
|
+
}
|
125
|
+
image_parts << create_image_part(image_data)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
else
|
129
|
+
# Process single image (for backward compatibility)
|
130
|
+
image_data = process_single_input_image(parameters)
|
131
|
+
image_parts << create_image_part(image_data)
|
132
|
+
end
|
133
|
+
|
134
|
+
image_parts
|
135
|
+
end
|
136
|
+
|
137
|
+
# Process single input image (for backward compatibility)
|
138
|
+
def process_single_input_image(parameters)
|
139
|
+
if parameters[:image_base64]
|
140
|
+
# When Base64 data is provided directly
|
141
|
+
{
|
142
|
+
data: parameters[:image_base64],
|
143
|
+
mime_type: parameters[:mime_type] || "image/jpeg"
|
144
|
+
}
|
145
|
+
elsif parameters[:image_path]
|
146
|
+
# When file path is provided
|
147
|
+
process_image_file(parameters[:image_path])
|
148
|
+
elsif parameters[:image]
|
149
|
+
# When file object is provided
|
150
|
+
if parameters[:image].respond_to?(:read)
|
151
|
+
process_image_io(parameters[:image])
|
152
|
+
else
|
153
|
+
raise ArgumentError, "Invalid image parameter. Expected file path, file object, or base64 data."
|
154
|
+
end
|
155
|
+
else
|
156
|
+
raise ArgumentError, "No image data provided"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Create API part from image data
|
161
|
+
def create_image_part(image_data)
|
162
|
+
{
|
163
|
+
"inline_data" => {
|
164
|
+
"mime_type" => image_data[:mime_type],
|
165
|
+
"data" => image_data[:data]
|
166
|
+
}
|
167
|
+
}
|
168
|
+
end
|
169
|
+
|
170
|
+
# Process input image (old method - kept for backward compatibility)
|
171
|
+
def process_input_image(parameters)
|
172
|
+
process_single_input_image(parameters)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Process image from file path (newly added)
|
176
|
+
def process_image_file(file_path)
|
177
|
+
raise ArgumentError, "File does not exist: #{file_path}" unless File.exist?(file_path)
|
178
|
+
|
179
|
+
require 'base64'
|
180
|
+
|
181
|
+
# Determine MIME type
|
182
|
+
mime_type = determine_image_mime_type(file_path)
|
183
|
+
|
184
|
+
# Read file and encode as Base64
|
185
|
+
file_data = File.binread(file_path)
|
186
|
+
base64_data = Base64.strict_encode64(file_data)
|
187
|
+
|
188
|
+
{
|
189
|
+
data: base64_data,
|
190
|
+
mime_type: mime_type
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
# Process image from IO object (newly added)
|
195
|
+
def process_image_io(image_io)
|
196
|
+
require 'base64'
|
197
|
+
|
198
|
+
# Move to beginning of file
|
199
|
+
image_io.rewind if image_io.respond_to?(:rewind)
|
200
|
+
|
201
|
+
# Read data
|
202
|
+
file_data = image_io.read
|
203
|
+
|
204
|
+
# Determine MIME type (use file path if available, otherwise infer from content)
|
205
|
+
mime_type = if image_io.respond_to?(:path) && image_io.path
|
206
|
+
determine_image_mime_type(image_io.path)
|
207
|
+
else
|
208
|
+
determine_mime_type_from_content(file_data)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Base64 encode
|
212
|
+
base64_data = Base64.strict_encode64(file_data)
|
213
|
+
|
214
|
+
{
|
215
|
+
data: base64_data,
|
216
|
+
mime_type: mime_type
|
217
|
+
}
|
218
|
+
end
|
219
|
+
|
220
|
+
# Determine image MIME type from file path (newly added)
|
221
|
+
def determine_image_mime_type(file_path)
|
222
|
+
ext = File.extname(file_path).downcase
|
223
|
+
case ext
|
224
|
+
when ".jpg", ".jpeg"
|
225
|
+
"image/jpeg"
|
226
|
+
when ".png"
|
227
|
+
"image/png"
|
228
|
+
when ".gif"
|
229
|
+
"image/gif"
|
230
|
+
when ".webp"
|
231
|
+
"image/webp"
|
232
|
+
when ".bmp"
|
233
|
+
"image/bmp"
|
234
|
+
when ".tiff", ".tif"
|
235
|
+
"image/tiff"
|
236
|
+
else
|
237
|
+
# Default to JPEG
|
238
|
+
"image/jpeg"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Determine MIME type from file content (newly added)
|
243
|
+
def determine_mime_type_from_content(data)
|
244
|
+
return "image/jpeg" if data.nil? || data.empty?
|
245
|
+
|
246
|
+
# Check file header
|
247
|
+
header = data[0, 8].bytes
|
248
|
+
|
249
|
+
case
|
250
|
+
when header[0..1] == [0xFF, 0xD8]
|
251
|
+
"image/jpeg"
|
252
|
+
when header[0..7] == [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
|
253
|
+
"image/png"
|
254
|
+
when header[0..2] == [0x47, 0x49, 0x46]
|
255
|
+
"image/gif"
|
256
|
+
when header[0..3] == [0x52, 0x49, 0x46, 0x46] && data[8..11].bytes == [0x57, 0x45, 0x42, 0x50]
|
257
|
+
"image/webp"
|
258
|
+
when header[0..1] == [0x42, 0x4D]
|
259
|
+
"image/bmp"
|
260
|
+
else
|
261
|
+
# Default to JPEG
|
262
|
+
"image/jpeg"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Image generation using Gemini 2.5 model (original code unchanged)
|
31
267
|
def gemini_generate(prompt, parameters)
|
32
|
-
#
|
33
|
-
model = parameters[:model] || "gemini-2.
|
268
|
+
# Prepare parameters
|
269
|
+
model = parameters[:model] || "gemini-2.5-flash-image-preview"
|
34
270
|
|
35
|
-
#
|
271
|
+
# Process size parameter (currently not used in Gemini API)
|
36
272
|
# aspect_ratio = process_size_parameter(parameters[:size])
|
37
273
|
|
38
|
-
#
|
274
|
+
# Build generation config
|
39
275
|
generation_config = {
|
40
|
-
"responseModalities" => ["
|
276
|
+
"responseModalities" => ["Image"] # Image output only, even for text-only image generation
|
41
277
|
}
|
42
278
|
|
43
|
-
#
|
279
|
+
# Build request parameters
|
44
280
|
request_params = {
|
45
281
|
"contents" => [{
|
46
282
|
"parts" => [
|
@@ -50,29 +286,29 @@ module Gemini
|
|
50
286
|
"generationConfig" => generation_config
|
51
287
|
}
|
52
288
|
|
53
|
-
# API
|
289
|
+
# API call
|
54
290
|
@client.json_post(
|
55
291
|
path: "models/#{model}:generateContent",
|
56
292
|
parameters: request_params
|
57
293
|
)
|
58
294
|
end
|
59
295
|
|
60
|
-
# Imagen 3
|
296
|
+
# Image generation using Imagen 3 model (original code unchanged)
|
61
297
|
def imagen_generate(prompt, parameters)
|
62
|
-
#
|
298
|
+
# Get model name (default is Imagen 3 standard model)
|
63
299
|
model = parameters[:model] || "imagen-3.0-generate-002"
|
64
300
|
|
65
|
-
#
|
301
|
+
# Get aspect ratio from size parameter
|
66
302
|
aspect_ratio = process_size_parameter(parameters[:size])
|
67
303
|
|
68
|
-
#
|
304
|
+
# Set number of images to generate
|
69
305
|
sample_count = parameters[:n] || parameters[:sample_count] || 1
|
70
|
-
sample_count = [[sample_count.to_i, 1].max, 4].min # 1
|
306
|
+
sample_count = [[sample_count.to_i, 1].max, 4].min # Limit to range 1-4
|
71
307
|
|
72
|
-
#
|
308
|
+
# Set person generation setting
|
73
309
|
person_generation = parameters[:person_generation] || "ALLOW_ADULT"
|
74
310
|
|
75
|
-
#
|
311
|
+
# Build request parameters
|
76
312
|
request_params = {
|
77
313
|
"instances" => [
|
78
314
|
{
|
@@ -84,20 +320,20 @@ module Gemini
|
|
84
320
|
}
|
85
321
|
}
|
86
322
|
|
87
|
-
#
|
323
|
+
# Add aspect ratio if specified
|
88
324
|
request_params["parameters"]["aspectRatio"] = aspect_ratio if aspect_ratio
|
89
325
|
|
90
|
-
#
|
326
|
+
# Add person generation setting
|
91
327
|
request_params["parameters"]["personGeneration"] = person_generation
|
92
328
|
|
93
|
-
# API
|
329
|
+
# API call
|
94
330
|
@client.json_post(
|
95
331
|
path: "models/#{model}:predict",
|
96
332
|
parameters: request_params
|
97
333
|
)
|
98
334
|
end
|
99
335
|
|
100
|
-
#
|
336
|
+
# Determine aspect ratio from size parameter (original code unchanged)
|
101
337
|
def process_size_parameter(size)
|
102
338
|
return nil unless size
|
103
339
|
|
@@ -115,7 +351,7 @@ module Gemini
|
|
115
351
|
when "1:1", "3:4", "4:3", "9:16", "16:9"
|
116
352
|
size.to_s
|
117
353
|
else
|
118
|
-
"1:1" #
|
354
|
+
"1:1" # Default
|
119
355
|
end
|
120
356
|
end
|
121
357
|
end
|
data/lib/gemini/response.rb
CHANGED
@@ -138,11 +138,8 @@ module Gemini
|
|
138
138
|
|
139
139
|
# Get function call information
|
140
140
|
def function_calls
|
141
|
-
|
142
|
-
|
143
|
-
first_candidate&.dig("content", "parts")
|
144
|
-
&.select { |part| part.key?("functionCall") }
|
145
|
-
&.map { |part| part["functionCall"] } || []
|
141
|
+
parts = first_candidate.dig("content", "parts") || []
|
142
|
+
parts.map { |part| part["functionCall"] }.compact
|
146
143
|
end
|
147
144
|
|
148
145
|
# Get response role (usually "model")
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Gemini
|
2
|
+
class ToolDefinition
|
3
|
+
def initialize(&block)
|
4
|
+
@functions = {}
|
5
|
+
instance_eval(&block) if block_given?
|
6
|
+
end
|
7
|
+
|
8
|
+
def function(name, description:, &block)
|
9
|
+
@functions[name] = {
|
10
|
+
name: name,
|
11
|
+
description: description,
|
12
|
+
parameters: {
|
13
|
+
type: 'object',
|
14
|
+
properties: {},
|
15
|
+
required: []
|
16
|
+
}
|
17
|
+
}
|
18
|
+
@current_function = name
|
19
|
+
instance_eval(&block) if block_given?
|
20
|
+
@current_function = nil
|
21
|
+
end
|
22
|
+
alias add_function function
|
23
|
+
|
24
|
+
def property(name, type:, description:, required: false)
|
25
|
+
raise 'property must be defined within a function block' unless @current_function
|
26
|
+
|
27
|
+
@functions[@current_function][:parameters][:properties][name] = {
|
28
|
+
type: type.to_s,
|
29
|
+
description: description
|
30
|
+
}
|
31
|
+
@functions[@current_function][:parameters][:required] << name if required
|
32
|
+
end
|
33
|
+
|
34
|
+
def +(other)
|
35
|
+
raise ArgumentError, 'can only merge with another ToolDefinition' unless other.is_a?(ToolDefinition)
|
36
|
+
|
37
|
+
new_definition = dup
|
38
|
+
other.instance_variable_get(:@functions).each do |name, definition|
|
39
|
+
new_definition.instance_variable_get(:@functions)[name] = definition
|
40
|
+
end
|
41
|
+
new_definition
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete_function(name)
|
45
|
+
@functions.delete(name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def list_functions
|
49
|
+
@functions.keys
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_h
|
53
|
+
{
|
54
|
+
function_declarations: @functions.values
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_json(*args)
|
59
|
+
to_h.to_json(*args)
|
60
|
+
end
|
61
|
+
|
62
|
+
def dup
|
63
|
+
new_instance = self.class.new
|
64
|
+
new_instance.instance_variable_set(:@functions, @functions.dup)
|
65
|
+
new_instance
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/gemini/version.rb
CHANGED
data/lib/gemini.rb
CHANGED
@@ -3,7 +3,8 @@ require "faraday/multipart"
|
|
3
3
|
require "json"
|
4
4
|
require 'dotenv/load'
|
5
5
|
|
6
|
-
require_relative
|
6
|
+
require_relative 'gemini/version'
|
7
|
+
require_relative 'gemini/tool_definition'
|
7
8
|
require_relative "gemini/http_headers"
|
8
9
|
require_relative "gemini/http"
|
9
10
|
require_relative "gemini/client"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-gemini-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- rira100000000
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-10-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -161,6 +161,7 @@ files:
|
|
161
161
|
- lib/gemini/response.rb
|
162
162
|
- lib/gemini/runs.rb
|
163
163
|
- lib/gemini/threads.rb
|
164
|
+
- lib/gemini/tool_definition.rb
|
164
165
|
- lib/gemini/version.rb
|
165
166
|
- lib/ruby/gemini.rb
|
166
167
|
homepage: https://github.com/rira100000000/ruby-gemini-api
|