From 23b03b23f7982183dfdf7188f7fe53a3386fa6cd Mon Sep 17 00:00:00 2001 From: Supernova Date: Wed, 2 Sep 2026 00:57:11 +0000 Subject: [PATCH 1/4] Add Claude Fable 5.1 model definitions and alias Register claude-fable-5-1 for anthropic, vertexai, and the eu/global/us bedrock regional inference profiles, mirroring Fable 5's narrower regional footprint (no au/jp variants). Cache-read pricing is cut ~75% per launch-day AWS/Microsoft coverage; that figure is unverified against Anthropic's own pricing page and flagged as such next to the pinned pricing spec, following the precedent set for GPT-5.6's cache-write pricing gap (87c91569). Co-authored-by: Sam Boland --- lib/ruby_llm/aliases.json | 6 + lib/ruby_llm/models.json | 341 +++++++++++++++++++++++++ spec/ruby_llm/models_fable_5_1_spec.rb | 56 ++++ 3 files changed, 403 insertions(+) create mode 100644 spec/ruby_llm/models_fable_5_1_spec.rb diff --git a/lib/ruby_llm/aliases.json b/lib/ruby_llm/aliases.json index eb453522b..ef746aae3 100644 --- a/lib/ruby_llm/aliases.json +++ b/lib/ruby_llm/aliases.json @@ -32,6 +32,12 @@ "bedrock": "us.anthropic.claude-fable-5", "vertexai": "claude-fable-5" }, + "claude-fable-5-1": { + "anthropic": "claude-fable-5-1", + "openrouter": "anthropic/claude-fable-5-1", + "bedrock": "us.anthropic.claude-fable-5-1", + "vertexai": "claude-fable-5-1" + }, "claude-haiku-4-5": { "anthropic": "claude-haiku-4-5-20251001", "openrouter": "anthropic/claude-haiku-4.5", diff --git a/lib/ruby_llm/models.json b/lib/ruby_llm/models.json index 28b241b02..ca8c58426 100644 --- a/lib/ruby_llm/models.json +++ b/lib/ruby_llm/models.json @@ -503,6 +503,72 @@ } } }, + { + "id": "claude-fable-5-1", + "name": "Claude Fable 5.1", + "provider": "anthropic", + "family": "claude-fable", + "created_at": "2026-08-31 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", + "citations" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 10, + "output_per_million": 50, + "cache_read_input_per_million": 0.25, + "cache_write_input_per_million": 12.5 + } + } + }, + "metadata": { + "source": "manual", + "provider_id": "anthropic", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-08-31", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 10, + "output": 50, + "cache_read": 0.25, + "cache_write": 12.5 + }, + "limit": { + "context": 1000000, + "output": 128000 + } + } + }, { "id": "claude-haiku-4-5", "name": "Claude Haiku 4.5 (latest)", @@ -12811,6 +12877,71 @@ "knowledge": "2026-01-31" } }, + { + "id": "eu.anthropic.claude-fable-5-1", + "name": "Claude Fable 5.1 (EU)", + "provider": "bedrock", + "family": "claude-fable", + "created_at": "2026-08-31 00:00:00 UTC", + "context_window": 1000000, + "max_output_tokens": 128000, + "knowledge_cutoff": "2026-01-31", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 11, + "output_per_million": 55, + "cache_read_input_per_million": 0.275, + "cache_write_input_per_million": 13.75 + } + } + }, + "metadata": { + "source": "manual", + "provider_id": "amazon-bedrock", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-08-31", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 11, + "output": 55, + "cache_read": 0.275, + "cache_write": 13.75 + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "knowledge": "2026-01-31" + } + }, { "id": "eu.anthropic.claude-haiku-4-5-20251001-v1:0", "name": "Claude Haiku 4.5 (EU)", @@ -13474,6 +13605,71 @@ "knowledge": "2026-01-31" } }, + { + "id": "global.anthropic.claude-fable-5-1", + "name": "Claude Fable 5.1 (Global)", + "provider": "bedrock", + "family": "claude-fable", + "created_at": "2026-08-31 00:00:00 UTC", + "context_window": 1000000, + "max_output_tokens": 128000, + "knowledge_cutoff": "2026-01-31", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "reasoning", + "vision" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 10, + "output_per_million": 50, + "cache_read_input_per_million": 0.25, + "cache_write_input_per_million": 12.5 + } + } + }, + "metadata": { + "source": "manual", + "provider_id": "amazon-bedrock", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-08-31", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 10, + "output": 50, + "cache_read": 0.25, + "cache_write": 12.5 + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "knowledge": "2026-01-31" + } + }, { "id": "global.anthropic.claude-haiku-4-5-20251001-v1:0", "name": "Claude Haiku 4.5 (Global)", @@ -18284,6 +18480,97 @@ "knowledge": "2026-01-31" } }, + { + "id": "us.anthropic.claude-fable-5-1", + "name": "Claude Fable 5.1 (US)", + "provider": "bedrock", + "family": "claude-fable", + "created_at": "2026-08-31 00:00:00 UTC", + "context_window": 1000000, + "max_output_tokens": 128000, + "knowledge_cutoff": "2026-01-31", + "modalities": { + "input": [ + "text", + "image" + ], + "output": [ + "text" + ] + }, + "capabilities": [ + "function_calling", + "reasoning", + "vision", + "streaming" + ], + "pricing": { + "text_tokens": { + "standard": { + "input_per_million": 10, + "output_per_million": 50, + "cache_read_input_per_million": 0.25, + "cache_write_input_per_million": 12.5 + } + } + }, + "metadata": { + "provider_name": "Anthropic", + "model_arn": "arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-fable-5-1", + "inference_types": [ + "INFERENCE_PROFILE" + ], + "converse": { + "additionalRequestFieldsSchema": "{\"reasoningConfig\":{\"enabled\":{\"type\":\"boolean\",\"default\":false},\"budgetTokens\":{\"type\":\"enum\",\"default\":\"low\",\"enum\":{\"low\":1024,\"medium\":40000,\"high\":63999},\"minimum\":1024,\"maximum\":63999}},\"hideSamplingParameter\":true}", + "maxTokensDefault": 4096, + "maxTokensMaximum": 128000, + "reasoningSupported": { + "embedded": false + }, + "stopSequencesDefault": [], + "systemRoleSupported": true, + "userDocumentTypesSupported": [ + "pdf" + ], + "userImageTypesSupported": [ + "jpeg", + "png", + "gif", + "webp" + ], + "userVideoTypesSupported": [] + }, + "source": "manual", + "provider_id": "amazon-bedrock", + "open_weights": false, + "attachment": true, + "temperature": false, + "last_updated": "2026-08-31", + "reasoning_options": [ + { + "type": "effort", + "values": [ + "low", + "medium", + "high", + "xhigh", + "max" + ] + } + ], + "cost": { + "input": 10, + "output": 50, + "cache_read": 0.25, + "cache_write": 12.5 + }, + "limit": { + "context": 1000000, + "output": 128000 + }, + "knowledge": "2026-01-31" + } + }, { "id": "us.anthropic.claude-haiku-4-5-20251001-v1:0", "name": "Claude Haiku 4.5 (US)", @@ -61213,6 +61500,60 @@ "publisher_model_template": null } }, + { + "id": "claude-fable-5-1", + "name": "claude-fable-5-1", + "provider": "vertexai", + "family": "claude-fable", + "created_at": null, + "context_window": null, + "max_output_tokens": null, + "knowledge_cutoff": null, + "modalities": { + "input": [], + "output": [] + }, + "capabilities": [ + "streaming", + "function_calling" + ], + "pricing": {}, + "metadata": { + "version_id": "default", + "open_source_category": "PROPRIETARY", + "launch_stage": "GA", + "supported_actions": { + "openNotebook": { + "references": { + "us-central1": { + "uri": "https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/generative_ai/anthropic_claude_intro.ipynb" + } + }, + "title": "Open Notebook" + }, + "openGenerationAiStudio": { + "references": { + "us-central1": { + "uri": "https://console.cloud.google.com/vertex-ai/generative/multimodal/create/text?model=claude-fable-5-1" + } + } + }, + "openNotebooks": { + "notebooks": [ + { + "references": { + "us-central1": { + "uri": "https://colab.research.google.com/github/GoogleCloudPlatform/vertex-ai-samples/blob/main/notebooks/official/generative_ai/anthropic_claude_intro.ipynb" + } + }, + "title": "Open Notebook" + } + ] + } + }, + "publisher_model_template": null + } + }, { "id": "claude-haiku-4-5", "name": "Claude Haiku 4.5", diff --git a/spec/ruby_llm/models_fable_5_1_spec.rb b/spec/ruby_llm/models_fable_5_1_spec.rb new file mode 100644 index 000000000..87a5ee8df --- /dev/null +++ b/spec/ruby_llm/models_fable_5_1_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe RubyLLM::Models do + include_context 'with configured RubyLLM' + + # cache_read_input_per_million (0.25 on the base/global/US entries, 0.275 on + # EU) is NOT independently verified against Anthropic's live pricing page. + # It comes from Anthropic's Claude Fable 5.1 launch announcement + # (anthropic.com/claude-fable-and-mythos-5-1) and same-day AWS/Microsoft + # partner blog posts reporting a ~75% cache-read price cut versus Claude + # Fable 5's cache_read_input_per_million of 1 (i.e. roughly 1/4 of Fable 5's + # rate, scaled per-region the same way Fable 5's own regional entries are). + # Base input/output pricing ($10/$50) and cache_write_input_per_million + # (12.5, i.e. 1.25x input, unchanged from Fable 5) are confirmed unchanged + # by the same sources. Revisit the cache_read figures once models.dev or + # Anthropic's pricing page publish real data for claude-fable-5-1. + { + 'claude-fable-5-1' => { input: 10, output: 50, cache_read: 0.25, cache_write: 12.5 }, + 'global.anthropic.claude-fable-5-1' => { input: 10, output: 50, cache_read: 0.25, cache_write: 12.5 }, + 'us.anthropic.claude-fable-5-1' => { input: 10, output: 50, cache_read: 0.25, cache_write: 12.5 }, + 'eu.anthropic.claude-fable-5-1' => { input: 11, output: 55, cache_read: 0.275, cache_write: 13.75 } + }.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-fable-5-1 alias to the anthropic provider' do + expect(RubyLLM.models.find('claude-fable-5-1').provider).to eq('anthropic') + end + + it 'resolves us.anthropic.claude-fable-5-1 when bedrock_region is configured' do + entry = RubyLLM::Model::Info.new( + id: 'us.anthropic.claude-fable-5-1', + name: 'Claude Fable 5.1 (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-fable-5-1', :bedrock) + expect(found.id).to eq('us.anthropic.claude-fable-5-1') + expect(found.provider).to eq('bedrock') + end +end From c6bb92ee0496b5587e781b7999d602d5ec483bad Mon Sep 17 00:00:00 2001 From: Supernova Date: Wed, 2 Sep 2026 01:04:55 +0000 Subject: [PATCH 2/4] Fix Style/DirectiveScope rubocop offenses blocking CI rubocop 1.90.0 (pinned in Gemfile.lock) flags disable/enable pairs wrapping a single statement; autocorrect (-A) to disable-next across the 13 pre-existing files this applies to, unrelated to the Fable 5.1 model registration in this branch but blocking its CI lint check. Co-authored-by: Sam Boland --- lib/generators/ruby_llm/generator_helpers.rb | 3 +-- lib/ruby_llm/active_record/chat_methods.rb | 3 +-- lib/ruby_llm/protocols/anthropic/chat.rb | 3 +-- lib/ruby_llm/protocols/chat_completions/chat.rb | 3 +-- lib/ruby_llm/protocols/converse/chat.rb | 3 +-- lib/ruby_llm/protocols/gemini/chat.rb | 3 +-- lib/ruby_llm/protocols/responses/chat.rb | 3 +-- lib/ruby_llm/provider.rb | 6 ++---- lib/ruby_llm/providers/mistral/chat.rb | 3 +-- lib/ruby_llm/providers/openrouter/chat.rb | 3 +-- spec/ruby_llm/active_record/acts_as_batch_spec.rb | 3 +-- spec/ruby_llm/generators/chat_ui_generator_spec.rb | 3 +-- spec/ruby_llm_gemspec_spec.rb | 3 +-- 13 files changed, 14 insertions(+), 28 deletions(-) diff --git a/lib/generators/ruby_llm/generator_helpers.rb b/lib/generators/ruby_llm/generator_helpers.rb index 653184ef2..514e013b9 100644 --- a/lib/generators/ruby_llm/generator_helpers.rb +++ b/lib/generators/ruby_llm/generator_helpers.rb @@ -195,7 +195,7 @@ def gem_in_bundle?(gem_name) private - # rubocop:disable Metrics/ParameterLists + # rubocop:disable-next Metrics/ParameterLists def add_association_params(params, default_assoc, table_name, model_name, owner_table:, owner_model_name:, plural: false) assoc = plural ? table_name.to_sym : table_name.singularize.to_sym @@ -207,7 +207,6 @@ def add_association_params(params, default_assoc, table_name, model_name, params << "#{default_assoc.to_s.singularize}_class: '#{model_name}'" if model_name != assoc.to_s.classify params << "#{default_assoc}_foreign_key: :#{foreign_key}" if foreign_key != default_foreign_key end - # rubocop:enable Metrics/ParameterLists def collection_association?(default_assoc, plural) plural || default_assoc.to_s.pluralize == default_assoc.to_s diff --git a/lib/ruby_llm/active_record/chat_methods.rb b/lib/ruby_llm/active_record/chat_methods.rb index f2e4365ea..9e0b32b0d 100644 --- a/lib/ruby_llm/active_record/chat_methods.rb +++ b/lib/ruby_llm/active_record/chat_methods.rb @@ -357,7 +357,7 @@ def persist_message_completion(message) end end - # rubocop:disable Metrics/PerceivedComplexity + # rubocop:disable-next Metrics/PerceivedComplexity def completion_attributes(message, content_text, tool_call_id) attrs = { role: message.role, content: content_text, input_tokens: message.input_tokens, output_tokens: message.output_tokens } @@ -376,7 +376,6 @@ def completion_attributes(message, content_text, tool_call_id) end attrs end - # rubocop:enable Metrics/PerceivedComplexity def persist_tool_calls(tool_calls, message_record: @message) tool_call_klass = message_record.tool_calls_association.klass diff --git a/lib/ruby_llm/protocols/anthropic/chat.rb b/lib/ruby_llm/protocols/anthropic/chat.rb index cebc19942..580c342bf 100644 --- a/lib/ruby_llm/protocols/anthropic/chat.rb +++ b/lib/ruby_llm/protocols/anthropic/chat.rb @@ -14,7 +14,7 @@ def completion_url 'v1/messages' end - # rubocop:disable Metrics/ParameterLists + # rubocop:disable-next Metrics/ParameterLists def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, citations: false, tool_prefs: nil) warn_unsupported_citations(model) if citations && !model.citations? @@ -26,7 +26,6 @@ def render_payload(messages, tools:, temperature:, model:, stream: false, add_optional_fields(payload, system_content:, tools:, tool_prefs:, temperature:, schema:) end end - # rubocop:enable Metrics/ParameterLists def warn_unsupported_citations(model) RubyLLM.logger.warn( diff --git a/lib/ruby_llm/protocols/chat_completions/chat.rb b/lib/ruby_llm/protocols/chat_completions/chat.rb index 80d96274a..46086f647 100644 --- a/lib/ruby_llm/protocols/chat_completions/chat.rb +++ b/lib/ruby_llm/protocols/chat_completions/chat.rb @@ -14,7 +14,7 @@ def completion_url module_function - # rubocop:disable Metrics/ParameterLists,Metrics/PerceivedComplexity + # rubocop:disable-next Metrics/ParameterLists,Metrics/PerceivedComplexity def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, citations: false, tool_prefs: nil) warn_unsupported_citations(model) if citations && !model.citations? @@ -53,7 +53,6 @@ def render_payload(messages, tools:, temperature:, model:, stream: false, schema payload[:stream_options] = { include_usage: true } if stream payload end - # rubocop:enable Metrics/ParameterLists,Metrics/PerceivedComplexity def warn_unsupported_citations(model) RubyLLM.logger.warn( diff --git a/lib/ruby_llm/protocols/converse/chat.rb b/lib/ruby_llm/protocols/converse/chat.rb index af41dc885..83addb6c1 100644 --- a/lib/ruby_llm/protocols/converse/chat.rb +++ b/lib/ruby_llm/protocols/converse/chat.rb @@ -28,7 +28,7 @@ def escape_model_id(model_id) model_id.to_s.gsub('/', '%2F') end - # rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument + # rubocop:disable-next Metrics/ParameterLists,Lint/UnusedMethodArgument def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, citations: false, tool_prefs: nil) warn_unsupported_citations(model) if citations @@ -55,7 +55,6 @@ def render_payload(messages, tools:, temperature:, model:, stream: false, payload end - # rubocop:enable Metrics/ParameterLists,Lint/UnusedMethodArgument def warn_unsupported_citations(model) RubyLLM.logger.warn( diff --git a/lib/ruby_llm/protocols/gemini/chat.rb b/lib/ruby_llm/protocols/gemini/chat.rb index 40be01922..3bc71cd80 100644 --- a/lib/ruby_llm/protocols/gemini/chat.rb +++ b/lib/ruby_llm/protocols/gemini/chat.rb @@ -18,7 +18,7 @@ def completion_url "models/#{@model.id}:generateContent" end - # rubocop:disable Metrics/ParameterLists,Metrics/PerceivedComplexity,Lint/UnusedMethodArgument + # rubocop:disable-next Metrics/ParameterLists,Metrics/PerceivedComplexity,Lint/UnusedMethodArgument def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, citations: false, tool_prefs: nil) warn_unsupported_citations(model) if citations && !model.citations? @@ -41,7 +41,6 @@ def render_payload(messages, tools:, temperature:, model:, stream: false, schema payload end - # rubocop:enable Metrics/ParameterLists,Metrics/PerceivedComplexity,Lint/UnusedMethodArgument def warn_unsupported_citations(model) RubyLLM.logger.warn( diff --git a/lib/ruby_llm/protocols/responses/chat.rb b/lib/ruby_llm/protocols/responses/chat.rb index f71c1ed46..50126fca4 100644 --- a/lib/ruby_llm/protocols/responses/chat.rb +++ b/lib/ruby_llm/protocols/responses/chat.rb @@ -14,7 +14,7 @@ def completion_url module_function - # rubocop:disable Metrics/ParameterLists,Metrics/PerceivedComplexity + # rubocop:disable-next Metrics/ParameterLists,Metrics/PerceivedComplexity def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, citations: false, tool_prefs: nil) warn_unsupported_citations(model) if citations && !model.citations? @@ -43,7 +43,6 @@ def render_payload(messages, tools:, temperature:, model:, stream: false, schema payload end - # rubocop:enable Metrics/ParameterLists,Metrics/PerceivedComplexity def parse_completion_response(response) parse_completion_body(response.body, raw: response) diff --git a/lib/ruby_llm/provider.rb b/lib/ruby_llm/provider.rb index 07143acbe..5a7d25dd2 100644 --- a/lib/ruby_llm/provider.rb +++ b/lib/ruby_llm/provider.rb @@ -50,7 +50,7 @@ def protocol_for(_model, **) default_protocol end - # rubocop:disable Metrics/ParameterLists + # rubocop:disable-next Metrics/ParameterLists def complete(messages, tools:, temperature:, model:, params: {}, headers: {}, schema: nil, thinking: nil, citations: false, tool_prefs: nil, protocol: nil, &) protocol_class = resolve_protocol(protocol, model, tools:, schema:, thinking:, tool_prefs:, citations:) @@ -67,10 +67,9 @@ def complete(messages, tools:, temperature:, model:, params: {}, headers: {}, sc & ) end - # rubocop:enable Metrics/ParameterLists # The request a completion call would send, without sending it. - # rubocop:disable Metrics/ParameterLists + # rubocop:disable-next Metrics/ParameterLists def render(messages, tools:, temperature:, model:, params: {}, schema: nil, thinking: nil, citations: false, tool_prefs: nil, protocol: nil) protocol_class = resolve_protocol(protocol, model, tools:, schema:, thinking:, tool_prefs:, citations:) @@ -85,7 +84,6 @@ def render(messages, tools:, temperature:, model:, params: {}, schema: nil, thin citations: citations ) end - # rubocop:enable Metrics/ParameterLists def preprocess_message(message, model:, protocol: nil) protocol_class = resolve_protocol( diff --git a/lib/ruby_llm/providers/mistral/chat.rb b/lib/ruby_llm/providers/mistral/chat.rb index 1c5ed2883..f05a36ad4 100644 --- a/lib/ruby_llm/providers/mistral/chat.rb +++ b/lib/ruby_llm/providers/mistral/chat.rb @@ -22,7 +22,7 @@ def format_messages(messages) end end - # rubocop:disable Metrics/ParameterLists + # rubocop:disable-next Metrics/ParameterLists def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, citations: false, tool_prefs: nil) payload = super @@ -31,7 +31,6 @@ def render_payload(messages, tools:, temperature:, model:, stream: false, normalize_required_tool_choice(payload) payload end - # rubocop:enable Metrics/ParameterLists def build_tool_choice(tool_choice) return 'any' if tool_choice == :required diff --git a/lib/ruby_llm/providers/openrouter/chat.rb b/lib/ruby_llm/providers/openrouter/chat.rb index 5abfed288..8eeb0edf0 100644 --- a/lib/ruby_llm/providers/openrouter/chat.rb +++ b/lib/ruby_llm/providers/openrouter/chat.rb @@ -10,7 +10,7 @@ module Chat module_function - # rubocop:disable Metrics/ParameterLists + # rubocop:disable-next Metrics/ParameterLists def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, citations: false, tool_prefs: nil) payload = super @@ -21,7 +21,6 @@ def render_payload(messages, tools:, temperature:, model:, stream: false, schema payload[:reasoning] = reasoning if reasoning payload end - # rubocop:enable Metrics/ParameterLists def strip_schema_strict(payload) schema_def = payload.dig(:response_format, :json_schema, :schema) diff --git a/spec/ruby_llm/active_record/acts_as_batch_spec.rb b/spec/ruby_llm/active_record/acts_as_batch_spec.rb index 9268a9461..a8211328a 100644 --- a/spec/ruby_llm/active_record/acts_as_batch_spec.rb +++ b/spec/ruby_llm/active_record/acts_as_batch_spec.rb @@ -7,13 +7,12 @@ let(:model) { 'claude-haiku-4-5' } - # rubocop:disable RSpec/AnyInstance + # rubocop:disable-next RSpec/AnyInstance def stub_anthropic_batch(create:, find:, results:) allow_any_instance_of(RubyLLM::Providers::Anthropic).to receive(:create_batch).and_return(create) allow_any_instance_of(RubyLLM::Providers::Anthropic).to receive(:find_batch).and_return(find) allow_any_instance_of(RubyLLM::Providers::Anthropic).to receive(:batch_results).and_return(results) end - # rubocop:enable RSpec/AnyInstance def answer(content) RubyLLM::Message.new(role: :assistant, content: content, input_tokens: 5, output_tokens: 1, model_id: model) diff --git a/spec/ruby_llm/generators/chat_ui_generator_spec.rb b/spec/ruby_llm/generators/chat_ui_generator_spec.rb index 9367fc456..6f2f6ae85 100644 --- a/spec/ruby_llm/generators/chat_ui_generator_spec.rb +++ b/spec/ruby_llm/generators/chat_ui_generator_spec.rb @@ -395,13 +395,12 @@ def expect_chat_script_to_succeed(script) end if config[:extra_view_example] - # rubocop:disable RSpec/NoExpectationExample + # rubocop:disable-next RSpec/NoExpectationExample it config[:extra_view_example] do within_test_app(app_path) do instance_exec(&config[:extra_view_assertions]) end end - # rubocop:enable RSpec/NoExpectationExample end it config[:functionality_example] do diff --git a/spec/ruby_llm_gemspec_spec.rb b/spec/ruby_llm_gemspec_spec.rb index 1a47b867d..48573b32b 100644 --- a/spec/ruby_llm_gemspec_spec.rb +++ b/spec/ruby_llm_gemspec_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -# rubocop:disable RSpec/DescribeClass +# rubocop:disable-next RSpec/DescribeClass RSpec.describe 'ruby_llm.gemspec' do subject(:gemspec) { Gem::Specification.load(File.expand_path('../ruby_llm.gemspec', __dir__)) } @@ -18,4 +18,3 @@ def runtime_dependency(name) expect(runtime_dependency('faraday-retry').requirement.to_s).to eq('>= 1') end end -# rubocop:enable RSpec/DescribeClass From b2aca32886ca6cfb2380a29066f199a174a9d025 Mon Sep 17 00:00:00 2001 From: Supernova Date: Wed, 2 Sep 2026 01:15:37 +0000 Subject: [PATCH 3/4] Exclude archspec from JRuby to fix native extension build failure archspec (a dev-group gem unused by any code in this repo) depends on rubydex, which has no jruby-platform prebuilt binary and fails to compile via mkmf under JRuby's toolchain, breaking the jruby-10.0.2.0/rails-7.1 CI job repo-wide. Restrict it to :mri, mirroring the existing 'async', platform: :mri line right below it. Co-authored-by: Sam Boland --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 47c5c80e9..37a2a1243 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ gemspec group :development do # rubocop:disable Metrics/BlockLength gem 'appraisal' - gem 'archspec' + gem 'archspec', platform: :mri gem 'async', platform: :mri gem 'bundler', '>= 2.0' gem 'colorize' From 7afd561a485da6b3b9cf6c1d7a21d14f3b9819b2 Mon Sep 17 00:00:00 2001 From: Supernova Date: Wed, 2 Sep 2026 01:21:37 +0000 Subject: [PATCH 4/4] Regenerate Appraisal gemfiles for the archspec JRuby fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The jruby-10.0.2.0/rails-7.1 CI job installs from gemfiles/rails_7.1.gemfile, not the root Gemfile — these are separate, committed files generated by `appraisal`. b2aca328 restricted archspec to :mri in the root Gemfile but didn't touch these, so the JRuby job kept resolving the unconstrained archspec -> rubydex dependency and failing the same way. Ran `bundle exec appraisal generate` to propagate the fix to all four. Co-authored-by: Sam Boland --- gemfiles/rails_7.1.gemfile | 2 +- gemfiles/rails_7.2.gemfile | 2 +- gemfiles/rails_8.0.gemfile | 2 +- gemfiles/rails_8.1.gemfile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gemfiles/rails_7.1.gemfile b/gemfiles/rails_7.1.gemfile index e8bd55b5c..b2bf88188 100644 --- a/gemfiles/rails_7.1.gemfile +++ b/gemfiles/rails_7.1.gemfile @@ -4,7 +4,7 @@ source "https://rubygems.org" group :development do gem "appraisal" - gem "archspec" + gem "archspec", platform: :mri gem "async", platform: :mri gem "bundler", ">= 2.0" gem "colorize" diff --git a/gemfiles/rails_7.2.gemfile b/gemfiles/rails_7.2.gemfile index 1a4586315..292cdb12c 100644 --- a/gemfiles/rails_7.2.gemfile +++ b/gemfiles/rails_7.2.gemfile @@ -4,7 +4,7 @@ source "https://rubygems.org" group :development do gem "appraisal" - gem "archspec" + gem "archspec", platform: :mri gem "async", platform: :mri gem "bundler", ">= 2.0" gem "colorize" diff --git a/gemfiles/rails_8.0.gemfile b/gemfiles/rails_8.0.gemfile index dbfc6d8bc..45759bd52 100644 --- a/gemfiles/rails_8.0.gemfile +++ b/gemfiles/rails_8.0.gemfile @@ -4,7 +4,7 @@ source "https://rubygems.org" group :development do gem "appraisal" - gem "archspec" + gem "archspec", platform: :mri gem "async", platform: :mri gem "bundler", ">= 2.0" gem "colorize" diff --git a/gemfiles/rails_8.1.gemfile b/gemfiles/rails_8.1.gemfile index 5d2ec9f63..4c4b4c737 100644 --- a/gemfiles/rails_8.1.gemfile +++ b/gemfiles/rails_8.1.gemfile @@ -4,7 +4,7 @@ source "https://rubygems.org" group :development do gem "appraisal" - gem "archspec" + gem "archspec", platform: :mri gem "async", platform: :mri gem "bundler", ">= 2.0" gem "colorize"