Class: Gemini::Client

Inherits:
Object
  • Object
show all
Includes:
HTTP
Defined in:
lib/gemini/client.rb

Constant Summary collapse

SENSITIVE_ATTRIBUTES =
%i[@api_key @extra_headers].freeze
CONFIG_KEYS =
%i[api_key uri_base extra_headers log_errors request_timeout].freeze
VALID_THINKING_LEVELS =
%w[minimal low medium high].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from HTTP

#delete, #get, #json_post, #multipart_post, #post

Methods included from HTTPHeaders

#add_headers

Constructor Details

#initialize(api_key = nil, config = {}, &faraday_middleware) ⇒ Client

Returns a new instance of Client.

Raises:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/gemini/client.rb', line 12

def initialize(api_key = nil, config = {}, &faraday_middleware)
  # Handle API key passed directly as argument
  config[:api_key] = api_key if api_key
  
  CONFIG_KEYS.each do |key|
    # Set instance variables. Use global config if no setting provided
    instance_variable_set(
      "@#{key}",
      config[key].nil? ? Gemini.configuration.send(key) : config[key]
    )
  end
  
  @api_key ||= ENV["GEMINI_API_KEY"]
  @faraday_middleware = faraday_middleware
  
  raise ConfigurationError, "API key is not set" unless @api_key
end

Instance Attribute Details

#api_key=(value) ⇒ Object (writeonly)

Sets the attribute api_key

Parameters:

  • value

    the value to set the attribute api_key to.



10
11
12
# File 'lib/gemini/client.rb', line 10

def api_key=(value)
  @api_key = value
end

Instance Method Details

#audioObject



45
46
47
# File 'lib/gemini/client.rb', line 45

def audio
  @audio ||= Gemini::Audio.new(client: self)
end

#cached_contentObject

キャッシュ管理アクセサ



69
70
71
# File 'lib/gemini/client.rb', line 69

def cached_content
  @cached_content ||= Gemini::CachedContent.new(client: self)
end

#chat(parameters: {}, &stream_callback) ⇒ Object

OpenAI chat-like text generation method for Gemini API Extended to support streaming callbacks



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/gemini/client.rb', line 135

def chat(parameters: {}, &stream_callback)
  model = parameters.delete(:model) || "gemini-2.5-flash"

  # thinking_budget / thinking_level をパラメータから抽出
  thinking_budget = parameters.delete(:thinking_budget)
  thinking_level = parameters.delete(:thinking_level)

  # Thinking設定
  thinking_config = build_thinking_config(thinking_budget, thinking_level)
  if thinking_config
    parameters[:generationConfig] ||= {}
    parameters[:generationConfig][:thinkingConfig] = thinking_config
  end

  # If streaming callback is provided
  if block_given?
    path = "models/#{model}:streamGenerateContent"
    # Set up stream callback
    stream_params = parameters.dup
    stream_params[:stream] = proc { |chunk| process_stream_chunk(chunk, &stream_callback) }
    response = json_post(path: path, parameters: stream_params)
    return Gemini::Response.new(response)
  else
    # Normal batch response mode
    path = "models/#{model}:generateContent"
    response = json_post(path: path, parameters: parameters)
    return Gemini::Response.new(response)
  end
end

#chat_with_file(file_path, prompt, model: "gemini-2.5-flash", **parameters) ⇒ Object

単一ファイルのヘルパー



382
383
384
# File 'lib/gemini/client.rb', line 382

def chat_with_file(file_path, prompt, model: "gemini-2.5-flash", **parameters)
  chat_with_multimodal([file_path], prompt, model: model, **parameters)
end

#chat_with_multimodal(file_paths, prompt, model: "gemini-2.5-flash", **parameters) ⇒ Object

ファイルを使った会話(複数ファイル対応)



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/gemini/client.rb', line 283

def chat_with_multimodal(file_paths, prompt, model: "gemini-2.5-flash", **parameters)
  # スレッドを作成
  thread = threads.create(parameters: { model: model })
  thread_id = thread["id"]
  
  # 複数のファイルをアップロードして追加
  file_infos = []
  
  begin
    # ファイルをアップロードしてメッセージとして追加
    file_paths.each do |file_path|
      file = File.open(file_path, "rb")
      begin
        upload_result = files.upload(file: file)
        file_uri = upload_result["file"]["uri"]
        file_name = upload_result["file"]["name"]
        mime_type = determine_mime_type(file_path)
        
        # ファイル情報を保存
        file_infos << {
          uri: file_uri,
          name: file_name,
          mime_type: mime_type
        }
        
        # ファイルをメッセージとして追加
        messages.create(
          thread_id: thread_id,
          parameters: {
            role: "user",
            content: [
              { file_data: { mime_type: mime_type, file_uri: file_uri } }
            ]
          }
        )
      ensure
        file.close
      end
    end
    
    # プロンプトメッセージを追加
    messages.create(
      thread_id: thread_id,
      parameters: {
        role: "user",
        content: prompt
      }
    )
    
    # 実行
    run = runs.create(thread_id: thread_id, parameters: parameters)
    
    # メッセージを取得
    messages_list = messages.list(thread_id: thread_id)
    
    # 結果とファイル情報を返す
    {
      messages: messages_list,
      run: run,
      file_infos: file_infos,
      thread_id: thread_id
    }
  rescue => e
    # エラー処理
    { error: e.message, file_infos: file_infos }
  end
