Class: Nuntius::Client
- Inherits:
-
Object
- Object
- Nuntius::Client
- Defined in:
- lib/core/client.rb
Overview
Core client class for Gemini AI API communication
Constant Summary collapse
- BASE_URL =
'https://generativelanguage.googleapis.com/v1beta/models'- CANONICAL_PATH =
Canonical model source: config/models.yaml (repository-wide) This file is the single source of truth. Code must not duplicate model names.
File.('../../config/models.yaml', __dir__)
- CANONICAL =
begin if File.exist?(CANONICAL_PATH) YAML.safe_load(File.read(CANONICAL_PATH)) || {} else {} end rescue StandardError {} end.freeze
- CANONICAL_DEFAULT =
(CANONICAL['default'] || 'gemini-3.7-flash').freeze
- CANONICAL_FALLBACK =
(CANONICAL['fallback'] || 'gemini-3.6-flash').freeze
- CANONICAL_STABLE =
(CANONICAL['stable_baseline'] || 'gemini-3.5-flash').freeze
- CANONICAL_LIGHTWEIGHT =
(CANONICAL['lightweight'] || ['gemini-3.1-flash-lite', 'gemini-3.5-flash-lite']).freeze
- CANONICAL_LIGHT =
CANONICAL_LIGHTWEIGHT.first || 'gemini-3.1-flash-lite'
- MODELS =
generateContent text model aliases: Flash family only (Free-Tier compatible). Values are derived from config/models.yaml. Do not hard-code elsewhere.
{ flash_latest: 'gemini-flash-latest', flash_3_7: CANONICAL_DEFAULT, flash_3_6: CANONICAL_FALLBACK, flash_3_5: CANONICAL_STABLE, flash_3_5_lite: CANONICAL_LIGHTWEIGHT[1] || 'gemini-3.5-flash-lite', flash_3_preview: 'gemini-3-flash-preview', flash_3_1_lite: CANONICAL_LIGHT, flash_2_5: 'gemini-2.5-flash', flash_2_0: 'gemini-2.0-flash', # Short aliases: default Flash is 3.7, fallback 3.6. flash: CANONICAL_DEFAULT, flash_fallback: CANONICAL_FALLBACK, flash_lite: CANONICAL_LIGHT, pro_2_0: 'gemini-2.0-flash' # legacy alias, resolves to Flash (gemini-2.0-flash), keep for backward compat }.freeze
- DEPRECATED_MODELS =
Deprecated/retired models: Pro family requires billing, not usable on Free-Tier. Kept for backward compat but warn and default to :flash (gemini-3.7-flash).
{ pro_latest: 'gemini-pro-latest', pro: 'gemini-pro-latest', pro_3_preview: 'gemini-3-pro-preview', pro_3_1_preview: 'gemini-3.1-pro-preview', pro_2_5: 'gemini-2.5-pro', pro_1_5: 'gemini-1.5-pro', flash_1_5: 'gemini-1.5-flash', flash_8b: 'gemini-1.5-flash-8b' }.freeze
Class Method Summary collapse
-
.logger ⇒ Object
Configure logging.
Instance Method Summary collapse
- #chat(messages, options = {}) ⇒ Object
- #generate_image_text(image_base64, prompt, options = {}) ⇒ Object
- #generate_text(prompt, options = {}) ⇒ Object
-
#initialize(api_key = nil, model: :flash) ⇒ Client
constructor
A new instance of Client.
Constructor Details
#initialize(api_key = nil, model: :flash) ⇒ Client
Returns a new instance of Client.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/core/client.rb', line 83 def initialize(api_key = nil, model: :flash) # Prioritize passed API key, then environment variable @api_key = api_key || ENV.fetch('GEMINI_API_KEY', nil) # Rate limiting - track last request time @last_request_time = nil # More conservative rate limiting in CI environments @min_request_interval = ENV['CI'] == 'true' || ENV['GITHUB_ACTIONS'] == 'true' ? 3.0 : 1.0 # Extensive logging for debugging self.class.logger.debug('Initializing Client') self.class.logger.debug("API Key present: #{!@api_key.nil?}") self.class.logger.debug("API Key length: #{@api_key&.length}") # Validate API key before proceeding validate_api_key! @model = resolve_model(model) self.class.logger.debug("Selected model: #{@model}") end |
Class Method Details
.logger ⇒ Object
Configure logging
72 73 74 75 76 77 78 79 80 81 |
# File 'lib/core/client.rb', line 72 def self.logger @logger ||= Logger.new($stdout).tap do |log| log.level = Logger::DEBUG log.formatter = proc do |severity, datetime, _progname, msg| # Mask any potential API key in logs masked_msg = msg.to_s.gsub(/AIza[a-zA-Z0-9_-]{35,}/, '[REDACTED]') "#{datetime}: #{severity} -- #{masked_msg}\n" end end end |
Instance Method Details
#chat(messages, options = {}) ⇒ Object
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/core/client.rb', line 143 def chat(, = {}) request_body = { contents: .map { |msg| { role: msg[:role], parts: [{ text: msg[:content] }] } }, generationConfig: build_generation_config() } # Add system instruction if provided if [:system_instruction] request_body[:systemInstruction] = { parts: [ { text: [:system_instruction] } ] } end apply_moderation(send_request(request_body), ) end |
#generate_image_text(image_base64, prompt, options = {}) ⇒ Object
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/core/client.rb', line 126 def generate_image_text(image_base64, prompt, = {}) raise Error, 'Image is required' if image_base64.nil? || image_base64.empty? request_body = { contents: [ { parts: [ { inline_data: { mime_type: 'image/jpeg', data: image_base64 } }, { text: prompt } ] } ], generationConfig: build_generation_config() } # Use the flash model for image-to-text tasks (Free-Tier compatible) apply_moderation(send_request(request_body, model: :flash), ) end |
#generate_text(prompt, options = {}) ⇒ Object
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/core/client.rb', line 105 def generate_text(prompt, = {}) validate_prompt!(prompt) request_body = { contents: [{ parts: [{ text: prompt }] }], generationConfig: build_generation_config() } # Add safety settings if provided if [:safety_settings] request_body[:safetySettings] = [:safety_settings].map do |setting| { category: setting[:category], threshold: setting[:threshold] } end end apply_moderation(send_request(request_body), ) end |