From b26749420c1212aa4a94864b3589fc8a515b2aa9 Mon Sep 17 00:00:00 2001 From: HKLHaoBin Date: Sun, 27 Sep 2026 12:09:41 +0800 Subject: [PATCH 1/2] fix(llm): omit temperature for OpenAI GPT-6 family (#1101) Official openai channel already skipped temperature for gpt-5* (#857); GPT-6 Astra/Sol/Luna reject non-default temperature the same way. Extend the omit rule and cover display-name / prefixed model ids in unit tests. --- .../app/crates/openless-core/src/omni.rs | 6 +- .../app/crates/openless-core/src/polish.rs | 70 ++++++++++++++++--- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/openless-all/app/crates/openless-core/src/omni.rs b/openless-all/app/crates/openless-core/src/omni.rs index 171daff84..4007c3687 100644 --- a/openless-all/app/crates/openless-core/src/omni.rs +++ b/openless-all/app/crates/openless-core/src/omni.rs @@ -16,7 +16,7 @@ use serde_json::{json, Value}; use crate::polish::{ append_utf8_sse_chunk, apply_openai_compatible_thinking_control, chat_completions_url, extract_assistant_content, finish_utf8_sse_chunks, http_client_builder, - openai_model_is_gpt5_family, safe_str_slice, send_with_transient_retry, LLMError, + openai_model_omits_custom_temperature, safe_str_slice, send_with_transient_retry, LLMError, }; pub const OMNI_GEMINI_PROVIDER_ID: &str = "gemini"; @@ -89,9 +89,9 @@ impl OpenAICompatibleOmni { "messages": messages, }); if let Some(temperature) = self.config.temperature { - // OpenAI 官方 gpt-5 系列只接受默认 temperature=1(issue #857),同润色路径。 + // OpenAI 官方 gpt-5 / gpt-6 只接受默认 temperature=1(#857 / #1101),同润色路径。 if !(self.config.provider_id.trim() == "openai" - && openai_model_is_gpt5_family(&self.config.model)) + && openai_model_omits_custom_temperature(&self.config.model)) { body["temperature"] = json!(temperature); } diff --git a/openless-all/app/crates/openless-core/src/polish.rs b/openless-all/app/crates/openless-core/src/polish.rs index 7b8e37198..8ce169f4e 100644 --- a/openless-all/app/crates/openless-core/src/polish.rs +++ b/openless-all/app/crates/openless-core/src/polish.rs @@ -798,11 +798,11 @@ impl OpenAICompatibleLLMProvider { "messages": messages, }); if let Some(temperature) = self.config.temperature { - // OpenAI 官方 gpt-5 系列在 Chat Completions 只接受默认 temperature=1, - // 传 0.3 会被 400 拒绝(issue #857)。官方渠道的 gpt-5* 不下发该字段, + // OpenAI 官方 gpt-5 / gpt-6 系列在 Chat Completions 只接受默认 temperature=1, + // 传 0.3 会被 400 拒绝(issue #857 / #1101)。官方渠道的这些模型不下发该字段, // 让服务端用默认值;其余模型保持原行为。 if !(self.config.provider_id.trim() == "openai" - && openai_model_is_gpt5_family(&self.config.model)) + && openai_model_omits_custom_temperature(&self.config.model)) { body["temperature"] = temperature_json(temperature); } @@ -1993,16 +1993,26 @@ pub(crate) fn openai_compatible_thinking_control_for_base_url( None } -/// OpenAI 官方 gpt-5 系列(gpt-5 / gpt-5-mini / gpt-5-nano / gpt-5.5 等)在 -/// Chat Completions 中只接受默认 temperature=1,传其它值会返回 400(issue #857)。 -/// 模型名归一化规则与 `openai_chat_reasoning_effort` 保持一致。 -pub(crate) fn openai_model_is_gpt5_family(model: &str) -> bool { +fn normalize_openai_model_id(model: &str) -> String { model .trim() .strip_prefix("openai/") .unwrap_or_else(|| model.trim()) .to_ascii_lowercase() - .starts_with("gpt-5") +} + +/// OpenAI 官方 gpt-5 系列(gpt-5 / gpt-5-mini / gpt-5-nano / gpt-5.5 等)。 +/// 模型名归一化规则与 `openai_chat_reasoning_effort` 保持一致。 +pub(crate) fn openai_model_is_gpt5_family(model: &str) -> bool { + normalize_openai_model_id(model).starts_with("gpt-5") +} + +/// OpenAI 官方渠道下应省略自定义 `temperature` 的模型族。 +/// gpt-5*(#857)与 gpt-6*(#1101,含 Astra/Sol/Luna)只接受服务端默认值。 +/// 展示名如 `GPT-6 Astra` 归一化后仍以 `gpt-6` 开头,一并覆盖。 +pub(crate) fn openai_model_omits_custom_temperature(model: &str) -> bool { + openai_model_is_gpt5_family(model) + || normalize_openai_model_id(model).starts_with("gpt-6") } fn openai_chat_reasoning_effort(model: &str, thinking_enabled: bool) -> Option<&'static str> { @@ -3394,6 +3404,32 @@ mod tests { } } + #[test] + fn chat_body_omits_temperature_for_openai_gpt6_family() { + for model in [ + "gpt-6-astra", + "gpt-6-sol", + "gpt-6-luna", + "GPT-6 Astra", + "openai/gpt-6-astra", + ] { + let provider = OpenAICompatibleLLMProvider::new(OpenAICompatibleConfig::new( + "openai", + "OpenAI", + "https://api.openai.com/v1", + "k", + model, + )); + + let body = provider.chat_body(false, vec![json!({ "role": "user", "content": "hi" })]); + + assert!( + body.get("temperature").is_none(), + "{model} must not receive temperature (issue #1101)" + ); + } + } + #[test] fn chat_body_keeps_default_temperature_for_openai_non_gpt5_models() { for model in ["gpt-4o", "gpt-4o-mini", "gpt-4.1"] { @@ -3411,6 +3447,24 @@ mod tests { } } + #[test] + fn chat_body_keeps_custom_temperature_for_gpt6_on_custom_provider() { + let provider = OpenAICompatibleLLMProvider::new( + OpenAICompatibleConfig::new( + "custom", + "Custom", + "https://api.openai.com/v1", + "k", + "gpt-6-astra", + ) + .with_temperature(Some(1.0)), + ); + + let body = provider.chat_body(false, vec![json!({ "role": "user", "content": "hi" })]); + + assert_eq!(body["temperature"], json!(1.0)); + } + #[test] fn chat_body_keeps_custom_temperature_for_gpt5_on_custom_provider() { // custom 预设由用户显式配温度(issue #857 的绕过路径:custom + temperature=1), From 2fdc4d33c60256965ad2f9e77db3535f2f37445d Mon Sep 17 00:00:00 2001 From: Chris233 Date: Mon, 28 Sep 2026 01:00:30 +0800 Subject: [PATCH 2/2] test(llm): tighten GPT-6 API model coverage --- openless-all/app/crates/openless-core/src/omni.rs | 15 +++++++++++++++ .../app/crates/openless-core/src/polish.rs | 15 +++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/openless-all/app/crates/openless-core/src/omni.rs b/openless-all/app/crates/openless-core/src/omni.rs index 4007c3687..2a5e944d0 100644 --- a/openless-all/app/crates/openless-core/src/omni.rs +++ b/openless-all/app/crates/openless-core/src/omni.rs @@ -553,6 +553,21 @@ mod tests { assert!((body["temperature"].as_f64().unwrap() - 0.3).abs() < 1e-6); } + #[test] + fn omni_body_omits_temperature_for_openai_gpt6_api_ids() { + for model in ["gpt-6-astra", "gpt-6-sol", "gpt-6-luna"] { + let mut omni = config(); + omni.model = model.into(); + let provider = OpenAICompatibleOmni::new(omni); + let body = provider.omni_body(false, vec![json!({"role": "user", "content": "x"})]); + + assert!( + body.get("temperature").is_none(), + "{model} must not receive temperature (issue #1101)" + ); + } + } + #[test] fn omni_gemini_routing_uses_provider_id_or_base_url() { assert!(!config().is_gemini()); diff --git a/openless-all/app/crates/openless-core/src/polish.rs b/openless-all/app/crates/openless-core/src/polish.rs index 8ce169f4e..75d572a7c 100644 --- a/openless-all/app/crates/openless-core/src/polish.rs +++ b/openless-all/app/crates/openless-core/src/polish.rs @@ -2009,18 +2009,13 @@ pub(crate) fn openai_model_is_gpt5_family(model: &str) -> bool { /// OpenAI 官方渠道下应省略自定义 `temperature` 的模型族。 /// gpt-5*(#857)与 gpt-6*(#1101,含 Astra/Sol/Luna)只接受服务端默认值。 -/// 展示名如 `GPT-6 Astra` 归一化后仍以 `gpt-6` 开头,一并覆盖。 +/// API 模型 ID 如 `gpt-6-astra` 归一化后以 `gpt-6` 开头,一并覆盖。 pub(crate) fn openai_model_omits_custom_temperature(model: &str) -> bool { - openai_model_is_gpt5_family(model) - || normalize_openai_model_id(model).starts_with("gpt-6") + openai_model_is_gpt5_family(model) || normalize_openai_model_id(model).starts_with("gpt-6") } fn openai_chat_reasoning_effort(model: &str, thinking_enabled: bool) -> Option<&'static str> { - let normalized = model - .trim() - .strip_prefix("openai/") - .unwrap_or_else(|| model.trim()) - .to_ascii_lowercase(); + let normalized = normalize_openai_model_id(model); if normalized.starts_with("gpt-5-pro") { return Some("high"); @@ -3405,12 +3400,11 @@ mod tests { } #[test] - fn chat_body_omits_temperature_for_openai_gpt6_family() { + fn chat_body_omits_temperature_for_openai_gpt6_api_ids() { for model in [ "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", - "GPT-6 Astra", "openai/gpt-6-astra", ] { let provider = OpenAICompatibleLLMProvider::new(OpenAICompatibleConfig::new( @@ -3423,6 +3417,7 @@ mod tests { let body = provider.chat_body(false, vec![json!({ "role": "user", "content": "hi" })]); + assert_eq!(body["model"], model); assert!( body.get("temperature").is_none(), "{model} must not receive temperature (issue #1101)"