end

#completions(parameters: {}, &stream_callback) ⇒ Object

Method corresponding to OpenAI’s completions Uses same endpoint as chat in Gemini API



190
191
192
# File 'lib/gemini/client.rb', line 190

def completions(parameters: {}, &stream_callback)
  chat(parameters: parameters, &stream_callback)
end

#conn(multipart: false) ⇒ Object

Access to conn (Faraday connection) for Audio features Wrapper to allow using private methods from HTTP module externally



129
130
131
# File 'lib/gemini/client.rb', line 129

def conn(multipart: false)
  super(multipart: multipart)
end

#count_tokens(input = nil, model: Gemini::Tokens::DEFAULT_MODEL, contents: nil, system_instruction: nil, tools: nil, generation_config: nil, cached_content: nil, **parameters) ⇒ Object

Convenience wrapper for countTokens. input can be a String, Array of parts/strings, Hash, or omitted when contents: is given.



108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/gemini/client.rb', line 108

def count_tokens(input = nil, model: Gemini::Tokens::DEFAULT_MODEL, contents: nil,
                 system_instruction: nil, tools: nil, generation_config: nil,
                 cached_content: nil, **parameters)
  tokens.count(
    input,
    model: model,
    contents: contents,
    system_instruction: system_instruction,
    tools: tools,
    generation_config: generation_config,
    cached_content: cached_content,
    **parameters
  )
end

#determine_mime_type(path_or_url) ⇒ Object

MIMEタイプを判定するメソッド(パブリックに変更)



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# File 'lib/gemini/client.rb', line 429

