Module: RubyLLM::Providers::OpenAI::Capabilities
- Defined in:
- lib/ruby_llm/providers/openai/capabilities.rb
Overview
Determines capabilities and pricing for OpenAI models
Constant Summary collapse
- MODEL_PATTERNS =
{ dall_e: /^dall-e/, chatgpt4o: /^chatgpt-4o/, gpt41: /^gpt-4\.1(?!-(?:mini|nano))/, gpt41_mini: /^gpt-4\.1-mini/, gpt41_nano: /^gpt-4\.1-nano/, gpt4: /^gpt-4(?:-\d{6})?$/, gpt4_turbo: /^gpt-4(?:\.5)?-(?:\d{6}-)?(preview|turbo)/, gpt35_turbo: /^gpt-3\.5-turbo/, gpt4o: /^gpt-4o(?!-(?:mini|audio|realtime|transcribe|tts|search))/, gpt4o_audio: /^gpt-4o-(?:audio)/, gpt4o_mini: /^gpt-4o-mini(?!-(?:audio|realtime|transcribe|tts|search))/, gpt4o_mini_audio: /^gpt-4o-mini-audio/, gpt4o_mini_realtime: /^gpt-4o-mini-realtime/, gpt4o_mini_transcribe: /^gpt-4o-mini-transcribe/, gpt4o_mini_tts: /^gpt-4o-mini-tts/, gpt4o_realtime: /^gpt-4o-realtime/, gpt4o_search: /^gpt-4o-search/, gpt4o_transcribe: /^gpt-4o-transcribe/, gpt5: /^gpt-5/, gpt5_mini: /^gpt-5-mini/, gpt5_nano: /^gpt-5-nano/, o1: /^o1(?!-(?:mini|pro))/, o1_mini: /^o1-mini/, o1_pro: /^o1-pro/, o3_mini: /^o3-mini/, babbage: /^babbage/, davinci: /^davinci/, embedding3_large: /^text-embedding-3-large/, embedding3_small: /^text-embedding-3-small/, embedding_ada: /^text-embedding-ada/, tts1: /^tts-1(?!-hd)/, tts1_hd: /^tts-1-hd/, whisper: /^whisper/, moderation: /^(?:omni|text)-moderation/ }.freeze
- PRICES =
{ gpt5: { input: 1.25, output: 10.0, cached_input: 0.125 }, gpt5_mini: { input: 0.25, output: 2.0, cached_input: 0.025 }, gpt5_nano: { input: 0.05, output: 0.4, cached_input: 0.005 }, gpt41: { input: 2.0, output: 8.0, cached_input: 0.5 }, gpt41_mini: { input: 0.4, output: 1.6, cached_input: 0.1 }, gpt41_nano: { input: 0.1, output: 0.4 }, chatgpt4o: { input: 5.0, output: 15.0 }, gpt4: { input: 10.0, output: 30.0 }, gpt4_turbo: { input: 10.0, output: 30.0 }, gpt45: { input: 75.0, output: 150.0 }, gpt35_turbo: { input: 0.5, output: 1.5 }, gpt4o: { input: 2.5, output: 10.0 }, gpt4o_audio: { input: 2.5, output: 10.0, audio_input: 40.0, audio_output: 80.0 }, gpt4o_mini: { input: 0.15, output: 0.6 }, gpt4o_mini_audio: { input: 0.15, output: 0.6, audio_input: 10.0, audio_output: 20.0 }, gpt4o_mini_realtime: { input: 0.6, output: 2.4 }, gpt4o_mini_transcribe: { input: 1.25, output: 5.0, audio_input: 3.0 }, gpt4o_mini_tts: { input: 0.6, output: 12.0 }, gpt4o_realtime: { input: 5.0, output: 20.0 }, gpt4o_search: { input: 2.5, output: 10.0 }, gpt4o_transcribe: { input: 2.5, output: 10.0, audio_input: 6.0 }, o1: { input: 15.0, output: 60.0 }, o1_mini: { input: 1.1, output: 4.4 }, o1_pro: { input: 150.0, output: 600.0 }, o3_mini: { input: 1.1, output: 4.4 }, babbage: { input: 0.4, output: 0.4 }, davinci: { input: 2.0, output: 2.0 }, embedding3_large: { price: 0.13 }, embedding3_small: { price: 0.02 }, embedding_ada: { price: 0.10 }, tts1: { price: 15.0 }, tts1_hd: { price: 30.0 }, whisper: { price: 0.006 }, moderation: { price: 0.0 } }.freeze
Class Method Summary collapse
- .apply_special_formatting(name) ⇒ Object
- .cached_input_price_for(model_id) ⇒ Object
-
.capabilities_for(model_id) ⇒ Object
rubocop:disable Metrics/PerceivedComplexity.
- .context_window_for(model_id) ⇒ Object
- .default_input_price ⇒ Object
- .default_output_price ⇒ Object
- .format_display_name(model_id) ⇒ Object
- .humanize(id) ⇒ Object
- .input_price_for(model_id) ⇒ Object
- .max_tokens_for(model_id) ⇒ Object
- .modalities_for(model_id) ⇒ Object
- .model_family(model_id) ⇒ Object
- .model_type(model_id) ⇒ Object
- .normalize_temperature(temperature, model_id) ⇒ Object
- .output_price_for(model_id) ⇒ Object
- .pricing_for(model_id) ⇒ Object
- .special_prefix_format(prefix) ⇒ Object
- .supports_functions?(model_id) ⇒ Boolean
- .supports_json_mode?(model_id) ⇒ Boolean
- .supports_structured_output?(model_id) ⇒ Boolean
- .supports_vision?(model_id) ⇒ Boolean
Class Method Details
.apply_special_formatting(name) ⇒ Object
204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 204 def apply_special_formatting(name) name .gsub(/(\d{4}) (\d{2}) (\d{2})/, '\1\2\3') .gsub(/^(?:Gpt|Chatgpt|Tts|Dall E) /) { |m| special_prefix_format(m.strip) } .gsub(/^O([13]) /, 'O\1-') .gsub(/^O[13] Mini/, '\0'.tr(' ', '-')) .gsub(/\d\.\d /, '\0'.sub(' ', '-')) .gsub(/4o (?=Mini|Preview|Turbo|Audio|Realtime|Transcribe|Tts)/, '4o-') .gsub(/\bHd\b/, 'HD') .gsub(/(?:Omni|Text) Moderation/, '\0'.tr(' ', '-')) .gsub('Text Embedding', 'text-embedding-') end |
.cached_input_price_for(model_id) ⇒ Object
162 163 164 165 166 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 162 def cached_input_price_for(model_id) family = model_family(model_id).to_sym prices = PRICES.fetch(family, {}) prices[:cached_input] end |
.capabilities_for(model_id) ⇒ Object
rubocop:disable Metrics/PerceivedComplexity
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 256 def capabilities_for(model_id) # rubocop:disable Metrics/PerceivedComplexity capabilities = [] capabilities << 'streaming' unless model_id.match?(/moderation|embedding/) capabilities << 'function_calling' if supports_functions?(model_id) capabilities << 'structured_output' if supports_json_mode?(model_id) capabilities << 'batch' if model_id.match?(/embedding|batch/) capabilities << 'reasoning' if model_id.match?(/o\d|gpt-5|codex/) if model_id.match?(/gpt-4-turbo|gpt-4o/) capabilities << 'image_generation' if model_id.match?(/vision/) capabilities << 'speech_generation' if model_id.match?(/audio/) capabilities << 'transcription' if model_id.match?(/audio/) end capabilities end |
.context_window_for(model_id) ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 47 def context_window_for(model_id) case model_family(model_id) when 'gpt41', 'gpt41_mini', 'gpt41_nano' then 1_047_576 when 'gpt5', 'gpt5_mini', 'gpt5_nano', 'chatgpt4o', 'gpt4_turbo', 'gpt4o', 'gpt4o_audio', 'gpt4o_mini', 'gpt4o_mini_audio', 'gpt4o_mini_realtime', 'gpt4o_realtime', 'gpt4o_search', 'gpt4o_transcribe', 'gpt4o_mini_search', 'o1_mini' then 128_000 when 'gpt4' then 8_192 when 'gpt4o_mini_transcribe' then 16_000 when 'o1', 'o1_pro', 'o3_mini' then 200_000 when 'gpt35_turbo' then 16_385 when 'gpt4o_mini_tts', 'tts1', 'tts1_hd', 'whisper', 'moderation', 'embedding3_large', 'embedding3_small', 'embedding_ada' then nil else 4_096 end end |
.default_input_price ⇒ Object
184 185 186 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 184 def default_input_price 0.50 end |
.default_output_price ⇒ Object
188 189 190 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 188 def default_output_price 1.50 end |
.format_display_name(model_id) ⇒ Object
192 193 194 195 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 192 def format_display_name(model_id) model_id.then { |id| humanize(id) } .then { |name| apply_special_formatting(name) } end |
.humanize(id) ⇒ Object
197 198 199 200 201 202 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 197 def humanize(id) id.tr('-', ' ') .split .map(&:capitalize) .join(' ') end |
.input_price_for(model_id) ⇒ Object
156 157 158 159 160 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 156 def input_price_for(model_id) family = model_family(model_id).to_sym prices = PRICES.fetch(family, { input: default_input_price }) prices[:input] || prices[:price] || default_input_price end |
.max_tokens_for(model_id) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 63 def max_tokens_for(model_id) case model_family(model_id) when 'gpt5', 'gpt5_mini', 'gpt5_nano' then 400_000 when 'gpt41', 'gpt41_mini', 'gpt41_nano' then 32_768 when 'chatgpt4o', 'gpt4o', 'gpt4o_mini', 'gpt4o_mini_search' then 16_384 when 'babbage', 'davinci' then 16_384 # rubocop:disable Lint/DuplicateBranch when 'gpt4' then 8_192 when 'gpt35_turbo' then 4_096 when 'gpt4_turbo', 'gpt4o_realtime', 'gpt4o_mini_realtime' then 4_096 # rubocop:disable Lint/DuplicateBranch when 'gpt4o_mini_transcribe' then 2_000 when 'o1', 'o1_pro', 'o3_mini' then 100_000 when 'o1_mini' then 65_536 when 'gpt4o_mini_tts', 'tts1', 'tts1_hd', 'whisper', 'moderation', 'embedding3_large', 'embedding3_small', 'embedding_ada' then nil else 16_384 # rubocop:disable Lint/DuplicateBranch end end |
.modalities_for(model_id) ⇒ Object
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 238 def modalities_for(model_id) modalities = { input: ['text'], output: ['text'] } # Vision support modalities[:input] << 'image' if supports_vision?(model_id) modalities[:input] << 'audio' if model_id.match?(/whisper|audio|tts|transcribe/) modalities[:input] << 'pdf' if supports_vision?(model_id) modalities[:output] << 'audio' if model_id.match?(/tts|audio/) modalities[:output] << 'image' if model_id.match?(/dall-e|image/) modalities[:output] << 'embeddings' if model_id.match?(/embedding/) modalities[:output] << 'moderation' if model_id.match?(/moderation/) modalities end |
.model_family(model_id) ⇒ Object
149 150 151 152 153 154 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 149 def model_family(model_id) MODEL_PATTERNS.each do |family, pattern| return family.to_s if model_id.match?(pattern) end 'other' end |
.model_type(model_id) ⇒ Object
174 175 176 177 178 179 180 181 182 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 174 def model_type(model_id) case model_family(model_id) when /embedding/ then 'embedding' when /^tts|whisper|gpt4o_(?:mini_)?(?:transcribe|tts)$/ then 'audio' when 'moderation' then 'moderation' when /dall/ then 'image' else 'chat' end end |
.normalize_temperature(temperature, model_id) ⇒ Object
226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 226 def self.normalize_temperature(temperature, model_id) if model_id.match?(/^(o\d|gpt-5)/) RubyLLM.logger.debug "Model #{model_id} requires temperature=1.0, ignoring provided value" 1.0 elsif model_id.match?(/-search/) RubyLLM.logger.debug "Model #{model_id} does not accept temperature parameter, removing" nil else temperature end end |
.output_price_for(model_id) ⇒ Object
168 169 170 171 172 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 168 def output_price_for(model_id) family = model_family(model_id).to_sym prices = PRICES.fetch(family, { output: default_output_price }) prices[:output] || prices[:price] || default_output_price end |
.pricing_for(model_id) ⇒ Object
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 274 def pricing_for(model_id) standard_pricing = { input_per_million: input_price_for(model_id), output_per_million: output_price_for(model_id) } if respond_to?(:cached_input_price_for) cached_price = cached_input_price_for(model_id) standard_pricing[:cached_input_per_million] = cached_price if cached_price end pricing = { text_tokens: { standard: standard_pricing } } if model_id.match?(/embedding|batch/) pricing[:text_tokens][:batch] = { input_per_million: standard_pricing[:input_per_million] * 0.5, output_per_million: standard_pricing[:output_per_million] * 0.5 } end pricing end |
.special_prefix_format(prefix) ⇒ Object
217 218 219 220 221 222 223 224 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 217 def special_prefix_format(prefix) case prefix # rubocop:disable Style/HashLikeCase when 'Gpt' then 'GPT-' when 'Chatgpt' then 'ChatGPT-' when 'Tts' then 'TTS-' when 'Dall E' then 'DALL-E-' end end |
.supports_functions?(model_id) ⇒ Boolean
90 91 92 93 94 95 96 97 98 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 90 def supports_functions?(model_id) case model_family(model_id) when 'gpt5', 'gpt5_mini', 'gpt5_nano', 'gpt41', 'gpt41_mini', 'gpt41_nano', 'gpt4', 'gpt4_turbo', 'gpt4o', 'gpt4o_mini', 'o1', 'o1_pro', 'o3_mini' then true when 'chatgpt4o', 'gpt35_turbo', 'o1_mini', 'gpt4o_mini_tts', 'gpt4o_transcribe', 'gpt4o_search', 'gpt4o_mini_search' then false else false # rubocop:disable Lint/DuplicateBranch end end |
.supports_json_mode?(model_id) ⇒ Boolean
108 109 110 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 108 def supports_json_mode?(model_id) supports_structured_output?(model_id) end |
.supports_structured_output?(model_id) ⇒ Boolean
100 101 102 103 104 105 106 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 100 def supports_structured_output?(model_id) case model_family(model_id) when 'gpt5', 'gpt5_mini', 'gpt5_nano', 'gpt41', 'gpt41_mini', 'gpt41_nano', 'chatgpt4o', 'gpt4o', 'gpt4o_mini', 'o1', 'o1_pro', 'o3_mini' then true else false end end |
.supports_vision?(model_id) ⇒ Boolean
81 82 83 84 85 86 87 88 |
# File 'lib/ruby_llm/providers/openai/capabilities.rb', line 81 def supports_vision?(model_id) case model_family(model_id) when 'gpt5', 'gpt5_mini', 'gpt5_nano', 'gpt41', 'gpt41_mini', 'gpt41_nano', 'chatgpt4o', 'gpt4', 'gpt4_turbo', 'gpt4o', 'gpt4o_mini', 'o1', 'o1_pro', 'moderation', 'gpt4o_search', 'gpt4o_mini_search' then true else false end end |