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" 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..0eed9c190 --- /dev/null +++ b/spec/ruby_llm/models_opus_5_5_spec.rb @@ -0,0 +1,49 @@ +# 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 + 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