Class: Seo::GeminiBatchClient
- Inherits:
-
Object
- Object
- Seo::GeminiBatchClient
- Defined in:
- app/services/seo/gemini_batch_client.rb
Overview
Faraday client for the Gemini Batch API (v1beta).
Submits SEO analysis requests at 50% of standard Gemini pricing.
API reference: https://ai.google.dev/gemini-api/docs/batch-api
Defined Under Namespace
Classes: BatchError, RateLimitError
Constant Summary collapse
- BASE_URL =
'https://generativelanguage.googleapis.com'- API_VERSION =
'v1beta'- TERMINAL_STATES =
%w[ JOB_STATE_SUCCEEDED JOB_STATE_FAILED JOB_STATE_CANCELLED JOB_STATE_EXPIRED ].freeze
Class Method Summary collapse
-
.build_request(custom_id:, system_prompt: nil, user_prompt:, schema: nil, cached_content: nil, temperature: Seo::PageAnalysisService::TEMPERATURE, max_tokens: Seo::PageAnalysisService::MAX_OUTPUT_TOKENS) ⇒ Hash
Build a single inline batch request entry.
-
.normalize_schema(schema) ⇒ Object
Normalize the ANALYSIS_SCHEMA (Ruby symbol keys) into the Gemini-expected format (string keys, no additionalProperties which Gemini doesn't use).
Instance Method Summary collapse
-
#cancel_batch(batch_name) ⇒ Hash
Cancel a batch job in progress.
-
#create_cache(model:, system_prompt:, ttl: '14400s') ⇒ String
Create a cached content object for the system prompt.
-
#create_inline_batch(model, requests, display_name: nil) ⇒ Hash
Create a batch job with inline requests (suitable for batches <20MB).
-
#delete_cache(cache_name) ⇒ Object
Delete a cached content object to stop ongoing storage charges.
-
#download_results(file_name) ⇒ String
Download results from a file-based batch (for future use with large batches).
-
#get_batch(batch_name) ⇒ Hash
Retrieve the current status of a batch job.
-
#initialize(api_key: nil) ⇒ GeminiBatchClient
constructor
A new instance of GeminiBatchClient.
-
#inline_responses(batch_response) ⇒ Array<Hash>
Extract inline responses from a completed batch.
-
#state(batch_response) ⇒ Object
Extract the state string from a batch response.
-
#terminal?(batch_response) ⇒ Boolean
Check if a batch job has reached a terminal state.
Constructor Details
#initialize(api_key: nil) ⇒ GeminiBatchClient
Returns a new instance of GeminiBatchClient.
29 30 31 32 |
# File 'app/services/seo/gemini_batch_client.rb', line 29 def initialize(api_key: nil) @api_key = api_key || Rails.application.credentials.dig(:google, :gemini, :api_key) raise ArgumentError, 'Gemini API key is required' if @api_key.blank? end |
Class Method Details
.build_request(custom_id:, system_prompt: nil, user_prompt:, schema: nil, cached_content: nil, temperature: Seo::PageAnalysisService::TEMPERATURE, max_tokens: Seo::PageAnalysisService::MAX_OUTPUT_TOKENS) ⇒ Hash
Build a single inline batch request entry.
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'app/services/seo/gemini_batch_client.rb', line 152 def self.build_request(custom_id:, system_prompt: nil, user_prompt:, schema: nil, cached_content: nil, temperature: Seo::PageAnalysisService::TEMPERATURE, max_tokens: Seo::PageAnalysisService::MAX_OUTPUT_TOKENS) request_body = { contents: [ { role: 'user', parts: [{ text: user_prompt }] } ], generationConfig: { temperature: temperature, maxOutputTokens: max_tokens, responseMimeType: 'application/json' } } if cached_content request_body[:cachedContent] = cached_content elsif system_prompt request_body[:systemInstruction] = { parts: [{ text: system_prompt }] } end if schema request_body[:generationConfig][:responseJsonSchema] = normalize_schema(schema) end { request: request_body, metadata: { key: custom_id } } end |
.normalize_schema(schema) ⇒ Object
Normalize the ANALYSIS_SCHEMA (Ruby symbol keys) into the Gemini-expected
format (string keys, no additionalProperties which Gemini doesn't use).
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'app/services/seo/gemini_batch_client.rb', line 185 def self.normalize_schema(schema) deep_stringify = ->(obj) do case obj when Hash obj.each_with_object({}) do |(k, v), h| next if k.to_s == 'additionalProperties' h[k.to_s] = deep_stringify.call(v) end when Array obj.map { |v| deep_stringify.call(v) } else obj end end deep_stringify.call(schema) end |
Instance Method Details
#cancel_batch(batch_name) ⇒ Hash
Cancel a batch job in progress.
106 107 108 109 |
# File 'app/services/seo/gemini_batch_client.rb', line 106 def cancel_batch(batch_name) response = connection.post("/#{API_VERSION}/#{batch_name}:cancel") handle_response(response) end |
#create_cache(model:, system_prompt:, ttl: '14400s') ⇒ String
Create a cached content object for the system prompt.
Returns the cache name (e.g. "cachedContents/abc123") for use in requests.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'app/services/seo/gemini_batch_client.rb', line 118 def create_cache(model:, system_prompt:, ttl: '14400s') body = { model: "models/#{model}", systemInstruction: { parts: [{ text: system_prompt }] }, ttl: ttl } response = connection.post( "/#{API_VERSION}/cachedContents", body.to_json ) result = handle_response(response) result['name'] end |
#create_inline_batch(model, requests, display_name: nil) ⇒ Hash
Create a batch job with inline requests (suitable for batches <20MB).
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'app/services/seo/gemini_batch_client.rb', line 40 def create_inline_batch(model, requests, display_name: nil) display_name ||= "seo-batch-#{Time.current.strftime('%Y%m%d-%H%M')}" body = { batch: { display_name: display_name, input_config: { requests: { requests: requests } } } } response = connection.post( "/#{API_VERSION}/models/#{model}:batchGenerateContent", body.to_json ) handle_response(response) end |
#delete_cache(cache_name) ⇒ Object
Delete a cached content object to stop ongoing storage charges.
137 138 139 140 |
# File 'app/services/seo/gemini_batch_client.rb', line 137 def delete_cache(cache_name) response = connection.delete("/#{API_VERSION}/#{cache_name}") raise BatchError, "Failed to delete cache: #{response.status}" unless response.success? end |
#download_results(file_name) ⇒ String
Download results from a file-based batch (for future use with large batches).
94 95 96 97 98 99 100 |
# File 'app/services/seo/gemini_batch_client.rb', line 94 def download_results(file_name) response = connection.get("/download/#{API_VERSION}/#{file_name}:download", alt: 'media') raise BatchError, "Failed to download results: #{response.status}" unless response.success? response.body end |
#get_batch(batch_name) ⇒ Hash
Retrieve the current status of a batch job.
66 67 68 69 |
# File 'app/services/seo/gemini_batch_client.rb', line 66 def get_batch(batch_name) response = connection.get("/#{API_VERSION}/#{batch_name}") handle_response(response) end |
#inline_responses(batch_response) ⇒ Array<Hash>
Extract inline responses from a completed batch.
86 87 88 |
# File 'app/services/seo/gemini_batch_client.rb', line 86 def inline_responses(batch_response) batch_response.dig('response', 'inlinedResponses') || [] end |
#state(batch_response) ⇒ Object
Extract the state string from a batch response.
78 79 80 |
# File 'app/services/seo/gemini_batch_client.rb', line 78 def state(batch_response) batch_response.dig('metadata', 'state') end |
#terminal?(batch_response) ⇒ Boolean
Check if a batch job has reached a terminal state.
72 73 74 75 |
# File 'app/services/seo/gemini_batch_client.rb', line 72 def terminal?(batch_response) state = batch_response.dig('metadata', 'state') TERMINAL_STATES.include?(state) end |