Class: RubyLLM::Models
- Inherits:
-
Object
- Object
- RubyLLM::Models
- Includes:
- Enumerable
- Defined in:
- lib/ruby_llm/models.rb,
lib/ruby_llm/models/schema.rb,
lib/ruby_llm/models/aliases.rb,
lib/ruby_llm/models/registry.rb
Overview
A Models registry is the catalog of AI models RubyLLM knows about, including their capabilities, context windows, and pricing. The global registry is available through RubyLLM.models.
RubyLLM.models.find 'claude-sonnet-5'
RubyLLM.models.by_provider(:openai).chat_models
RubyLLM.models.refresh
Filter methods return new Models instances, so calls chain. Models is enumerable over its Model entries. Class-level calls such as Models.find delegate to the global registry.
Defined Under Namespace
Modules: Registry Classes: Aliases, Schema
Constant Summary collapse
- MODELS_DEV_PROVIDER_MAP =
:nodoc:
{ # :nodoc: 'openai' => 'openai', 'anthropic' => 'anthropic', 'google' => 'gemini', 'google-vertex' => 'vertexai', 'amazon-bedrock' => 'bedrock', 'cohere' => 'cohere', 'deepseek' => 'deepseek', 'mistral' => 'mistral', 'ollama-cloud' => 'ollama_cloud', 'openrouter' => 'openrouter', 'perplexity' => 'perplexity', 'perplexity-agent' => 'perplexity', 'xai' => 'xai' }.freeze
- MODELS_DEV_INPUT_MODALITIES =
:nodoc:
%w[text image audio pdf video file].freeze
- MODELS_DEV_OUTPUT_MODALITIES =
:nodoc:
%w[text image audio video embeddings moderation rerank].freeze
- PROVIDER_PREFERENCE =
First-party providers outrank the aggregators that resell their models.
%w[ openai anthropic gemini deepseek mistral cohere perplexity xai vertexai bedrock openrouter azure ollama_cloud ollama gpustack ].freeze
- INSTANCE_DELEGATES =
:nodoc:
(Enumerable.instance_methods(false) + %i[ all each find listed unlisted chat_models embedding_models audio_models image_models by_family by_provider load_from_json load_from_store save_to_json ]).uniq.freeze
Class Attribute Summary collapse
-
.last_provider_failures ⇒ Object
readonly
The providers whose model list could not be fetched during the last refresh, as hashes of
:name,:slug, and:error.
Class Method Summary collapse
-
.add_provider_metadata(models_dev_model, provider_model) ⇒ Object
rubocop:disable Metrics/PerceivedComplexity.
-
.augment_capabilities(provider_slug, capabilities, model_id, modalities) ⇒ Object
:nodoc:.
-
.augment_model_capabilities(model) ⇒ Object
:nodoc:.
-
.blank_value?(value) ⇒ Boolean
:nodoc:.
-
.bundled_registry_file ⇒ Object
:nodoc:.
-
.fetch_merged_models(remote_only: false) ⇒ Object
Fetches and merges models directly from upstream provider APIs and models.dev for the maintainer registry builder.
-
.fetch_models_dev_models(existing_models) ⇒ Object
:nodoc:.
-
.fetch_provider_models(remote_only: true) ⇒ Object
:nodoc:.
-
.fetch_published_registry(etag: nil) ⇒ Object
:nodoc:.
-
.find_models_dev_model(key, models_dev_by_key, provider_model = nil) ⇒ Object
:nodoc:.
-
.index_by_key(models) ⇒ Object
:nodoc:.
-
.index_provider_aliases(models) ⇒ Object
:nodoc:.
-
.instance ⇒ Object
:nodoc:.
-
.load_models ⇒ Object
:nodoc:.
-
.log_models_dev_fetch(models_dev_fetch) ⇒ Object
:nodoc:.
-
.log_provider_fetch(provider_fetch) ⇒ Object
:nodoc:.
-
.merge_capabilities(models_dev_model, provider_model, modalities) ⇒ Object
:nodoc:.
-
.merge_models(provider_models, models_dev_models) ⇒ Object
:nodoc:.
-
.merge_with_existing(existing_models, provider_fetch, models_dev_fetch) ⇒ Object
:nodoc:.
-
.models_dev_capabilities(model_data, modalities, provider_slug) ⇒ Object
:nodoc:.
-
.models_dev_metadata(model_data, provider_key) ⇒ Object
:nodoc:.
-
.models_dev_model_attributes(model_data, provider_slug, provider_key) ⇒ Object
:nodoc:.
-
.models_dev_model_id(id, provider_slug) ⇒ Object
:nodoc:.
-
.models_dev_pricing(cost) ⇒ Object
:nodoc:.
-
.models_dev_provider_models(provider_key, provider_data) ⇒ Object
:nodoc:.
-
.models_dev_reported_capabilities(models_dev_model) ⇒ Object
models.dev leaves a field out where it has no opinion, so only the capabilities it reports on can overrule what a provider claims.
-
.models_dev_text_tokens_pricing(text_standard, cost) ⇒ Object
:nodoc:.
-
.models_from_bundle ⇒ Object
:nodoc:.
-
.models_from_file(file) ⇒ Object
:nodoc:.
-
.models_from_provider_gems ⇒ Object
:nodoc:.
-
.models_from_store(store) ⇒ Object
:nodoc:.
-
.normalize_embedding_modalities(data) ⇒ Object
:nodoc:.
-
.normalize_models_dev_knowledge(value) ⇒ Object
:nodoc:.
-
.normalize_models_dev_modalities(modalities) ⇒ Object
:nodoc:.
-
.parse_models_dev_catalog(body) ⇒ Object
An answer RubyLLM cannot read a single model out of is no answer: only a catalog carrying models may overrule what the registry holds.
-
.read_existing_models ⇒ Object
:nodoc:.
-
.refresh(remote_only: false) ⇒ Object
Refreshes the global model registry from the published catalog and configured providers.
-
.refresh_from_providers(remote_only: false) ⇒ Object
:nodoc:.
- .resolve(model_id, provider: nil, assume_model_exists: false, config: nil, operation: nil, default_model: nil) ⇒ Object
Instance Method Summary collapse
-
#all ⇒ Object
(also: #listed)
Returns an array of the Model entries the configured provider still lists.
-
#all_including_unlisted ⇒ Object
:nodoc:.
-
#audio_models ⇒ Object
Returns a new Models registry containing only models with audio output.
-
#by_family(family) ⇒ Object
Returns a new Models registry containing only models in
family. -
#by_provider(provider) ⇒ Object
Returns a new Models registry containing only models from
provider. -
#chat_models ⇒ Object
Returns a new Models registry containing only chat models.
-
#each ⇒ Object
Yields each Model in the registry.
-
#embedding_models ⇒ Object
Returns a new Models registry containing only embedding models.
-
#find(model_id, provider: nil, config: nil) ⇒ Object
Returns the Model matching
model_id, resolving aliases along the way. -
#image_models ⇒ Object
Returns a new Models registry containing only models with image output.
-
#initialize(models = nil) ⇒ Models
constructor
:startdoc:.
-
#load_from_json(file = RubyLLM.config.model_registry_file) ⇒ Object
Replaces the models in this registry with those read from the JSON
file. -
#load_from_store ⇒ Object
Replaces the models in this registry with entries from the configured model-registry store.
-
#refresh(remote_only: false) ⇒ Object
Replaces the registry with the latest published RubyLLM catalog, merged with models discovered from configured providers.
-
#refresh_from_providers(remote_only: false) ⇒ Object
:nodoc:.
-
#resolve(model_id, provider: nil, assume_model_exists: false, config: nil) ⇒ Object
:nodoc:.
-
#save_to_json(file = RubyLLM.config.model_registry_file) ⇒ Object
Exports this registry to
fileas pretty-printed JSON. -
#unlisted ⇒ Object
Returns an array of the Model entries the configured provider has stopped listing.
Constructor Details
#initialize(models = nil) ⇒ Models
:startdoc:
542 543 544 |
# File 'lib/ruby_llm/models.rb', line 542 def initialize(models = nil) # :nodoc: @models = models || self.class.load_models end |
Class Attribute Details
.last_provider_failures ⇒ Object (readonly)
The providers whose model list could not be fetched during the last
refresh, as hashes of :name, :slug, and :error. Their previous
models are kept, so an unreported failure leaves stale entries behind.
76 77 78 |
# File 'lib/ruby_llm/models.rb', line 76 def last_provider_failures @last_provider_failures end |
Class Method Details
.add_provider_metadata(models_dev_model, provider_model) ⇒ Object
rubocop:disable Metrics/PerceivedComplexity
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 |
# File 'lib/ruby_llm/models.rb', line 349 def (models_dev_model, provider_model) # rubocop:disable Metrics/PerceivedComplexity data = models_dev_model.to_h data[:name] = provider_model.name if blank_value?(data[:name]) data[:family] = provider_model.family if blank_value?(data[:family]) data[:created_at] = provider_model.created_at if blank_value?(data[:created_at]) data[:context_window] = provider_model.context_window if blank_value?(data[:context_window]) data[:max_output_tokens] = provider_model.max_output_tokens if blank_value?(data[:max_output_tokens]) data[:knowledge_cutoff] = provider_model.knowledge_cutoff if blank_value?(data[:knowledge_cutoff]) data[:modalities] = provider_model.modalities.to_h if blank_value?(data[:modalities]) if models_dev_model.type == :chat && provider_model.type != :chat data[:modalities] = provider_model.modalities.to_h end data[:pricing] = Support::Utils.deep_merge(provider_model.pricing.to_h, data[:pricing].to_h) data[:metadata] = provider_model..merge(data[:metadata] || {}) data[:capabilities] = merge_capabilities(models_dev_model, provider_model, data[:modalities]) (data) Model.new(data) end |
.augment_capabilities(provider_slug, capabilities, model_id, modalities) ⇒ Object
:nodoc:
381 382 383 384 385 386 |
# File 'lib/ruby_llm/models.rb', line 381 def augment_capabilities(provider_slug, capabilities, model_id, modalities) # :nodoc: augmenter = Provider.resolve(provider_slug)&.capabilities return capabilities unless augmenter augmenter.augment(capabilities, model_id: model_id, modalities: modalities.to_h) end |
.augment_model_capabilities(model) ⇒ Object
:nodoc:
374 375 376 377 378 379 |
# File 'lib/ruby_llm/models.rb', line 374 def augment_model_capabilities(model) # :nodoc: capabilities = augment_capabilities(model.provider, model.capabilities, model.id, model.modalities.to_h) return model if capabilities == model.capabilities Model.new(model.to_h.merge(capabilities: capabilities)) end |
.blank_value?(value) ⇒ Boolean
:nodoc:
409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/ruby_llm/models.rb', line 409 def blank_value?(value) # :nodoc: return true if value.nil? return value.empty? if value.is_a?(String) || value.is_a?(Array) if value.is_a?(Hash) return true if value.empty? return value.values.all? { |nested| blank_value?(nested) } end false end |
.bundled_registry_file ⇒ Object
:nodoc:
88 89 90 |
# File 'lib/ruby_llm/models.rb', line 88 def bundled_registry_file # :nodoc: File.('models.json', __dir__) end |
.fetch_merged_models(remote_only: false) ⇒ Object
Fetches and merges models directly from upstream provider APIs and models.dev for the maintainer registry builder.
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/ruby_llm/models.rb', line 155 def fetch_merged_models(remote_only: false) # :nodoc: RubyLLM.instrument('models.refresh.ruby_llm', remote_only:) do |payload| existing_models = read_existing_models provider_fetch = fetch_provider_models(remote_only: remote_only) @last_provider_failures = provider_fetch[:failed] log_provider_fetch(provider_fetch) payload[:failed_providers] = provider_fetch[:failed].map { |failure| failure[:slug] } models_dev_fetch = fetch_models_dev_models(existing_models) log_models_dev_fetch(models_dev_fetch) merged_models = merge_with_existing(existing_models, provider_fetch, models_dev_fetch) payload[:model_count] = merged_models.size merged_models end end |
.fetch_models_dev_models(existing_models) ⇒ Object
:nodoc:
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/ruby_llm/models.rb', line 227 def fetch_models_dev_models(existing_models) # :nodoc: RubyLLM.logger.info 'Fetching models from models.dev API...' connection = Transport::Connection.basic do |f| f.request :json f.response :json, parser_options: { symbolize_names: true } end { models: parse_models_dev_catalog(connection.get('https://models.dev/api.json').body), fetched: true } rescue StandardError => e RubyLLM.logger.warn("Failed to fetch models.dev (#{e.class}: #{e.}). Keeping existing.") { models: existing_models.select { |model| model.[:source] == 'models.dev' }, fetched: false } end |
.fetch_provider_models(remote_only: true) ⇒ Object
:nodoc:
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/ruby_llm/models.rb', line 173 def fetch_provider_models(remote_only: true) # :nodoc: config = RubyLLM.config providers = remote_only ? Provider.configured_remote_providers(config) : Provider.configured_providers(config) providers = providers.reject { |provider| Provider.model_registry_files.key?(provider.slug.to_sym) } result = { models: [], fetched_providers: [], configured_names: providers.map(&:display_name), failed: [], empty: [] } providers.each do |provider_class| models = provider_class.new(config).list_models if models.empty? result[:empty] << { name: provider_class.display_name, slug: provider_class.slug } else result[:models].concat(models) result[:fetched_providers] << provider_class.slug end rescue StandardError => e result[:failed] << { name: provider_class.display_name, slug: provider_class.slug, error: e } end result end |
.fetch_published_registry(etag: nil) ⇒ Object
:nodoc:
136 137 138 |
# File 'lib/ruby_llm/models.rb', line 136 def fetch_published_registry(etag: nil) # :nodoc: Registry::PublishedSource.new.fetch(etag:) end |
.find_models_dev_model(key, models_dev_by_key, provider_model = nil) ⇒ Object
:nodoc:
328 329 330 331 332 333 |
# File 'lib/ruby_llm/models.rb', line 328 def find_models_dev_model(key, models_dev_by_key, provider_model = nil) # :nodoc: return models_dev_by_key[key] if models_dev_by_key[key] provider, model_id = key.split(':', 2) Provider.resolve(provider)&.models_dev_alias(model_id, models_dev_by_key, provider_model) end |
.index_by_key(models) ⇒ Object
:nodoc:
335 336 337 338 339 |
# File 'lib/ruby_llm/models.rb', line 335 def index_by_key(models) # :nodoc: models.to_h do |model| ["#{model.provider}:#{model.id}", model] end end |
.index_provider_aliases(models) ⇒ Object
:nodoc:
341 342 343 344 345 346 347 |
# File 'lib/ruby_llm/models.rb', line 341 def index_provider_aliases(models) # :nodoc: models.each_with_object({}) do |model, aliases| Array(model.[:aliases]).each do |alias_id| aliases["#{model.provider}:#{alias_id}"] ||= model end end end |
.instance ⇒ Object
:nodoc:
84 85 86 |
# File 'lib/ruby_llm/models.rb', line 84 def instance # :nodoc: @instance ||= new end |
.load_models ⇒ Object
:nodoc:
92 93 94 95 96 97 98 |
# File 'lib/ruby_llm/models.rb', line 92 def load_models # :nodoc: base = models_from_store(RubyLLM.config.model_registry_store) || models_from_file(RubyLLM.config.model_registry_file) || models_from_bundle merge_models(models_from_provider_gems, base) end |
.log_models_dev_fetch(models_dev_fetch) ⇒ Object
:nodoc:
282 283 284 285 286 |
# File 'lib/ruby_llm/models.rb', line 282 def log_models_dev_fetch(models_dev_fetch) # :nodoc: return if models_dev_fetch[:fetched] RubyLLM.logger.warn('Using cached models.dev data due to fetch failure.') end |
.log_provider_fetch(provider_fetch) ⇒ Object
:nodoc:
269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/ruby_llm/models.rb', line 269 def log_provider_fetch(provider_fetch) # :nodoc: RubyLLM.logger.info "Fetching models from providers: #{provider_fetch[:configured_names].join(', ')}" provider_fetch[:failed].each do |failure| RubyLLM.logger.warn( "Failed to fetch #{failure[:name]} models (#{failure[:error].class}: #{failure[:error].}). " \ 'Keeping existing.' ) end Array(provider_fetch[:empty]).each do |provider| RubyLLM.logger.warn("#{provider[:name]} listed no models. Keeping existing.") end end |
.merge_capabilities(models_dev_model, provider_model, modalities) ⇒ Object
:nodoc:
368 369 370 371 372 |
# File 'lib/ruby_llm/models.rb', line 368 def merge_capabilities(models_dev_model, provider_model, modalities) # :nodoc: denied = models_dev_reported_capabilities(models_dev_model) - models_dev_model.capabilities reported = (models_dev_model.capabilities + provider_model.capabilities).uniq - denied augment_capabilities(provider_model.provider, reported, provider_model.id, modalities) end |
.merge_models(provider_models, models_dev_models) ⇒ Object
:nodoc:
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/ruby_llm/models.rb', line 305 def merge_models(provider_models, models_dev_models) # :nodoc: models_dev_by_key = index_by_key(models_dev_models) provider_by_key = index_by_key(provider_models) provider_by_alias = index_provider_aliases(provider_models) all_keys = models_dev_by_key.keys | provider_by_key.keys models = all_keys.map do |key| provider_model = provider_by_key[key] || provider_by_alias[key] models_dev_model = find_models_dev_model(key, models_dev_by_key, provider_model) if models_dev_model && provider_model (models_dev_model, provider_model) elsif models_dev_model models_dev_model else augment_model_capabilities(provider_model) end end models.sort_by { |m| [m.provider, m.id] } end |
.merge_with_existing(existing_models, provider_fetch, models_dev_fetch) ⇒ Object
:nodoc:
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/ruby_llm/models.rb', line 288 def merge_with_existing(existing_models, provider_fetch, models_dev_fetch) # :nodoc: existing_by_provider = existing_models.group_by(&:provider) preserved_models = existing_by_provider .except(*provider_fetch[:fetched_providers]) .values .flatten provider_models = provider_fetch[:models] + preserved_models models_dev_models = if models_dev_fetch[:fetched] models_dev_fetch[:models] else existing_models.select { |model| model.[:source] == 'models.dev' } end merge_models(provider_models, models_dev_models) end |
.models_dev_capabilities(model_data, modalities, provider_slug) ⇒ Object
:nodoc:
453 454 455 456 457 458 459 460 461 |
# File 'lib/ruby_llm/models.rb', line 453 def models_dev_capabilities(model_data, modalities, provider_slug) # :nodoc: capabilities = [] capabilities << 'function_calling' if model_data[:tool_call] capabilities << 'structured_output' if model_data[:structured_output] capabilities << 'reasoning' if model_data[:reasoning] || model_data[:reasoning_options] capabilities << 'vision' if modalities[:input].intersect?(%w[image video pdf]) capabilities << 'video' if modalities[:input].include?('video') augment_capabilities(provider_slug, capabilities.uniq, model_data[:id], modalities) end |
.models_dev_metadata(model_data, provider_key) ⇒ Object
:nodoc:
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 |
# File 'lib/ruby_llm/models.rb', line 500 def (model_data, provider_key) # :nodoc: = { source: 'models.dev', provider_id: provider_key, open_weights: model_data[:open_weights], attachment: model_data[:attachment], temperature: model_data[:temperature], last_updated: model_data[:last_updated], status: model_data[:status], interleaved: model_data[:interleaved], tool_call: model_data[:tool_call], structured_output: model_data[:structured_output], reasoning: model_data[:reasoning], reasoning_options: model_data[:reasoning_options], cost: model_data[:cost], limit: model_data[:limit], knowledge: model_data[:knowledge] } .compact end |
.models_dev_model_attributes(model_data, provider_slug, provider_key) ⇒ Object
:nodoc:
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 |
# File 'lib/ruby_llm/models.rb', line 422 def models_dev_model_attributes(model_data, provider_slug, provider_key) # :nodoc: modalities = normalize_models_dev_modalities(model_data[:modalities]) capabilities = models_dev_capabilities(model_data, modalities, provider_slug) created_date = [model_data[:release_date], model_data[:last_updated]] .find { |value| !value.to_s.strip.empty? } data = { id: models_dev_model_id(model_data[:id], provider_slug), name: model_data[:name] || model_data[:id], provider: provider_slug, family: model_data[:family], created_at: Support::Utils.iso_date_prefix_to_utc_midnight_string(created_date), context_window: model_data.dig(:limit, :context), max_output_tokens: model_data.dig(:limit, :output), knowledge_cutoff: normalize_models_dev_knowledge(model_data[:knowledge]), modalities: modalities, capabilities: capabilities, pricing: models_dev_pricing(model_data[:cost]), metadata: (model_data, provider_key) } (data) data end |
.models_dev_model_id(id, provider_slug) ⇒ Object
:nodoc:
448 449 450 451 |
# File 'lib/ruby_llm/models.rb', line 448 def models_dev_model_id(id, provider_slug) # :nodoc: provider = Provider.resolve(provider_slug) provider ? provider.models_dev_model_id(id) : id end |
.models_dev_pricing(cost) ⇒ Object
:nodoc:
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 |
# File 'lib/ruby_llm/models.rb', line 463 def models_dev_pricing(cost) # :nodoc: return {} unless cost text_standard = { input_per_million: cost[:input], output_per_million: cost[:output], cache_read_input_per_million: cost[:cache_read], cache_write_input_per_million: cost[:cache_write], reasoning_output_per_million: cost[:reasoning] }.compact audio_standard = { input_per_million: cost[:input_audio], output_per_million: cost[:output_audio] }.compact pricing = {} text_tokens = models_dev_text_tokens_pricing(text_standard, cost) pricing[:text_tokens] = text_tokens if text_tokens pricing[:audio_tokens] = { standard: audio_standard } if audio_standard.any? pricing end |
.models_dev_provider_models(provider_key, provider_data) ⇒ Object
:nodoc:
254 255 256 257 258 259 260 261 262 |
# File 'lib/ruby_llm/models.rb', line 254 def models_dev_provider_models(provider_key, provider_data) # :nodoc: provider_slug = MODELS_DEV_PROVIDER_MAP[provider_key.to_s] return [] unless provider_slug (provider_data[:models] || {}).values.filter_map do |model_data| model = Model.new(models_dev_model_attributes(model_data, provider_slug, provider_key.to_s)) model unless model.provider.nil? || model.id.nil? end end |
.models_dev_reported_capabilities(models_dev_model) ⇒ Object
models.dev leaves a field out where it has no opinion, so only the capabilities it reports on can overrule what a provider claims.
390 391 392 393 394 395 396 397 398 |
# File 'lib/ruby_llm/models.rb', line 390 def models_dev_reported_capabilities(models_dev_model) # :nodoc: = models_dev_model. reported = [] reported << 'function_calling' unless [:tool_call].nil? reported << 'structured_output' unless [:structured_output].nil? reported << 'reasoning' unless [:reasoning].nil? && [:reasoning_options].nil? reported << 'vision' unless models_dev_model.modalities.input.empty? reported end |
.models_dev_text_tokens_pricing(text_standard, cost) ⇒ Object
:nodoc:
486 487 488 489 490 491 492 493 494 495 496 497 498 |
# File 'lib/ruby_llm/models.rb', line 486 def models_dev_text_tokens_pricing(text_standard, cost) # :nodoc: long_context, threshold = Model::PricingCategory.long_context_from_cost(cost) return nil if text_standard.empty? && long_context.nil? text_tokens = {} text_tokens[:standard] = text_standard if text_standard.any? if long_context text_tokens[:long_context] = long_context text_tokens[:long_context_threshold] = threshold if threshold end text_tokens end |
.models_from_bundle ⇒ Object
:nodoc:
126 127 128 129 130 131 132 133 134 |
# File 'lib/ruby_llm/models.rb', line 126 def models_from_bundle # :nodoc: Registry.read(bundled_registry_file) || begin RubyLLM.logger.warn( "Bundled model registry is missing: #{bundled_registry_file}. " \ 'Refresh the registry to rebuild it.' ) [] end end |
.models_from_file(file) ⇒ Object
:nodoc:
116 117 118 119 120 121 122 123 124 |
# File 'lib/ruby_llm/models.rb', line 116 def models_from_file(file) # :nodoc: return unless file models = Registry.read(file) models unless models.nil? || models.empty? rescue ModelRegistryError => e RubyLLM.logger.warn("Ignoring invalid model registry file #{file}: #{e.}") nil end |
.models_from_provider_gems ⇒ Object
:nodoc:
100 101 102 103 104 |
# File 'lib/ruby_llm/models.rb', line 100 def models_from_provider_gems # :nodoc: Provider.model_registry_files.flat_map do |provider, file| Array(models_from_file(file)).select { |model| model.provider == provider.to_s } end end |
.models_from_store(store) ⇒ Object
:nodoc:
106 107 108 109 110 111 112 113 114 |
# File 'lib/ruby_llm/models.rb', line 106 def models_from_store(store) # :nodoc: return unless store models = Array(store.read) return models unless models.empty? RubyLLM.logger.debug { 'Model registry store is empty, falling back to the registry file' } nil end |
.normalize_embedding_modalities(data) ⇒ Object
:nodoc:
400 401 402 403 404 405 406 407 |
# File 'lib/ruby_llm/models.rb', line 400 def (data) # :nodoc: return unless data[:id].to_s.include?('embedding') modalities = data[:modalities].to_h modalities[:input] = ['text'] if modalities[:input].nil? || modalities[:input].empty? modalities[:output] = ['embeddings'] data[:modalities] = modalities end |
.normalize_models_dev_knowledge(value) ⇒ Object
:nodoc:
530 531 532 533 534 535 536 537 |
# File 'lib/ruby_llm/models.rb', line 530 def normalize_models_dev_knowledge(value) # :nodoc: return if value.nil? return value if value.is_a?(Date) Date.parse(value.to_s) rescue ArgumentError nil end |
.normalize_models_dev_modalities(modalities) ⇒ Object
:nodoc:
521 522 523 524 525 526 527 528 |
# File 'lib/ruby_llm/models.rb', line 521 def normalize_models_dev_modalities(modalities) # :nodoc: normalized = { input: [], output: [] } return normalized unless modalities normalized[:input] = Array(modalities[:input]).compact & MODELS_DEV_INPUT_MODALITIES normalized[:output] = Array(modalities[:output]).compact & MODELS_DEV_OUTPUT_MODALITIES normalized end |
.parse_models_dev_catalog(body) ⇒ Object
An answer RubyLLM cannot read a single model out of is no answer: only a catalog carrying models may overrule what the registry holds.
245 246 247 248 249 250 251 252 |
# File 'lib/ruby_llm/models.rb', line 245 def parse_models_dev_catalog(body) # :nodoc: raise ModelRegistryError, "models.dev returned #{body.class} instead of a catalog" unless body.is_a?(Hash) models = body.flat_map { |provider_key, data| models_dev_provider_models(provider_key, data) } raise ModelRegistryError, 'models.dev returned no models RubyLLM knows a provider for' if models.empty? models end |
.read_existing_models ⇒ Object
:nodoc:
264 265 266 267 |
# File 'lib/ruby_llm/models.rb', line 264 def read_existing_models # :nodoc: existing_models = instance.all existing_models.empty? ? load_models : existing_models end |
.refresh(remote_only: false) ⇒ Object
Refreshes the global model registry from the published catalog and
configured providers. Returns the global Models instance. See
#refresh for the remote_only: option.
143 144 145 |
# File 'lib/ruby_llm/models.rb', line 143 def refresh(remote_only: false) instance.refresh(remote_only: remote_only) end |
.refresh_from_providers(remote_only: false) ⇒ Object
:nodoc:
147 148 149 |
# File 'lib/ruby_llm/models.rb', line 147 def refresh_from_providers(remote_only: false) # :nodoc: instance.refresh_from_providers(remote_only: remote_only) end |
.resolve(model_id, provider: nil, assume_model_exists: false, config: nil, operation: nil, default_model: nil) ⇒ Object
196 197 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 |
# File 'lib/ruby_llm/models.rb', line 196 def resolve(model_id, provider: nil, assume_model_exists: false, config: nil, operation: nil, default_model: nil) # rubocop:disable Metrics/PerceivedComplexity config ||= RubyLLM.config provider_class = provider ? Provider.providers[provider.to_sym] : nil if operation && provider_class && !provider_class.model_required?(operation:) raise ArgumentError, "#{operation} does not accept a model" unless model_id.nil? return [nil, provider_class.new(config)] end model_id ||= default_model assume_model_exists = true if provider_class&.local? || provider_class&.assume_models_exist? if assume_model_exists raise ArgumentError, 'Provider must be specified if assume_model_exists is true' unless provider provider_class ||= Provider.resolve!(provider) model = begin Models.find(model_id, provider: provider, config: config) rescue ModelNotFoundError nil end model ||= Model.default(model_id, provider_class.slug) else model = Models.find model_id, provider: provider, config: config provider_class = Provider.resolve!(model.provider) end [model, provider_class.new(config)] end |
Instance Method Details
#all ⇒ Object Also known as: listed
Returns an array of the Model entries the configured provider still lists. Models it has stopped listing are left out; #find still resolves them, and #unlisted reports them.
579 580 581 |
# File 'lib/ruby_llm/models.rb', line 579 def all all_including_unlisted.reject(&:unlisted?) end |
#all_including_unlisted ⇒ Object
:nodoc:
597 598 599 |
# File 'lib/ruby_llm/models.rb', line 597 def all_including_unlisted # :nodoc: @models end |
#audio_models ⇒ Object
Returns a new Models registry containing only models with audio output.
637 638 639 |
# File 'lib/ruby_llm/models.rb', line 637 def audio_models select_models { |m| m.type == :audio || m.modalities.output.include?('audio') } end |
#by_family(family) ⇒ Object
651 652 653 |
# File 'lib/ruby_llm/models.rb', line 651 def by_family(family) select_models { |m| m.family == family.to_s } end |
#by_provider(provider) ⇒ Object
660 661 662 |
# File 'lib/ruby_llm/models.rb', line 660 def by_provider(provider) select_models { |m| m.provider == provider.to_s } end |
#chat_models ⇒ Object
Returns a new Models registry containing only chat models.
626 627 628 |
# File 'lib/ruby_llm/models.rb', line 626 def chat_models select_models { |m| m.type == :chat } end |
#each ⇒ Object
605 606 607 |
# File 'lib/ruby_llm/models.rb', line 605 def each(&) all.each(&) end |
#embedding_models ⇒ Object
Returns a new Models registry containing only embedding models.
631 632 633 |
# File 'lib/ruby_llm/models.rb', line 631 def select_models { |m| m.type == :embedding || m.modalities.output.include?('embeddings') } end |
#find(model_id, provider: nil, config: nil) ⇒ Object
Returns the Model matching model_id, resolving aliases along the
way. Without provider, picks the preferred provider that carries
the model, first-party providers before aggregators. Raises
RubyLLM::ModelNotFoundError if no model matches.
RubyLLM.models.find 'gpt-5.6'
RubyLLM.models.find 'claude-sonnet-5', provider: :bedrock
617 618 619 620 621 622 623 |
# File 'lib/ruby_llm/models.rb', line 617 def find(model_id, provider: nil, config: nil) if provider find_with_provider(model_id, provider, config) else find_without_provider(model_id) end end |
#image_models ⇒ Object
Returns a new Models registry containing only models with image output.
643 644 645 |
# File 'lib/ruby_llm/models.rb', line 643 def image_models select_models { |m| m.type == :image || m.modalities.output.include?('image') } end |
#load_from_json(file = RubyLLM.config.model_registry_file) ⇒ Object
Replaces the models in this registry with those read from the JSON
file. The default is the configured
RubyLLM.config.model_registry_file. A missing or invalid
file falls back to the registry bundled with the gem.
550 551 552 553 |
# File 'lib/ruby_llm/models.rb', line 550 def load_from_json(file = RubyLLM.config.model_registry_file) @models = self.class.models_from_file(file) || self.class.models_from_bundle self end |
#load_from_store ⇒ Object
Replaces the models in this registry with entries from the configured model-registry store.
557 558 559 560 561 562 563 |
# File 'lib/ruby_llm/models.rb', line 557 def load_from_store store = RubyLLM.config.model_registry_store raise ModelRegistryError, 'No model registry store is configured' unless store @models = Array(store.read) self end |
#refresh(remote_only: false) ⇒ Object
Replaces the registry with the latest published RubyLLM catalog,
merged with models discovered from configured providers. The result is
saved to the platform cache, or to the database in Rails applications.
Pass remote_only: true to skip local providers such as Ollama and
GPUStack. Returns self.
Raises ModelRegistryError when the catalog cannot be fetched or the result cannot be persisted, leaving the current registry unchanged.
RubyLLM.models.refresh
RubyLLM.models.refresh(remote_only: true).chat_models
676 677 678 679 680 681 682 683 684 685 686 687 |
# File 'lib/ruby_llm/models.rb', line 676 def refresh(remote_only: false) RubyLLM.instrument('models.refresh.ruby_llm', remote_only:) do |payload| published = fetch_published_models main_models = merge_discovered_models(published.models, remote_only:) merged_models = self.class.merge_models(self.class.models_from_provider_gems, main_models) persisted_models = RubyLLM.config.model_registry_store ? merged_models : main_models persist_registry!(persisted_models, published:) @models = stored_models || merged_models payload.merge!(model_count: all.size, not_modified: published.not_modified) end self end |
#refresh_from_providers(remote_only: false) ⇒ Object
:nodoc:
689 690 691 692 |
# File 'lib/ruby_llm/models.rb', line 689 def refresh_from_providers(remote_only: false) # :nodoc: @models = self.class.fetch_merged_models(remote_only: remote_only) self end |
#resolve(model_id, provider: nil, assume_model_exists: false, config: nil) ⇒ Object
:nodoc:
694 695 696 |
# File 'lib/ruby_llm/models.rb', line 694 def resolve(model_id, provider: nil, assume_model_exists: false, config: nil) # :nodoc: self.class.resolve(model_id, provider: provider, assume_model_exists: assume_model_exists, config: config) end |
#save_to_json(file = RubyLLM.config.model_registry_file) ⇒ Object
571 572 573 574 |
# File 'lib/ruby_llm/models.rb', line 571 def save_to_json(file = RubyLLM.config.model_registry_file) Registry::FileStore.new(file).write(all) self end |
#unlisted ⇒ Object
589 590 591 |
# File 'lib/ruby_llm/models.rb', line 589 def unlisted all_including_unlisted.select(&:unlisted?) end |