diff --git a/CHANGELOG.md b/CHANGELOG.md index fc84647..e26e8e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## [Unreleased] +### Fixed +- `Raix::Configuration` no longer defaults `temperature` to `0.0`. The default was being injected into every request payload, which OpenRouter rejects with `404 No endpoints found that can handle the requested parameters` when routed to providers whose `supported_parameters` list omits `temperature` (notably Anthropic's Claude 4.7 family) and `provider.require_parameters: true` is set — which Raix sets automatically whenever `json: true` is passed to `chat_completion`. Callers that want a specific temperature should set one explicitly (`self.temperature = 0.0` on the including class, or `Raix.configure { |c| c.temperature = 0.0 }` globally); when unset, Raix now omits the parameter and the provider's own server-side default applies. `max_tokens`, `max_completion_tokens`, and `model` defaults are unchanged. + ## [2.0.4] - 2026-05-19 ### Fixed diff --git a/lib/raix/configuration.rb b/lib/raix/configuration.rb index bb8859c..bc23405 100644 --- a/lib/raix/configuration.rb +++ b/lib/raix/configuration.rb @@ -51,12 +51,19 @@ def self.attr_accessor_with_fallback(method_name) DEFAULT_MAX_TOKENS = 1000 DEFAULT_MAX_COMPLETION_TOKENS = 16_384 DEFAULT_MODEL = "meta-llama/llama-3.3-8b-instruct:free" - DEFAULT_TEMPERATURE = 0.0 DEFAULT_MAX_TOOL_CALLS = 25 # Initializes a new instance of the Configuration class with default values. + # + # Note: temperature is intentionally not defaulted. Setting a non-nil + # temperature here would force it into every request payload, and some + # providers (e.g. Anthropic's Claude 4.7 family on OpenRouter) do not list + # `temperature` in their supported parameters. Combined with + # `provider.require_parameters: true` (which Raix sets when `json: true`), + # an injected default of 0.0 causes OpenRouter to reject the request with + # "No endpoints found that can handle the requested parameters." Callers + # who want a specific temperature should set one explicitly. def initialize(fallback: nil) - self.temperature = DEFAULT_TEMPERATURE self.max_completion_tokens = DEFAULT_MAX_COMPLETION_TOKENS self.max_tokens = DEFAULT_MAX_TOKENS self.model = DEFAULT_MODEL diff --git a/spec/raix/default_temperature_spec.rb b/spec/raix/default_temperature_spec.rb new file mode 100644 index 0000000..e83ac86 --- /dev/null +++ b/spec/raix/default_temperature_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +# Regression coverage: Raix must not inject a default `temperature` into the +# request when the caller hasn't set one. +# +# Some OpenRouter-routed providers (notably Anthropic's Claude 4.7 family) do +# not list `temperature` in their `supported_parameters`. When `json: true` +# adds `provider.require_parameters: true` to the payload, an unsolicited +# `temperature: 0.0` from a Raix default causes OpenRouter to return +# "No endpoints found that can handle the requested parameters" (404). +class TemperatureDefaultProbe + include Raix::ChatCompletion +end + +RSpec.describe Raix::ChatCompletion, "default temperature handling" do + let(:instance) { TemperatureDefaultProbe.new } + + let(:fake_response_message) do + instance_double( + "RubyLLM::Message", + content: "ok", + tool_calls: nil, + tool_call?: false, + input_tokens: 1, + output_tokens: 1, + model_id: "anthropic/claude-opus-4-7", + raw: nil + ) + end + + let(:fake_chat) do + instance_double( + "RubyLLM::Chat", + with_instructions: nil, + add_message: nil, + with_temperature: nil, + with_params: nil, + with_tool: nil, + ask: fake_response_message, + complete: fake_response_message + ) + end + + before { allow(RubyLLM).to receive(:chat).and_return(fake_chat) } + + it "does not call with_temperature when no temperature is set anywhere" do + instance.send(:ruby_llm_request, + params: {}, + model: "anthropic/claude-opus-4-7", + messages: [{ role: "user", content: "hi" }]) + + expect(fake_chat).not_to have_received(:with_temperature) + end + + it "does not include temperature in additional params when unset" do + instance.send(:ruby_llm_request, + params: {}, + model: "anthropic/claude-opus-4-7", + messages: [{ role: "user", content: "hi" }]) + + expect(fake_chat).not_to have_received(:with_params) { |kwargs| + kwargs.key?(:temperature) + } + end + + it "still forwards an explicitly-set temperature (including 0.0)" do + instance.temperature = 0.0 + instance.chat_completion(messages: [{ user: "hi" }]) + + expect(fake_chat).to have_received(:with_temperature).with(0.0) + end + + it "forwards a class-level configured temperature" do + klass = Class.new do + include Raix::ChatCompletion + configure { |config| config.temperature = 0.3 } + end + klass.new.chat_completion(messages: [{ user: "hi" }]) + + expect(fake_chat).to have_received(:with_temperature).with(0.3) + end +end + +RSpec.describe Raix::Configuration, "temperature default" do + it "leaves temperature unset on a fresh configuration" do + expect(described_class.new.temperature).to be_nil + end + + it "still defaults max_tokens, max_completion_tokens, and model" do + config = described_class.new + + expect(config.max_tokens).to eq(Raix::Configuration::DEFAULT_MAX_TOKENS) + expect(config.max_completion_tokens).to eq(Raix::Configuration::DEFAULT_MAX_COMPLETION_TOKENS) + expect(config.model).to eq(Raix::Configuration::DEFAULT_MODEL) + end +end