Class: LlmModel

Inherits:
ApplicationRecord show all
Defined in:
app/models/llm_model.rb

Overview

== Schema Information

Table name: ruby_llm_models
Database name: primary

id :bigint not null, primary key
capabilities :jsonb
context_window :integer
family :string
knowledge_cutoff :date
max_output_tokens :integer
metadata :jsonb
modalities :jsonb
model_created_at :datetime
name :string not null
pricing :jsonb
provider :string not null
created_at :datetime not null
updated_at :datetime not null
model_id :string not null

Indexes

index_ruby_llm_models_on_capabilities (capabilities) USING gin
index_ruby_llm_models_on_family (family)
index_ruby_llm_models_on_modalities (modalities) USING gin
index_ruby_llm_models_on_provider_and_model_id (provider,model_id) UNIQUE

RubyLLM 2.0 owns the model registry outright: acts_as_model and
config.model_registry_class are gone, and the rows moved to
ruby_llm_models, read and written by RubyLLM::ActiveRecord::Model — which
the gem marks :nodoc: private API.

This class stays as the application's read model over that table, for the
same reason AssistantToolCall does: it keeps the gem-private constant and the
gem-chosen table name in one file instead of scattered across workers.

Writes go through the gem (RubyLLM.models.refresh! and the acts_as_chat
find_or_create path). The one exception is #apply_reasoning_options! below.

Constant Summary

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Class Method Summary collapse

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Class Method Details

.apply_reasoning_options!Integer

Advertise adaptive thinking to RubyLLM.

RubyLLM builds the Anthropic thinking: {type: 'adaptive'} +
output_config.effort payload itself, but gates it on
model.reasoning_option('effort') and raises "Anthropic thinking effort
is not supported for " when it is missing. The gem's bundled
models.json carries no reasoning_options for our Claude ids, so nothing
would advertise it.

RubyLLM::Model reads reasoning_options out of metadata (model.rb:186),
so seeding it there is enough to make the native path fire. Deriving from
AiModelConstants — the single source of truth for model generations —
covers records created by a later registry refresh with no seed data to
maintain.

MUST be re-run after every RubyLLM.models.refresh!: refresh rebuilds each
row's metadata from models.json, which drops the key again. That's why
this is a class method rather than the 1.x #to_llm override — there is
no gem-side read hook left to hang it on.

Returns:

  • (Integer)

    number of rows updated



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'app/models/llm_model.rb', line 80

def apply_reasoning_options!
  adaptive = where(provider: 'anthropic').select do |model|
    AiModelConstants.adaptive_thinking?(model.model_id) &&
      model.['reasoning_options'].blank?
  end

  adaptive.each do |model|
    model.update_column(
      :metadata,
      model..merge(
        'reasoning_options' => [{ 'type' => 'effort', 'values' => AiModelConstants::ADAPTIVE_EFFORT_LEVELS }]
      )
    )
  end.size
end

.refresh!Object

Pull the live provider registry into ruby_llm_models.

1.x got this free from acts_as_model. In 2.0 the refresh and the
persistence are two separate gem calls, and neither knows about our
adaptive-thinking metadata — so all three steps belong together, behind
the name both callers (WeeklyLlmModelSyncWorker, rails llm:sync)
already use.



53
54
55
56
57
# File 'app/models/llm_model.rb', line 53

def refresh!
  RubyLLM.models.refresh!
  RubyLLM::ActiveRecord::Model.save_to_database
  apply_reasoning_options!
end