Class: Gemini::Response
- Inherits:
-
Object
- Object
- Gemini::Response
- Defined in:
- lib/gemini/response.rb
Instance Attribute Summary collapse
-
#raw_data ⇒ Object
readonly
Raw response data from API.
Instance Method Summary collapse
- #as_json_array(model_class) ⇒ Object
- #as_json_object(model_class) ⇒ Object
- #as_json_with_keys(*keys) ⇒ Object
-
#build_function_call_parts_with_signature ⇒ Object
関数呼び出しにSignatureを付与してパーツを構築.
-
#candidates ⇒ Object
Get all candidates (if multiple candidates are present).
-
#completion_tokens ⇒ Object
Get number of tokens used for completion.
-
#embedding ⇒ Object
Get the embedding values as an Array of Floats.
-
#embedding_dimension ⇒ Object
Get the dimensionality (length) of the first embedding vector.
-
#embedding_response? ⇒ Boolean
Check if the raw response contains embedding data.
-
#embeddings ⇒ Object
Get all embedding value arrays for batch responses.
-
#error ⇒ Object
Get error message if any.
-
#finish_reason ⇒ Object
Get finish reason (STOP, SAFETY, etc.).
-
#first_candidate ⇒ Object
Get the first candidate.
-
#first_thought_signature ⇒ Object
最初のThought Signatureを取得.
-
#formatted_text ⇒ Object
Get formatted text (HTML/markdown, etc.).
-
#full_content ⇒ Object
Get all content with string representation.
-
#function_calls ⇒ Object
Get function call information.
-
#gemini_3? ⇒ Boolean
Gemini 3系かどうか.
-
#grounded? ⇒ Boolean
Check if response has grounding metadata.
-
#grounding_chunks ⇒ Object
Get grounding chunks (source references).
-
#grounding_metadata ⇒ Object
Get grounding metadata (for Google Search grounding).
-
#grounding_sources ⇒ Object
Get formatted grounding sources (simplified access).
-
#has_thought_signature? ⇒ Boolean
Signatureが存在するか.
-
#image ⇒ Object
画像生成結果から最初の画像を取得(Base64エンコード形式).
-
#image_mime_types ⇒ Object
画像のMIMEタイプを取得.
-
#image_parts ⇒ Object
Get image parts (if any).
-
#image_urls ⇒ Object
Get image URLs from multimodal responses (if any).
-
#images ⇒ Object
画像生成結果からすべての画像を取得(Base64エンコード形式の配列).
-
#initialize(response_data) ⇒ Response
constructor
A new instance of Response.
-
#inspect ⇒ Object
Inspection method for debugging.
- #json ⇒ Object
- #json? ⇒ Boolean
-
#model_version ⇒ Object
モデルバージョンを取得.
-
#parts ⇒ Object
Get all content parts.
-
#prompt_tokens ⇒ Object
Get number of prompt tokens used.
-
#retrieved_urls ⇒ Object
Get retrieved URLs from URL context.
-
#role ⇒ Object
Get response role (usually “model”).
-
#safety_blocked? ⇒ Boolean
Check if response was blocked for safety reasons.
-
#safety_ratings ⇒ Object
Get safety ratings.
-
#save_image(filepath) ⇒ Object
最初の画像をファイルに保存.
-
#save_images(filepaths) ⇒ Object
複数の画像をファイルに保存.
-
#search_entry_point ⇒ Object
Get search entry point URL (if available).
-
#stream_chunks ⇒ Object
Process chunks for streaming responses.
-
#success? ⇒ Boolean
Check if response was successful.
-
#text ⇒ Object
Get simple text response (combines multiple parts if present).
-
#text_parts ⇒ Object
Get all text parts as an array.
-
#thought_signatures ⇒ Object
Thought Signatureを取得(配列).
-
#thoughts_token_count ⇒ Object
思考トークン数を取得.
- #to_formatted_json(pretty: false) ⇒ Object
-
#to_s ⇒ Object
Override to_s method to return text.
-
#total_tokens ⇒ Object
Get total tokens used.
-
#url_context? ⇒ Boolean
Check if response has URL context metadata.
-
#url_context_metadata ⇒ Object
Get URL context metadata (for URL Context tool).
-
#url_retrieval_statuses ⇒ Object
Get URL retrieval statuses.
-
#usage ⇒ Object
Get token usage information.
-
#valid? ⇒ Boolean
Check if response is valid.
Constructor Details
#initialize(response_data) ⇒ Response
Returns a new instance of Response.
6 7 8 |
# File 'lib/gemini/response.rb', line 6 def initialize(response_data) @raw_data = response_data end |
Instance Attribute Details
#raw_data ⇒ Object (readonly)
Raw response data from API
4 5 6 |
# File 'lib/gemini/response.rb', line 4 def raw_data @raw_data end |
Instance Method Details
#as_json_array(model_class) ⇒ Object
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 |
# File 'lib/gemini/response.rb', line 506 def as_json_array(model_class) json_data = json return [] unless json_data && json_data.is_a?(Array) begin json_data.map do |item| if model_class.respond_to?(:from_json) model_class.from_json(item) elsif defined?(ActiveModel::Model) && model_class.ancestors.include?(ActiveModel::Model) model_class.new(item) else instance = model_class.new item.each do |key, value| setter_method = "#{key}=" if instance.respond_to?(setter_method) instance.send(setter_method, value) end end instance end end rescue => e [] end end |
#as_json_object(model_class) ⇒ Object
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 |
# File 'lib/gemini/response.rb', line 480 def as_json_object(model_class) json_data = json return nil unless json_data begin if model_class.respond_to?(:from_json) model_class.from_json(json_data) elsif defined?(ActiveModel::Model) && model_class.ancestors.include?(ActiveModel::Model) model_class.new(json_data) else instance = model_class.new json_data.each do |key, value| setter_method = "#{key}=" if instance.respond_to?(setter_method) instance.send(setter_method, value) end end instance end rescue => e nil end end |
#as_json_with_keys(*keys) ⇒ Object
534 535 536 537 538 539 540 541 542 543 |
# File 'lib/gemini/response.rb', line 534 def as_json_with_keys(*keys) json_data = json return [] unless json_data && json_data.is_a?(Array) json_data.map do |item| keys.each_with_object({}) do |key, result| result[key.to_s] = item[key.to_s] if item.key?(key.to_s) end end end |
#build_function_call_parts_with_signature ⇒ Object
関数呼び出しにSignatureを付与してパーツを構築
300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/gemini/response.rb', line 300 def build_function_call_parts_with_signature function_call_parts = parts.select { |p| p['functionCall'] } signature = first_thought_signature function_call_parts.map.with_index do |part, index| fc_part = { functionCall: part['functionCall'] } # 最初のパートにのみSignatureを付与 fc_part[:thoughtSignature] = signature if index == 0 && signature fc_part end end |
#candidates ⇒ Object
Get all candidates (if multiple candidates are present)
67 68 69 |
# File 'lib/gemini/response.rb', line 67 def candidates @raw_data&.dig("candidates") || [] end |
#completion_tokens ⇒ Object
Get number of tokens used for completion
226 227 228 |
# File 'lib/gemini/response.rb', line 226 def completion_tokens usage&.dig("candidateTokens") || 0 end |
#embedding ⇒ Object
Get the embedding values as an Array of Floats. For single embedContent responses returns the values array. For batchEmbedContents responses returns the first embedding’s values.
89 90 91 92 93 94 95 96 |
# File 'lib/gemini/response.rb', line 89 def return nil unless @raw_data if @raw_data["embedding"].is_a?(Hash) @raw_data["embedding"]["values"] elsif @raw_data["embeddings"].is_a?(Array) && @raw_data["embeddings"].first.is_a?(Hash) @raw_data["embeddings"].first["values"] end end |
#embedding_dimension ⇒ Object
Get the dimensionality (length) of the first embedding vector
113 114 115 116 |
# File 'lib/gemini/response.rb', line 113 def values = values.is_a?(Array) ? values.length : 0 end |
#embedding_response? ⇒ Boolean
Check if the raw response contains embedding data
80 81 82 83 84 |
# File 'lib/gemini/response.rb', line 80 def return false if @raw_data.nil? (@raw_data.key?("embedding") && !@raw_data["embedding"].nil?) || (@raw_data.key?("embeddings") && @raw_data["embeddings"].is_a?(Array) && !@raw_data["embeddings"].empty?) end |
#embeddings ⇒ Object
Get all embedding value arrays for batch responses. Returns an Array of Arrays of Floats. For single embedContent responses, returns a single-element array.
101 102 103 104 105 106 107 108 109 110 |
# File 'lib/gemini/response.rb', line 101 def return [] unless @raw_data if @raw_data["embeddings"].is_a?(Array) @raw_data["embeddings"].map { |e| e["values"] }.compact elsif @raw_data["embedding"].is_a?(Hash) && @raw_data["embedding"]["values"] [@raw_data["embedding"]["values"]] else [] end end |
#error ⇒ Object
Get error message if any
119 120 121 122 123 124 125 126 |
# File 'lib/gemini/response.rb', line 119 def error return nil if valid? # Return nil for empty responses (to display "Empty response" in to_s method) return nil if @raw_data.nil? || @raw_data.empty? @raw_data&.dig("error", "message") || "Unknown error" end |
#finish_reason ⇒ Object
Get finish reason (STOP, SAFETY, etc.)
134 135 136 |
# File 'lib/gemini/response.rb', line 134 def finish_reason first_candidate&.dig("finishReason") end |
#first_candidate ⇒ Object
Get the first candidate
62 63 64 |
# File 'lib/gemini/response.rb', line 62 def first_candidate @raw_data&.dig("candidates", 0) end |
#first_thought_signature ⇒ Object
最初のThought Signatureを取得
280 281 282 |
# File 'lib/gemini/response.rb', line 280 def first_thought_signature thought_signatures.first end |
#formatted_text ⇒ Object
Get formatted text (HTML/markdown, etc.)
21 22 23 24 25 |
# File 'lib/gemini/response.rb', line 21 def formatted_text return nil unless valid? text # Currently returns plain text, but could add formatting in the future end |
#full_content ⇒ Object
Get all content with string representation
49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/gemini/response.rb', line 49 def full_content parts.map do |part| if part.key?("text") part["text"] elsif part.key?("inline_data") && part["inline_data"]["mime_type"].start_with?("image/") "[IMAGE: #{part["inline_data"]["mime_type"]}]" else "[UNKNOWN CONTENT]" end end.join("\n") end |
#function_calls ⇒ Object
Get function call information
252 253 254 255 |
# File 'lib/gemini/response.rb', line 252 def function_calls parts = first_candidate.dig("content", "parts") || [] parts.map { |part| part["functionCall"] }.compact end |
#gemini_3? ⇒ Boolean
Gemini 3系かどうか
295 296 297 |
# File 'lib/gemini/response.rb', line 295 def gemini_3? model_version&.start_with?('gemini-3') || false end |
#grounded? ⇒ Boolean
Check if response has grounding metadata
149 150 151 |
# File 'lib/gemini/response.rb', line 149 def grounded? !.nil? && !.empty? end |
#grounding_chunks ⇒ Object
Get grounding chunks (source references)
154 155 156 |
# File 'lib/gemini/response.rb', line 154 def grounding_chunks &.dig("groundingChunks") || [] end |
#grounding_metadata ⇒ Object
Get grounding metadata (for Google Search grounding)
144 145 146 |
# File 'lib/gemini/response.rb', line 144 def first_candidate&.dig("groundingMetadata") end |
#grounding_sources ⇒ Object
Get formatted grounding sources (simplified access)
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/gemini/response.rb', line 164 def grounding_sources return [] unless grounded? grounding_chunks.map do |chunk| if chunk["web"] { url: chunk["web"]["uri"], title: chunk["web"]["title"], type: "web" } else # Handle other potential chunk types { type: "unknown", data: chunk } end end end |
#has_thought_signature? ⇒ Boolean
Signatureが存在するか
285 286 287 |
# File 'lib/gemini/response.rb', line 285 def has_thought_signature? !thought_signatures.empty? end |
#image ⇒ Object
画像生成結果から最初の画像を取得(Base64エンコード形式)
313 314 315 |
# File 'lib/gemini/response.rb', line 313 def image images.first end |
#image_mime_types ⇒ Object
画像のMIMEタイプを取得
374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/gemini/response.rb', line 374 def image_mime_types return [] unless valid? if first_candidate&.dig("content", "parts") first_candidate["content"]["parts"] .select { |part| part.key?("inline_data") && part["inline_data"]["mime_type"].start_with?("image/") } .map { |part| part["inline_data"]["mime_type"] } else # Imagen 3のデフォルトはPNG Array.new(images.size, "image/png") end end |
#image_parts ⇒ Object
Get image parts (if any)
42 43 44 45 46 |
# File 'lib/gemini/response.rb', line 42 def image_parts return [] unless valid? parts.select { |part| part.key?("inline_data") && part["inline_data"]["mime_type"].start_with?("image/") } end |
#image_urls ⇒ Object
Get image URLs from multimodal responses (if any)
243 244 245 246 247 248 249 |
# File 'lib/gemini/response.rb', line 243 def image_urls return [] unless valid? first_candidate&.dig("content", "parts") &.select { |part| part.key?("image_url") } &.map { |part| part.dig("image_url", "url") } || [] end |
#images ⇒ Object
画像生成結果からすべての画像を取得(Base64エンコード形式の配列)
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 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/gemini/response.rb', line 318 def images image_array = [] return image_array unless @raw_data # Gemini 2.0スタイルレスポンスを正確に解析 # キーはcamelCase形式で使用されているので注意(inlineDataなど) if @raw_data.key?('candidates') && !@raw_data['candidates'].empty? candidate = @raw_data['candidates'][0] if candidate.key?('content') && candidate['content'].key?('parts') parts = candidate['content']['parts'] parts.each do |part| # キャメルケースでアクセス(inlineData) if part.key?('inlineData') inline_data = part['inlineData'] if inline_data.key?('mimeType') && inline_data['mimeType'].to_s.start_with?('image/') && inline_data.key?('data') # 画像データを追加 image_array << inline_data['data'] puts "画像データを検出しました: #{inline_data['mimeType']}" if ENV["DEBUG"] end end end end # Imagen 3スタイルレスポンスのチェック elsif @raw_data.key?('predictions') @raw_data['predictions'].each do |pred| if pred.key?('bytesBase64Encoded') image_array << pred['bytesBase64Encoded'] puts "Imagen 3形式の画像データを検出しました" if ENV["DEBUG"] end end end # フォールバック:直接JSONから抽出 if image_array.empty? puts "標準的な方法で画像データが見つかりませんでした。正規表現による抽出を試みます..." if ENV["DEBUG"] raw_json = @raw_data.to_json # "data"キーで長いBase64文字列を検索 base64_matches = raw_json.scan(/"data":"([A-Za-z0-9+\/=]{100,})"/) if !base64_matches.empty? puts "検出したBase64データ: #{base64_matches.size}個" if ENV["DEBUG"] base64_matches.each do |match| image_array << match[0] end end end puts "検出した画像データ数: #{image_array.size}" if ENV["DEBUG"] image_array end |
#inspect ⇒ Object
Inspection method for debugging
455 456 457 |
# File 'lib/gemini/response.rb', line 455 def inspect "#<Gemini::Response text=#{text ? text[0..30] + (text.length > 30 ? '...' : '') : 'nil'} success=#{success?}>" end |
#json ⇒ Object
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 |
# File 'lib/gemini/response.rb', line 459 def json return nil unless valid? text_content = text return nil unless text_content begin if text_content.strip.start_with?('{') || text_content.strip.start_with?('[') JSON.parse(text_content) else nil end rescue JSON::ParserError => e nil end end |
#json? ⇒ Boolean
476 477 478 |
# File 'lib/gemini/response.rb', line 476 def json? !json.nil? end |
#model_version ⇒ Object
モデルバージョンを取得
290 291 292 |
# File 'lib/gemini/response.rb', line 290 def model_version @raw_data['modelVersion'] end |
#parts ⇒ Object
Get all content parts
28 29 30 31 32 |
# File 'lib/gemini/response.rb', line 28 def parts return [] unless valid? first_candidate&.dig("content", "parts") || [] end |
#prompt_tokens ⇒ Object
Get number of prompt tokens used
221 222 223 |
# File 'lib/gemini/response.rb', line 221 def prompt_tokens usage&.dig("promptTokens") || 0 end |
#retrieved_urls ⇒ Object
Get retrieved URLs from URL context
195 196 197 198 199 |
# File 'lib/gemini/response.rb', line 195 def retrieved_urls return [] unless url_context? &.dig("urlMetadata") || [] end |
#role ⇒ Object
Get response role (usually “model”)
258 259 260 |
# File 'lib/gemini/response.rb', line 258 def role first_candidate&.dig("content", "role") end |
#safety_blocked? ⇒ Boolean
Check if response was blocked for safety reasons
139 140 141 |
# File 'lib/gemini/response.rb', line 139 def safety_blocked? finish_reason == "SAFETY" end |
#safety_ratings ⇒ Object
Get safety ratings
263 264 265 |
# File 'lib/gemini/response.rb', line 263 def first_candidate&.dig("safetyRatings") || [] end |
#save_image(filepath) ⇒ Object
最初の画像をファイルに保存
388 389 390 |
# File 'lib/gemini/response.rb', line 388 def save_image(filepath) save_images([filepath]).first end |
#save_images(filepaths) ⇒ Object
複数の画像をファイルに保存
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 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 |
# File 'lib/gemini/response.rb', line 393 def save_images(filepaths) require 'base64' result = [] image_data = images puts "保存する画像データ数: #{image_data.size}" if ENV["DEBUG"] # ファイルパスと画像データの数が一致しない場合 if filepaths.size < image_data.size puts "警告: ファイルパスの数(#{filepaths.size})が画像データの数(#{image_data.size})より少ないです" if ENV["DEBUG"] # ファイルパスの数に合わせて画像データを切り詰める image_data = image_data[0...filepaths.size] elsif filepaths.size > image_data.size puts "警告: ファイルパスの数(#{filepaths.size})が画像データの数(#{image_data.size})より多いです" if ENV["DEBUG"] # 画像データの数に合わせてファイルパスを切り詰める filepaths = filepaths[0...image_data.size] end image_data.each_with_index do |data, i| begin if !data || data.empty? puts "警告: インデックス #{i} の画像データが空です" if ENV["DEBUG"] result << nil next end # データがBase64エンコードされていることを確認 if data.match?(/^[A-Za-z0-9+\/=]+$/) # 一般的なBase64データ decoded_data = Base64.strict_decode64(data) else # データプレフィックスがある場合など(例: data:image/png;base64,xxxxx) if data.include?('base64,') base64_part = data.split('base64,').last decoded_data = Base64.strict_decode64(base64_part) else puts "警告: インデックス #{i} のデータはBase64形式ではありません" if ENV["DEBUG"] decoded_data = data # 既にバイナリかもしれない end end File.open(filepaths[i], 'wb') do |f| f.write(decoded_data) end result << filepaths[i] rescue => e puts "エラー: 画像 #{i} の保存中にエラーが発生しました: #{e.}" if ENV["DEBUG"] puts e.backtrace.join("\n") if ENV["DEBUG"] result << nil end end result end |
#search_entry_point ⇒ Object
Get search entry point URL (if available)
159 160 161 |
# File 'lib/gemini/response.rb', line 159 def search_entry_point &.dig("searchEntryPoint", "renderedContent") end |
#stream_chunks ⇒ Object
Process chunks for streaming responses
236 237 238 239 240 |
# File 'lib/gemini/response.rb', line 236 def stream_chunks return [] unless @raw_data.is_a?(Array) @raw_data end |
#success? ⇒ Boolean
Check if response was successful
129 130 131 |
# File 'lib/gemini/response.rb', line 129 def success? valid? && !@raw_data.key?("error") end |
#text ⇒ Object
Get simple text response (combines multiple parts if present)
11 12 13 14 15 16 17 18 |
# File 'lib/gemini/response.rb', line 11 def text return nil unless valid? first_candidate&.dig("content", "parts") &.select { |part| part.key?("text") } &.map { |part| part["text"] } &.join("\n") || "" end |
#text_parts ⇒ Object
Get all text parts as an array
35 36 37 38 39 |
# File 'lib/gemini/response.rb', line 35 def text_parts return [] unless valid? parts.select { |part| part.key?("text") }.map { |part| part["text"] } end |
#thought_signatures ⇒ Object
Thought Signatureを取得(配列)
275 276 277 |
# File 'lib/gemini/response.rb', line 275 def thought_signatures parts.filter_map { |p| p['thoughtSignature'] } end |
#thoughts_token_count ⇒ Object
思考トークン数を取得
270 271 272 |
# File 'lib/gemini/response.rb', line 270 def thoughts_token_count @raw_data.dig('usageMetadata', 'thoughtsTokenCount') end |
#to_formatted_json(pretty: false) ⇒ Object
545 546 547 548 549 550 551 552 553 554 |
# File 'lib/gemini/response.rb', line 545 def to_formatted_json(pretty: false) json_data = json return nil unless json_data if pretty JSON.pretty_generate(json_data) else JSON.generate(json_data) end end |
#to_s ⇒ Object
Override to_s method to return text
450 451 452 |
# File 'lib/gemini/response.rb', line 450 def to_s text || error || "Empty response" end |
#total_tokens ⇒ Object
Get total tokens used
231 232 233 |
# File 'lib/gemini/response.rb', line 231 def total_tokens usage&.dig("totalTokens") || 0 end |
#url_context? ⇒ Boolean
Check if response has URL context metadata
190 191 192 |
# File 'lib/gemini/response.rb', line 190 def url_context? !.nil? && !.empty? end |
#url_context_metadata ⇒ Object
Get URL context metadata (for URL Context tool)
185 186 187 |
# File 'lib/gemini/response.rb', line 185 def first_candidate&.dig("urlContextMetadata") end |
#url_retrieval_statuses ⇒ Object
Get URL retrieval statuses
202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/gemini/response.rb', line 202 def url_retrieval_statuses return [] unless url_context? retrieved_urls.map do |url_info| { url: url_info["retrievedUrl"], status: url_info["urlRetrievalStatus"], title: url_info["title"] } end end |
#usage ⇒ Object
Get token usage information
216 217 218 |
# File 'lib/gemini/response.rb', line 216 def usage @raw_data&.dig("usage") || {} end |
#valid? ⇒ Boolean
Check if response is valid
72 73 74 75 76 77 |
# File 'lib/gemini/response.rb', line 72 def valid? !@raw_data.nil? && ((@raw_data.key?("candidates") && !@raw_data["candidates"].empty?) || (@raw_data.key?("predictions") && !@raw_data["predictions"].empty?) || ) end |