From 223c7d61e3fb474400c02de705094b44a60e2b25 Mon Sep 17 00:00:00 2001 From: Supernova Date: Tue, 22 Sep 2026 21:03:43 +0000 Subject: [PATCH 1/3] Add Claude Opus 5.5 model definitions and alias Register claude-opus-5-5 for anthropic, vertexai, and all six Bedrock region prefixes (bare/global/us/eu/au/jp), mirroring Opus 5's provider footprint. Pricing follows the registry's existing convention for Opus 5: each id is recorded at the same rate structure as that id's Opus 5 entry (base rates, or eu.'s 1.1x), taken from AWS's Bedrock price list "Standard, Global" tier for Claude Opus 5.5. Co-authored-by: Sam Boland --- lib/ruby_llm/aliases.json | 5 + lib/ruby_llm/models.json | 529 ++++++++++++++++++++++++++ spec/ruby_llm/models_opus_5_5_spec.rb | 55 +++ 3 files changed, 589 insertions(+) create mode 100644 spec/ruby_llm/models_opus_5_5_spec.rb diff --git a/lib/ruby_llm/aliases.json b/lib/ruby_llm/aliases.json index ef746aae3..185579fc5 100644 --- a/lib/ruby_llm/aliases.json +++ b/lib/ruby_llm/aliases.json @@ -92,6 +92,11 @@ "bedrock": "anthropic.claude-opus-5", "vertexai": "claude-opus-5" }, + "claude-opus-5-5": { + "anthropic": "claude-opus-5-5", + "bedrock": "anthropic.claude-opus-5-5", + "vertexai": "claude-opus-5-5" + }, "claude-sonnet-4": { "anthropic": "claude-sonnet-4-20250514", "openrouter": "anthropic/claude-sonnet-4", diff --git a/lib/ruby_llm/models.json b/lib/ruby_llm/models.json index ca8c58426..ede2c31bd 100644 --- a/lib/ruby_llm/models.json +++ b/lib/ruby_llm/models.json @@ -1339,6 +1339,73 @@ "knowledge": "2026-05" } }, + { + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "provider": "anthropic", + "family": "claude-opus", + "created_at": "2026-09-22 00:00:00 UTC", + "context_window": 1000000, + "max_output_tokens": 128000, + "knowledge_cutoff": null, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "structured_output", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 4, + "output_per_million": 20, + "cache_read_input_per_million": 0.2, + "cache_write_input_per_million": 5 + } + } + }, + "metadata": { + "source": "models.dev", + "provider_id": "anthropic", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-09-22", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 4, + "output": 20, + "cache_read": 0.2, + "cache_write": 5 + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "knowledge": "2026-06" + } + }, { "id": "claude-sonnet-4-0", "name": "Claude Sonnet 4 (latest)", @@ -11752,6 +11819,72 @@ "knowledge": "2026-05" } }, + { + "id": "anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5", + "provider": "bedrock", + "family": "claude-opus", + "created_at": "2026-09-22 00:00:00 UTC", + "context_window": 1000000, + "max_output_tokens": 128000, + "knowledge_cutoff": null, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 4, + "output_per_million": 20, + "cache_read_input_per_million": 0.2, + "cache_write_input_per_million": 5 + } + } + }, + "metadata": { + "source": "models.dev", + "provider_id": "amazon-bedrock", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-09-22", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 4, + "output": 20, + "cache_read": 0.2, + "cache_write": 5 + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "knowledge": "2026-06" + } + }, { "id": "anthropic.claude-sonnet-4-5-20250929-v1:0", "name": "Claude Sonnet 4.5", @@ -12215,6 +12348,72 @@ "knowledge": "2026-05" } }, + { + "id": "au.anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5 (AU)", + "provider": "bedrock", + "family": "claude-opus", + "created_at": "2026-09-22 00:00:00 UTC", + "context_window": 1000000, + "max_output_tokens": 128000, + "knowledge_cutoff": null, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 4, + "output_per_million": 20, + "cache_read_input_per_million": 0.2, + "cache_write_input_per_million": 5 + } + } + }, + "metadata": { + "source": "models.dev", + "provider_id": "amazon-bedrock", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-09-22", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 4, + "output": 20, + "cache_read": 0.2, + "cache_write": 5 + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "knowledge": "2026-06" + } + }, { "id": "au.anthropic.claude-sonnet-4-5-20250929-v1:0", "name": "Claude Sonnet 4.5 (AU)", @@ -13339,6 +13538,72 @@ "knowledge": "2026-05" } }, + { + "id": "eu.anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5 (EU)", + "provider": "bedrock", + "family": "claude-opus", + "created_at": "2026-09-22 00:00:00 UTC", + "context_window": 1000000, + "max_output_tokens": 128000, + "knowledge_cutoff": null, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 4.4, + "output_per_million": 22, + "cache_read_input_per_million": 0.22, + "cache_write_input_per_million": 5.5 + } + } + }, + "metadata": { + "source": "models.dev", + "provider_id": "amazon-bedrock", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-09-22", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 4.4, + "output": 22, + "cache_read": 0.22, + "cache_write": 5.5 + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "knowledge": "2026-06" + } + }, { "id": "eu.anthropic.claude-sonnet-4-5-20250929-v1:0", "name": "Claude Sonnet 4.5 (EU)", @@ -14067,6 +14332,72 @@ "knowledge": "2026-05" } }, + { + "id": "global.anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5 (Global)", + "provider": "bedrock", + "family": "claude-opus", + "created_at": "2026-09-22 00:00:00 UTC", + "context_window": 1000000, + "max_output_tokens": 128000, + "knowledge_cutoff": null, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 4, + "output_per_million": 20, + "cache_read_input_per_million": 0.2, + "cache_write_input_per_million": 5 + } + } + }, + "metadata": { + "source": "models.dev", + "provider_id": "amazon-bedrock", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-09-22", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 4, + "output": 20, + "cache_read": 0.2, + "cache_write": 5 + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "knowledge": "2026-06" + } + }, { "id": "global.anthropic.claude-sonnet-4-5-20250929-v1:0", "name": "Claude Sonnet 4.5 (Global)", @@ -14675,6 +15006,72 @@ "knowledge": "2026-05" } }, + { + "id": "jp.anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5 (JP)", + "provider": "bedrock", + "family": "claude-opus", + "created_at": "2026-09-22 00:00:00 UTC", + "context_window": 1000000, + "max_output_tokens": 128000, + "knowledge_cutoff": null, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 4, + "output_per_million": 20, + "cache_read_input_per_million": 0.2, + "cache_write_input_per_million": 5 + } + } + }, + "metadata": { + "source": "models.dev", + "provider_id": "amazon-bedrock", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-09-22", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 4, + "output": 20, + "cache_read": 0.2, + "cache_write": 5 + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "knowledge": "2026-06" + } + }, { "id": "jp.anthropic.claude-sonnet-4-5-20250929-v1:0", "name": "Claude Sonnet 4.5 (JP)", @@ -19184,6 +19581,72 @@ "knowledge": "2026-05" } }, + { + "id": "us.anthropic.claude-opus-5-5", + "name": "Claude Opus 5.5 (US)", + "provider": "bedrock", + "family": "claude-opus", + "created_at": "2026-09-22 00:00:00 UTC", + "context_window": 1000000, + "max_output_tokens": 128000, + "knowledge_cutoff": null, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 4, + "output_per_million": 20, + "cache_read_input_per_million": 0.2, + "cache_write_input_per_million": 5 + } + } + }, + "metadata": { + "source": "models.dev", + "provider_id": "amazon-bedrock", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-09-22", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 4, + "output": 20, + "cache_read": 0.2, + "cache_write": 5 + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "knowledge": "2026-06" + } + }, { "id": "us.anthropic.claude-sonnet-4-20250514-v1:0", "name": "Claude Sonnet 4", @@ -62370,6 +62833,72 @@ "knowledge": "2026-05" } }, + { + "id": "claude-opus-5-5", + "name": "Claude Opus 5.5", + "provider": "vertexai", + "family": "claude-opus", + "created_at": "2026-09-22 00:00:00 UTC", + "context_window": 1000000, + "max_output_tokens": 128000, + "knowledge_cutoff": null, + "modalities": { + "input": [ + "text", + "image", + "pdf" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 4, + "output_per_million": 20, + "cache_read_input_per_million": 0.2, + "cache_write_input_per_million": 5 + } + } + }, + "metadata": { + "source": "models.dev", + "provider_id": "google-vertex", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-09-22", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 4, + "output": 20, + "cache_read": 0.2, + "cache_write": 5 + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "knowledge": "2026-06" + } + }, { "id": "claude-sonnet-4", "name": "Claude Sonnet 4", diff --git a/spec/ruby_llm/models_opus_5_5_spec.rb b/spec/ruby_llm/models_opus_5_5_spec.rb new file mode 100644 index 000000000..dc7d304ef --- /dev/null +++ b/spec/ruby_llm/models_opus_5_5_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe RubyLLM::Models do + include_context 'with configured RubyLLM' + + # Pricing follows the registry's existing convention for Claude Opus 5: each + # id is recorded at the same rate structure as that id's Opus 5 entry. Base + # rates (input/output/cache_read/cache_write per million tokens) come from + # AWS's price list ("Standard, Global" tier for Claude Opus 5.5 on Amazon + # Bedrock), and only the eu. entry is recorded at 1.1x base, matching the + # 1.1x the eu.anthropic.claude-opus-5 entry already uses. This does not + # correct the separate, pre-existing question of whether us. (and other + # non-eu regional prefixes) should also be at 1.1x on Bedrock's own price + # list; that is out of scope here and applies equally to the existing Opus + # 5 entries. + { + 'claude-opus-5-5' => { input: 4, output: 20, cache_read: 0.20, cache_write: 5 }, + 'global.anthropic.claude-opus-5-5' => { input: 4, output: 20, cache_read: 0.20, cache_write: 5 }, + 'us.anthropic.claude-opus-5-5' => { input: 4, output: 20, cache_read: 0.20, cache_write: 5 }, + 'eu.anthropic.claude-opus-5-5' => { input: 4.4, output: 22, cache_read: 0.22, cache_write: 5.5 } + }.each do |id, cost| + it "registers #{id} with the documented effort values and pricing" do + # Look up by raw id rather than RubyLLM.models.find(id, :bedrock) — bedrock + # lookups renormalize the region prefix to the configured bedrock_region, + # which would mask the EU-specific entry's own pricing. + model = RubyLLM.models.all.find { |m| m.id == id } + + expect(model).not_to be_nil + expect(model.reasoning_option_values('effort')).to eq(%w[low medium high xhigh max]) + expect(model.metadata[:cost]).to include(cost) + end + end + + it 'resolves the claude-opus-5-5 alias to the anthropic provider' do + expect(RubyLLM.models.find('claude-opus-5-5').provider).to eq('anthropic') + end + + it 'resolves us.anthropic.claude-opus-5-5 when bedrock_region is configured' do + entry = RubyLLM::Model::Info.new( + id: 'us.anthropic.claude-opus-5-5', + name: 'Claude Opus 5.5 (US)', + provider: 'bedrock', + metadata: { 'inference_types' => ['INFERENCE_PROFILE'] } + ) + models = described_class.new([entry]) + allow(RubyLLM).to receive(:config).and_return( + instance_double(RubyLLM::Configuration, bedrock_region: 'us-west-2') + ) + found = models.find('us.anthropic.claude-opus-5-5', :bedrock) + expect(found.id).to eq('us.anthropic.claude-opus-5-5') + expect(found.provider).to eq('bedrock') + end +end From 6fad7207b6d0ea8b3b9b7d44bd12ff3d8dbb9655 Mon Sep 17 00:00:00 2001 From: Supernova Date: Tue, 22 Sep 2026 21:20:01 +0000 Subject: [PATCH 2/3] Pin json below 3.0 to fix CI on Rails 7.1/7.2 Gemfiles have no committed lockfile, so bundler resolves the unconstrained transitive json dependency (from async's console and from faraday) to whatever is newest at install time. json 3.0.2 drops the quirks_mode: keyword that ActiveSupport's legacy JSON encoder still passes on Rails 7.1/7.2, which broke schema loading for every spec run through rails_helper.rb (e.g. spec/ruby_llm/active_record/*) on the Ruby 4.0 / rails-7.2 CI job. Pin in the base Gemfile and regenerate the Appraisal gemfiles so every Rails version installs a compatible json. Co-authored-by: Sam Boland --- Gemfile | 3 +++ gemfiles/rails_7.1.gemfile | 1 + gemfiles/rails_7.2.gemfile | 1 + gemfiles/rails_8.0.gemfile | 1 + gemfiles/rails_8.1.gemfile | 1 + 5 files changed, 7 insertions(+) diff --git a/Gemfile b/Gemfile index 37a2a1243..34d2cd0ec 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,9 @@ group :development do # rubocop:disable Metrics/BlockLength gem 'flay' gem 'image_processing', '~> 1.2' gem 'irb' + # ActiveSupport's legacy JSON encoder passes a quirks_mode: keyword that + # json 3.x dropped, breaking schema/fixture loading on Rails 7.1/7.2. + gem 'json', '< 3.0' gem 'json-schema' gem 'nokogiri' gem 'overcommit', '>= 0.66' diff --git a/gemfiles/rails_7.1.gemfile b/gemfiles/rails_7.1.gemfile index b2bf88188..a136c8d28 100644 --- a/gemfiles/rails_7.1.gemfile +++ b/gemfiles/rails_7.1.gemfile @@ -13,6 +13,7 @@ group :development do gem "flay" gem "image_processing", "~> 1.2" gem "irb" + gem "json", "< 3.0" gem "json-schema" gem "nokogiri" gem "overcommit", ">= 0.66" diff --git a/gemfiles/rails_7.2.gemfile b/gemfiles/rails_7.2.gemfile index 292cdb12c..7cb7e050f 100644 --- a/gemfiles/rails_7.2.gemfile +++ b/gemfiles/rails_7.2.gemfile @@ -13,6 +13,7 @@ group :development do gem "flay" gem "image_processing", "~> 1.2" gem "irb" + gem "json", "< 3.0" gem "json-schema" gem "nokogiri" gem "overcommit", ">= 0.66" diff --git a/gemfiles/rails_8.0.gemfile b/gemfiles/rails_8.0.gemfile index 45759bd52..4a23796be 100644 --- a/gemfiles/rails_8.0.gemfile +++ b/gemfiles/rails_8.0.gemfile @@ -13,6 +13,7 @@ group :development do gem "flay" gem "image_processing", "~> 1.2" gem "irb" + gem "json", "< 3.0" gem "json-schema" gem "nokogiri" gem "overcommit", ">= 0.66" diff --git a/gemfiles/rails_8.1.gemfile b/gemfiles/rails_8.1.gemfile index 4c4b4c737..04ac53c2e 100644 --- a/gemfiles/rails_8.1.gemfile +++ b/gemfiles/rails_8.1.gemfile @@ -13,6 +13,7 @@ group :development do gem "flay" gem "image_processing", "~> 1.2" gem "irb" + gem "json", "< 3.0" gem "json-schema" gem "nokogiri" gem "overcommit", ">= 0.66" From 412c91c22079af19fe3121e93cafafa038f264b0 Mon Sep 17 00:00:00 2001 From: Supernova Date: Tue, 22 Sep 2026 21:45:39 +0000 Subject: [PATCH 3/3] Test bedrock region resolution against the real registry The us.anthropic.claude-opus-5-5 example built an in-memory Model::Info and stubbed RubyLLM.config wholesale, so it never exercised the actual registry entry or config object. Stub only bedrock_region on the real config and look the id up through RubyLLM.models, matching how the resolution path is used in practice. --- spec/ruby_llm/models_opus_5_5_spec.rb | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/spec/ruby_llm/models_opus_5_5_spec.rb b/spec/ruby_llm/models_opus_5_5_spec.rb index dc7d304ef..0eed9c190 100644 --- a/spec/ruby_llm/models_opus_5_5_spec.rb +++ b/spec/ruby_llm/models_opus_5_5_spec.rb @@ -38,18 +38,12 @@ end it 'resolves us.anthropic.claude-opus-5-5 when bedrock_region is configured' do - entry = RubyLLM::Model::Info.new( - id: 'us.anthropic.claude-opus-5-5', - name: 'Claude Opus 5.5 (US)', - provider: 'bedrock', - metadata: { 'inference_types' => ['INFERENCE_PROFILE'] } - ) - models = described_class.new([entry]) - allow(RubyLLM).to receive(:config).and_return( - instance_double(RubyLLM::Configuration, bedrock_region: 'us-west-2') - ) - found = models.find('us.anthropic.claude-opus-5-5', :bedrock) + allow(RubyLLM.config).to receive(:bedrock_region).and_return('us-west-2') + + found = RubyLLM.models.find('us.anthropic.claude-opus-5-5', :bedrock) + expect(found.id).to eq('us.anthropic.claude-opus-5-5') expect(found.provider).to eq('bedrock') + expect(found.reasoning_option_values('effort')).to eq(%w[low medium high xhigh max]) end end