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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5a02e1edca475bc3a7ab45e9e595186a99e2546fb46c1f1f1232a8215d45469
4
- data.tar.gz: a688b8e1a0bb724ab24d8cb1113ad689a784bc41915b180e17e9b4552dad4628
3
+ metadata.gz: 42b86546e154705767e40dad92410df0e1be21e335ae95630d8c2213f42a8a04
4
+ data.tar.gz: ea10d1a8c13bf0c49ccd720c9e1c25494b9c9a4955879b15e20c2722d5f8f712
5
5
  SHA512:
6
- metadata.gz: a5d4af0804fb7ded6bbf069b492b31b56979ab6421dafcf671704a8aae62405946ca4efa6cdb1ef13ab69ec16a19fb0b1d19df5045d6840ae843c7500250094d
7
- data.tar.gz: e9c83f07e0cfb54827d7c0a4180ab8731a7b8580669073d7d9acfe0a8b45b015d18c42acf6fbd1ffd2da3b9acb89ea7723da7e1abba756761666944a985f63dc
6
+ metadata.gz: 778b3de54c33818cb7d8fc6f2adc6e3876b5db4ef6dae3c9c1e1ad0db429c4b9c3225ae0518545feaba441cea212fbff0843ee72fe095c73f5fbc3b0c3a37862
7
+ data.tar.gz: 07cfd038ac791757e11ea1784ba48760fd3b292b6cb5edf8434930dcd4815da89431259ca9359718dc665117630d82d70852f934ef1644130d92666d0c504e12
data/CHANGELOG.md CHANGED
@@ -5,3 +5,9 @@
5
5
 
6
6
  ## [0.1.1] - 2025-05-04
7
7
  - Changed generate_contents to accept temperature parameter
8
+
9
+ ## [0.1.2] - 2025-07-10
10
+ - Add function calling
11
+
12
+ ## [0.1.3] - 2025-10-09
13
+ - Add support for multi-image input
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [README ‐ 日本語](https://github.com/rira100000000/ruby-gemini-api/wiki/README-%E2%80%90-%E6%97%A5%E6%9C%AC%E8%AA%9E)
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.0
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.0-flash-exp-image-generation",
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 a complete example, check out the `demo/image_generation_demo.rb` file included with the gem.
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
- # Method with usage similar to OpenAI's chat
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
- params[:generation_config] ||= {}
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[:generation_config]["temperature"] = temperature
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
- # モデルの決定(デフォルトはGemini 2.0)
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
- # Gemini 2.0モデルを使用した画像生成
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.0-flash-exp-image-generation"
268
+ # Prepare parameters
269
+ model = parameters[:model] || "gemini-2.5-flash-image-preview"
34
270
 
35
- # サイズパラメータの処理(現在はGemini APIでは使用しない)
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" => ["Text", "Image"]
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
- # モデル名の取得(デフォルトはImagen 3の標準モデル)
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 # 14の範囲に制限
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
@@ -138,11 +138,8 @@ module Gemini
138
138
 
139
139
  # Get function call information
140
140
  def function_calls
141
- return [] unless valid?
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gemini
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
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 "gemini/version"
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.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-05-04 00:00:00.000000000 Z
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