def determine_mime_type(path_or_url)
  extension = File.extname(path_or_url).downcase
  
  # ドキュメント形式
  document_types = {
    ".pdf" => "application/pdf",
    ".js" => "application/x-javascript",
    ".py" => "application/x-python",
    ".txt" => "text/plain",
    ".html" => "text/html",
    ".htm" => "text/html",
    ".css" => "text/css",
    ".md" => "text/md",
    ".csv" => "text/csv",
    ".xml" => "text/xml",
    ".rtf" => "text/rtf"
  }
  
  # 画像形式
  image_types = {
    ".jpg" => "image/jpeg",
    ".jpeg" => "image/jpeg",
    ".png" => "image/png",
    ".gif" => "image/gif",
    ".webp" => "image/webp",
    ".heic" => "image/heic",
    ".heif" => "image/heif"
  }
  
  # 音声形式
  audio_types = {
    ".wav" => "audio/wav",
    ".mp3" => "audio/mp3",
    ".aiff" => "audio/aiff",
    ".aac" => "audio/aac",
    ".ogg" => "audio/ogg",
    ".flac" => "audio/flac"
  }
  
  # 拡張子からMIMEタイプを判定
  mime_type = document_types[extension] || image_types[extension] || audio_types[extension]
  return mime_type if mime_type
  
  # ファイルの内容から判定を試みる
  if File.exist?(path_or_url)
    # ファイルの最初の数バイトを読み込んで判定
    first_bytes = File.binread(path_or_url, 8).bytes
    case
    when first_bytes[0..1] == [0xFF, 0xD8]
      return "image/jpeg"  # JPEG
    when first_bytes[0..7] == [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
      return "image/png"   # PNG
    when first_bytes[0..2] == [0x47, 0x49, 0x46]
      return "image/gif"   # GIF
    when first_bytes[0..3] == [0x52, 0x49, 0x46, 0x46] && first_bytes[8..11] == [0x57, 0x45, 0x42, 0x50]
      return "image/webp"  # WEBP
    when first_bytes[0..3] == [0x25, 0x50, 0x44, 0x46]
      return "application/pdf" # PDF
    when first_bytes[0..1] == [0x49, 0x44]
      return "audio/mp3"   # MP3
    when first_bytes[0..3] == [0x52, 0x49, 0x46, 0x46]
      return "audio/wav"   # WAV
    end
  end
  
  # URLまたは判定できない場合
  if path_or_url.start_with?("http://", "https://")
    "application/octet-stream"
  else
    "application/octet-stream"
  end
end

#documentsObject

ドキュメント処理アクセサ



59
60
61
# File 'lib/gemini/client.rb', line 59

def documents
  @documents ||= Gemini::Documents.new(client: self)
end

#embed_content(input, model: Gemini::Embeddings::DEFAULT_MODEL, task_type: nil, title: nil, output_dimensionality: nil, **parameters) ⇒ Object

Generate embeddings for the given input. input can be a String (single embed) or Array of Strings (batch embed). Supports task_type, title (RETRIEVAL_DOCUMENT only), and output_dimensionality.



168
169
170
171
172
173
174
175
176
177
178
# File 'lib/gemini/client.rb', line 168

def embed_content(input, model: Gemini::Embeddings::DEFAULT_MODEL, task_type: nil,
                  title: nil, output_dimensionality: nil, **parameters)
  embeddings_api.create(
    input: input,
    model: model,
    task_type: task_type,
    title: title,
    output_dimensionality: output_dimensionality,
    **parameters
  )
end

#embeddings(parameters: {}) ⇒ Object

Method corresponding to OpenAI’s embeddings (kept for compatibility)



181
182
183
184
185
186
# File 'lib/gemini/client.rb', line 181

def embeddings(parameters: {})
  model = parameters.delete(:model) || Gemini::Embeddings::DEFAULT_MODEL
  path = "models/#{model.to_s.delete_prefix("models/")}:embedContent"
  response = json_post(path: path, parameters: parameters)
  Gemini::Response.new(response)
end

#embeddings_apiObject

Embeddings APIアクセサ



79
80
81
# File 'lib/gemini/client.rb', line 79

def embeddings_api
  @embeddings_api ||= Gemini::Embeddings.new(client: self)
end

#filesObject



49
50
51
# File 'lib/gemini/client.rb', line 49

def files
  @files ||= Gemini::Files.new(client: self)
end

#generate_content(prompt, model: "gemini-2.5-flash", system_instruction: nil, response_mime_type: nil, response_schema: nil, temperature: 0.5, tools: nil, url_context: false, google_search: false, thinking_budget: nil, thinking_level: nil, **parameters, &stream_callback) ⇒ Object

Method with usage similar to OpenAI’s chat



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/gemini/client.rb', line 202

def generate_content(prompt, model: "gemini-2.5-flash", system_instruction: nil,
                    response_mime_type: nil, response_schema: nil, temperature: 0.5, tools: nil,
                    url_context: false, google_search: false,
                    thinking_budget: nil, thinking_level: nil,
                    **parameters, &stream_callback)
  content = format_content(prompt)
  params = {
    contents: [content],
    model: model
  }

  if system_instruction
    params[:system_instruction] = format_content(system_instruction)
  end
  params[:generation_config] ||= {}
  params[:generation_config]["temperature"] = temperature
  if response_mime_type
    params[:generation_config]["response_mime_type"] = response_mime_type
  end

  if response_schema
    params[:generation_config]["response_schema"] = response_schema
  end

  # Thinking設定を追加
  thinking_config = build_thinking_config(thinking_budget, thinking_level)
  if thinking_config
    params[:generation_config][:thinkingConfig] = thinking_config
  end

  # Handle tool shortcuts
  tools = build_tools_array(tools, url_context: url_context, google_search: google_search)
  params[:tools] = tools if tools && !tools.empty?

  params.merge!(parameters)

  if block_given?
    chat(parameters: params, &stream_callback)
  else
    chat(parameters: params)
  end
end

#generate_content_stream(prompt, model: "gemini-2.5-flash", system_instruction: nil, response_mime_type: nil, response_schema: nil, temperature: 0.5, url_context: false, google_search: false, **parameters, &block) ⇒ Object

Streaming text generation

Raises:

  • (ArgumentError)


246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/gemini/client.rb', line 246

def generate_content_stream(prompt, model: "gemini-2.5-flash", system_instruction: nil,
                          response_mime_type: nil, response_schema: nil, temperature: 0.5,
                          url_context: false, google_search: false, **parameters, &block)
  raise ArgumentError, "Block is required for streaming" unless block_given?

  content = format_content(prompt)
  params = {
    contents: [content],
    model: model
  }

  if system_instruction
    params[:system_instruction] = format_content(system_instruction)
  end

  params[:generation_config] ||= {}

  if response_mime_type
    params[:generation_config][:response_mime_type] = response_mime_type
  end

  if response_schema
    params[:generation_config][:response_schema] = response_schema
  end
  params[:generation_config]["temperature"] = temperature

  # Handle tool shortcuts
  tools = build_tools_array(nil, url_context: url_context, google_search: google_search)
  params[:tools] = tools if tools && !tools.empty?

  # Merge other parameters
  params.merge!(parameters)

  chat(parameters: params, &block)
end

#generate_content_with_cache(prompt, cached_content:, model: "gemini-2.5-flash", **parameters) ⇒ Object



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/gemini/client.rb', line 351

def generate_content_with_cache(prompt, cached_content:, model: "gemini-2.5-flash", **parameters)
  # モデル名にmodels/プレフィックスを追加
  model_name = model.start_with?("models/") ? model : "models/#{model}"
  
  # リクエストパラメータを構築
  params = {
    contents: [
      {
        parts: [{ text: prompt }],
        role: "user"
      }
    ],
    cachedContent: cached_content
  }
  
  # その他のパラメータをマージ
  params.merge!(parameters)
  
  # 直接エンドポイントURLを構築
  endpoint = "#{model_name}:generateContent"
  
  # APIリクエスト
  response = json_post(
    path: endpoint,
    parameters: params
  )
  
  Gemini::Response.new(response)
end

#generate_speech(text, voice: nil, multi_speaker: nil, model: Gemini::TTS::DEFAULT_MODEL, speech_config: nil, **parameters) ⇒ Object

Convenience wrapper for TTS speech generation.



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/gemini/client.rb', line 94

def generate_speech(text, voice: nil, multi_speaker: nil, model: Gemini::TTS::DEFAULT_MODEL,
                    speech_config: nil, **parameters)
  tts.generate(
    text,
    voice: voice,
    multi_speaker: multi_speaker,
    model: model,
    speech_config: speech_config,
    **parameters
  )
end

#imagesObject

画像生成アクセサ



54
55
56
# File 'lib/gemini/client.rb', line 54

def images
  @images ||= Gemini::Images.new(client: self)
end

#inspectObject

Debug inspect method



420
421
422
423
424
425
426
# File 'lib/gemini/client.rb', line 420

def inspect
  vars = instance_variables.map do |var|
    value = instance_variable_get(var)
    SENSITIVE_ATTRIBUTES.include?(var) ? "#{var}=[REDACTED]" : "#{var}=#{value.inspect}"
  end
  "#<#{self.class}:#{object_id} #{vars.join(', ')}>"
end

#liveObject

Live APIアクセサ



74
75
76
# File 'lib/gemini/client.rb', line 74

def live
  @live ||= Gemini::Live.new(client: self)
end

#messagesObject

Message management accessor



36
37
38
# File 'lib/gemini/client.rb', line 36

def messages
  @messages ||= Gemini::Messages.new(client: self)
end

#modelsObject

Accessor for sub-clients



195
196
197
# File 'lib/gemini/client.rb', line 195

def models
  @models ||= Gemini::Models.new(client: self)
end

#reset_headersObject



123
124
125
# File 'lib/gemini/client.rb', line 123

def reset_headers
  @extra_headers = {}
end

#runsObject

Run management accessor



41
42
43
# File 'lib/gemini/client.rb', line 41

def runs
  @runs ||= Gemini::Runs.new(client: self)
end

#threadsObject

Thread management accessor



31
32
33
# File 'lib/gemini/client.rb', line 31

def threads
  @threads ||= Gemini::Threads.new(client: self)
end

#tokensObject

Token counting APIアクセサ



84
85
86
# File 'lib/gemini/client.rb', line 84

def tokens
  @tokens ||= Gemini::Tokens.new(client: self)
end

#ttsObject

TTS (speech generation) APIアクセサ



89
90
91
# File 'lib/gemini/client.rb', line 89

def tts
  @tts ||= Gemini::TTS.new(client: self)
end

#upload_and_process_file(file_path, prompt, content_type: nil, model: "gemini-2.5-flash", **parameters) ⇒ Object

ファイルをアップロードして質問するシンプルなヘルパー



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/gemini/client.rb', line 387

def upload_and_process_file(file_path, prompt, content_type: nil, model: "gemini-2.5-flash", **parameters)
  # MIMEタイプを自動判定
  mime_type = content_type || determine_mime_type(file_path)
  
  # ファイルをアップロード
  file = File.open(file_path, "rb")
  begin
    upload_result = files.upload(file: file)
    file_uri = upload_result["file"]["uri"]
    file_name = upload_result["file"]["name"]
    
    # コンテンツを生成
    response = generate_content(
      [
        { text: prompt },
        { file_data: { mime_type: mime_type, file_uri: file_uri } }
      ],
      model: model,
      **parameters
    )
    
    # レスポンスと一緒にファイル情報も返す
    {
      response: response,
      file_uri: file_uri,
      file_name: file_name
    }
  ensure
    file.close
  end
end

#videoObject

動画処理アクセサ



64
65
66
# File 'lib/gemini/client.rb', line 64

def video
  @video ||= Gemini::Video.new(client: self)
end