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

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:



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

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.



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

def api_key=(value)
  @api_key = value
end

Instance Method Details

#audioObject



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

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

#cached_contentObject

キャッシュ管理アクセサ



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

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



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/gemini/client.rb', line 84

def chat(parameters: {}, &stream_callback)
  model = parameters.delete(:model) || "gemini-2.5-flash"
  
  # 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

単一ファイルのヘルパー



297
298
299
# File 'lib/gemini/client.rb', line 297

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

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



198
199
200
201
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/gemini/client.rb', line 198

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



113
114
115
# File 'lib/gemini/client.rb', line 113

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



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

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

#determine_mime_type(path_or_url) ⇒ Object

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



344
345
346
347
348
349
350
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
380
381
382
383
384
385
386
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
# File 'lib/gemini/client.rb', line 344

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

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



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

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

#embeddings(parameters: {}) ⇒ Object

Method corresponding to OpenAI’s embeddings



104
105
106
107
108
109
# File 'lib/gemini/client.rb', line 104

def embeddings(parameters: {})
  model = parameters.delete(:model) || "text-embedding-model"
  path = "models/#{model}:embedContent"
  response = json_post(path: path, parameters: parameters)
  Gemini::Response.new(response)
end

#filesObject



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

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, **parameters, &stream_callback) ⇒ Object

Method with usage similar to OpenAI’s chat



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/gemini/client.rb', line 125

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, **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

  # 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)


161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/gemini/client.rb', line 161

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



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/gemini/client.rb', line 266

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

#imagesObject

画像生成アクセサ



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

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

#inspectObject

Debug inspect method



335
336
337
338
339
340
341
# File 'lib/gemini/client.rb', line 335

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

#messagesObject

Message management accessor



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

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

#modelsObject

Accessor for sub-clients



118
119
120
# File 'lib/gemini/client.rb', line 118

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

#reset_headersObject



72
73
74
# File 'lib/gemini/client.rb', line 72

def reset_headers
  @extra_headers = {}
end

#runsObject

Run management accessor



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

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

#threadsObject

Thread management accessor



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

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

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

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



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
# File 'lib/gemini/client.rb', line 302

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

動画処理アクセサ



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

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