Class: RubyLLM::Models

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

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_failuresObject (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])
  normalize_embedding_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:

Returns:

  • (Boolean)


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_fileObject

:nodoc:



88
89
90
# File 'lib/ruby_llm/models.rb', line 88

def bundled_registry_file # :nodoc:
  File.expand_path('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.message}). 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

.instanceObject

:nodoc:



84
85
86
# File 'lib/ruby_llm/models.rb', line 84

def instance # :nodoc:
  @instance ||= new
end

.load_modelsObject

: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].message}). " \
      '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)
  }

  normalize_embedding_modalities(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_bundleObject

: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.message}")
  nil
end

.models_from_provider_gemsObject

: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 normalize_embedding_modalities(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.

Raises:



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_modelsObject

: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

#allObject 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_unlistedObject

:nodoc:



597
598
599
# File 'lib/ruby_llm/models.rb', line 597

def all_including_unlisted # :nodoc:
  @models
end

#audio_modelsObject

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

Returns a new Models registry containing only models in family.

RubyLLM.models.by_family('claude3_sonnet')


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

Returns a new Models registry containing only models from provider. Accepts a symbol or a string.

RubyLLM.models.by_provider(:openai).select { |model| model.supports?(:vision) }


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_modelsObject

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

#eachObject

Yields each Model in the registry.

RubyLLM.models.each { |model| puts model.id }


605
606
607
# File 'lib/ruby_llm/models.rb', line 605

def each(&)
  all.each(&)
end

#embedding_modelsObject

Returns a new Models registry containing only embedding models.



631
632
633
# File 'lib/ruby_llm/models.rb', line 631

def embedding_models
  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_modelsObject

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_storeObject

Replaces the models in this registry with entries from the configured model-registry store.

Raises:



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

Exports this registry to file as pretty-printed JSON. The default is the configured RubyLLM.config.model_registry_file. A regular #refresh already persists to the active registry store.

RubyLLM.models.save_to_json('/tmp/models.json')


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

#unlistedObject

Returns an array of the Model entries the configured provider has stopped listing. Only a store that keeps them, such as the Rails model table, ever reports one.

RubyLLM.models.unlisted.map(&:id)


589
590
591
# File 'lib/ruby_llm/models.rb', line 589

def unlisted
  all_including_unlisted.select(&:unlisted?)
end