FEATURE: allow disabling of top_p and temp for thinking models (#1184)

thinking models such as Claude 3.7 Thinking and o1 / o3 do not
support top_p or temp.

Previously you would have to carefully remove it from everywhere
by having it be a provider param we now support blanker removing
without forcing people to update automation rules or personas
This commit is contained in:
Sam 2025-03-11 16:54:02 +11:00 committed by GitHub
parent f4708d4178
commit 8f4cd2fcbd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 182 additions and 0 deletions

View File

@ -26,17 +26,23 @@ class LlmModel < ActiveRecord::Base
access_key_id: :text, access_key_id: :text,
region: :text, region: :text,
disable_native_tools: :checkbox, disable_native_tools: :checkbox,
disable_temperature: :checkbox,
disable_top_p: :checkbox,
enable_reasoning: :checkbox, enable_reasoning: :checkbox,
reasoning_tokens: :number, reasoning_tokens: :number,
}, },
anthropic: { anthropic: {
disable_native_tools: :checkbox, disable_native_tools: :checkbox,
disable_temperature: :checkbox,
disable_top_p: :checkbox,
enable_reasoning: :checkbox, enable_reasoning: :checkbox,
reasoning_tokens: :number, reasoning_tokens: :number,
}, },
open_ai: { open_ai: {
organization: :text, organization: :text,
disable_native_tools: :checkbox, disable_native_tools: :checkbox,
disable_temperature: :checkbox,
disable_top_p: :checkbox,
disable_streaming: :checkbox, disable_streaming: :checkbox,
reasoning_effort: { reasoning_effort: {
type: :enum, type: :enum,
@ -69,6 +75,8 @@ class LlmModel < ActiveRecord::Base
provider_order: :text, provider_order: :text,
provider_quantizations: :text, provider_quantizations: :text,
disable_streaming: :checkbox, disable_streaming: :checkbox,
disable_temperature: :checkbox,
disable_top_p: :checkbox,
}, },
} }
end end

View File

@ -488,6 +488,8 @@ en:
reasoning_effort: "Reasoning effort (only applicable to reasoning models)" reasoning_effort: "Reasoning effort (only applicable to reasoning models)"
enable_reasoning: "Enable reasoning (only applicable to Sonnet 3.7)" enable_reasoning: "Enable reasoning (only applicable to Sonnet 3.7)"
reasoning_tokens: "Number of tokens used for reasoning" reasoning_tokens: "Number of tokens used for reasoning"
disable_temperature: "Disable temperature (some thinking models don't support temperature)"
disable_top_p: "Disable top P (some thinking models don't support top P)"
related_topics: related_topics:
title: "Related topics" title: "Related topics"

View File

@ -10,6 +10,9 @@ module DiscourseAi
def normalize_model_params(model_params) def normalize_model_params(model_params)
# max_tokens, temperature, stop_sequences are already supported # max_tokens, temperature, stop_sequences are already supported
model_params = model_params.dup
model_params.delete(:top_p) if llm_model.lookup_custom_param("disable_top_p")
model_params.delete(:temperature) if llm_model.lookup_custom_param("disable_temperature")
model_params model_params
end end

View File

@ -16,6 +16,9 @@ module DiscourseAi
model_params = model_params.dup model_params = model_params.dup
# max_tokens, temperature, stop_sequences, top_p are already supported # max_tokens, temperature, stop_sequences, top_p are already supported
#
model_params.delete(:top_p) if llm_model.lookup_custom_param("disable_top_p")
model_params.delete(:temperature) if llm_model.lookup_custom_param("disable_temperature")
model_params model_params
end end

View File

@ -24,6 +24,9 @@ module DiscourseAi
model_params[:stop] = model_params.delete(:stop_sequences) model_params[:stop] = model_params.delete(:stop_sequences)
end end
model_params.delete(:top_p) if llm_model.lookup_custom_param("disable_top_p")
model_params.delete(:temperature) if llm_model.lookup_custom_param("disable_temperature")
model_params model_params
end end

View File

@ -16,6 +16,9 @@ module DiscourseAi
model_params[:stop] = model_params.delete(:stop_sequences) model_params[:stop] = model_params.delete(:stop_sequences)
end end
model_params.delete(:top_p) if llm_model.lookup_custom_param("disable_top_p")
model_params.delete(:temperature) if llm_model.lookup_custom_param("disable_temperature")
model_params model_params
end end

View File

@ -664,4 +664,54 @@ data: {"type":"content_block_start","index":0,"content_block":{"type":"redacted_
expect(log.feature_name).to eq("testing") expect(log.feature_name).to eq("testing")
expect(log.response_tokens).to eq(30) expect(log.response_tokens).to eq(30)
end end
describe "parameter disabling" do
it "excludes disabled parameters from the request" do
model.update!(provider_params: { disable_top_p: true, disable_temperature: true })
parsed_body = nil
stub_request(:post, url).with(
body:
proc do |req_body|
parsed_body = JSON.parse(req_body, symbolize_names: true)
true
end,
headers: {
"Content-Type" => "application/json",
"X-Api-Key" => "123",
"Anthropic-Version" => "2023-06-01",
},
).to_return(
status: 200,
body: {
id: "msg_123",
type: "message",
role: "assistant",
content: [{ type: "text", text: "test response" }],
model: "claude-3-opus-20240229",
usage: {
input_tokens: 10,
output_tokens: 5,
},
}.to_json,
)
# Request with parameters that should be ignored
llm.generate(
prompt,
user: Discourse.system_user,
top_p: 0.9,
temperature: 0.8,
max_tokens: 500,
)
# Verify disabled parameters aren't included
expect(parsed_body).not_to have_key(:top_p)
expect(parsed_body).not_to have_key(:temperature)
# Verify other parameters still work
expect(parsed_body).to have_key(:max_tokens)
expect(parsed_body[:max_tokens]).to eq(500)
end
end
end end

View File

@ -436,4 +436,52 @@ RSpec.describe DiscourseAi::Completions::Endpoints::AwsBedrock do
end end
end end
end end
describe "parameter disabling" do
it "excludes disabled parameters from the request" do
model.update!(
provider_params: {
access_key_id: "123",
region: "us-east-1",
disable_top_p: true,
disable_temperature: true,
},
)
proxy = DiscourseAi::Completions::Llm.proxy("custom:#{model.id}")
request = nil
content = {
content: [text: "test response"],
usage: {
input_tokens: 10,
output_tokens: 5,
},
}.to_json
stub_request(
:post,
"https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-3-sonnet-20240229-v1:0/invoke",
)
.with do |inner_request|
request = inner_request
true
end
.to_return(status: 200, body: content)
# Request with parameters that should be ignored
proxy.generate("test prompt", user: user, top_p: 0.9, temperature: 0.8, max_tokens: 500)
# Parse the request body
request_body = JSON.parse(request.body)
# Verify disabled parameters aren't included
expect(request_body).not_to have_key("top_p")
expect(request_body).not_to have_key("temperature")
# Verify other parameters still work
expect(request_body).to have_key("max_tokens")
expect(request_body["max_tokens"]).to eq(500)
end
end
end end

View File

@ -395,6 +395,37 @@ RSpec.describe DiscourseAi::Completions::Endpoints::OpenAi do
end end
end end
describe "parameter disabling" do
it "excludes disabled parameters from the request" do
model.update!(provider_params: { disable_top_p: true, disable_temperature: true })
parsed_body = nil
stub_request(:post, "https://api.openai.com/v1/chat/completions").with(
body:
proc do |req_body|
parsed_body = JSON.parse(req_body, symbolize_names: true)
true
end,
).to_return(
status: 200,
body: { choices: [{ message: { content: "test response" } }] }.to_json,
)
dialect = compliance.dialect(prompt: compliance.generic_prompt)
# Request with parameters that should be ignored
endpoint.perform_completion!(dialect, user, { top_p: 0.9, temperature: 0.8, max_tokens: 100 })
# Verify disabled parameters aren't included
expect(parsed_body).not_to have_key(:top_p)
expect(parsed_body).not_to have_key(:temperature)
# Verify other parameters still work
expect(parsed_body).to have_key(:max_tokens)
expect(parsed_body[:max_tokens]).to eq(100)
end
end
describe "image support" do describe "image support" do
it "can handle images" do it "can handle images" do
model = Fabricate(:llm_model, vision_enabled: true) model = Fabricate(:llm_model, vision_enabled: true)

View File

@ -44,4 +44,35 @@ RSpec.describe DiscourseAi::Completions::Endpoints::OpenRouter do
expect(parsed_body).to eq(expected) expect(parsed_body).to eq(expected)
end end
it "excludes disabled parameters from the request" do
open_router_model.update!(provider_params: { disable_top_p: true, disable_temperature: true })
parsed_body = nil
stub_request(:post, open_router_model.url).with(
body: proc { |body| parsed_body = JSON.parse(body, symbolize_names: true) },
headers: {
"Content-Type" => "application/json",
"X-Title" => "Discourse AI",
"HTTP-Referer" => "https://www.discourse.org/ai",
"Authorization" => "Bearer 123",
},
).to_return(
status: 200,
body: { "choices" => [message: { role: "assistant", content: "test response" }] }.to_json,
)
proxy = DiscourseAi::Completions::Llm.proxy("custom:#{open_router_model.id}")
# Request with parameters that should be ignored
proxy.generate("test", user: user, top_p: 0.9, temperature: 0.8, max_tokens: 500)
# Verify disabled parameters aren't included
expect(parsed_body).not_to have_key(:top_p)
expect(parsed_body).not_to have_key(:temperature)
# Verify other parameters still work
expect(parsed_body).to have_key(:max_tokens)
expect(parsed_body[:max_tokens]).to eq(500)
end
end end