* 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. * test(llm): tighten GPT-6 API model coverage --------- Co-authored-by: Chris233 <h-chris233@outlook.com>
This commit is contained in:
1 parent
820d7acf7a
commit
5b39599486
2 files changed
+80
-16
No files matched your search
@@ -16,7 +16,7 @@ use serde_json::{json, Value};
|
|||||||
use crate::polish::{
|
use crate::polish::{
|
||||||
append_utf8_sse_chunk, apply_openai_compatible_thinking_control, chat_completions_url,
|
append_utf8_sse_chunk, apply_openai_compatible_thinking_control, chat_completions_url,
|
||||||
extract_assistant_content, finish_utf8_sse_chunks, http_client_builder,
|
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";
|
pub const OMNI_GEMINI_PROVIDER_ID: &str = "gemini";
|
||||||
@@ -89,9 +89,9 @@ impl OpenAICompatibleOmni {
|
|||||||
"messages": messages,
|
"messages": messages,
|
||||||
});
|
});
|
||||||
if let Some(temperature) = self.config.temperature {
|
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"
|
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);
|
body["temperature"] = json!(temperature);
|
||||||
}
|
}
|
||||||
@@ -553,6 +553,21 @@ mod tests {
|
|||||||
assert!((body["temperature"].as_f64().unwrap() - 0.3).abs() < 1e-6);
|
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]
|
#[test]
|
||||||
fn omni_gemini_routing_uses_provider_id_or_base_url() {
|
fn omni_gemini_routing_uses_provider_id_or_base_url() {
|
||||||
assert!(!config().is_gemini());
|
assert!(!config().is_gemini());
|
||||||
|
|||||||
@@ -798,11 +798,11 @@ impl OpenAICompatibleLLMProvider {
|
|||||||
"messages": messages,
|
"messages": messages,
|
||||||
});
|
});
|
||||||
if let Some(temperature) = self.config.temperature {
|
if let Some(temperature) = self.config.temperature {
|
||||||
// OpenAI 官方 gpt-5 系列在 Chat Completions 只接受默认 temperature=1,
|
// OpenAI 官方 gpt-5 / gpt-6 系列在 Chat Completions 只接受默认 temperature=1,
|
||||||
// 传 0.3 会被 400 拒绝(issue #857)。官方渠道的 gpt-5* 不下发该字段,
|
// 传 0.3 会被 400 拒绝(issue #857 / #1101)。官方渠道的这些模型不下发该字段,
|
||||||
// 让服务端用默认值;其余模型保持原行为。
|
// 让服务端用默认值;其余模型保持原行为。
|
||||||
if !(self.config.provider_id.trim() == "openai"
|
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);
|
body["temperature"] = temperature_json(temperature);
|
||||||
}
|
}
|
||||||
@@ -1993,24 +1993,29 @@ pub(crate) fn openai_compatible_thinking_control_for_base_url(
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OpenAI 官方 gpt-5 系列(gpt-5 / gpt-5-mini / gpt-5-nano / gpt-5.5 等)在
|
fn normalize_openai_model_id(model: &str) -> String {
|
||||||
/// Chat Completions 中只接受默认 temperature=1,传其它值会返回 400(issue #857)。
|
|
||||||
/// 模型名归一化规则与 `openai_chat_reasoning_effort` 保持一致。
|
|
||||||
pub(crate) fn openai_model_is_gpt5_family(model: &str) -> bool {
|
|
||||||
model
|
model
|
||||||
.trim()
|
.trim()
|
||||||
.strip_prefix("openai/")
|
.strip_prefix("openai/")
|
||||||
.unwrap_or_else(|| model.trim())
|
.unwrap_or_else(|| model.trim())
|
||||||
.to_ascii_lowercase()
|
.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)只接受服务端默认值。
|
||||||
|
/// 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn openai_chat_reasoning_effort(model: &str, thinking_enabled: bool) -> Option<&'static str> {
|
fn openai_chat_reasoning_effort(model: &str, thinking_enabled: bool) -> Option<&'static str> {
|
||||||
let normalized = model
|
let normalized = normalize_openai_model_id(model);
|
||||||
.trim()
|
|
||||||
.strip_prefix("openai/")
|
|
||||||
.unwrap_or_else(|| model.trim())
|
|
||||||
.to_ascii_lowercase();
|
|
||||||
|
|
||||||
if normalized.starts_with("gpt-5-pro") {
|
if normalized.starts_with("gpt-5-pro") {
|
||||||
return Some("high");
|
return Some("high");
|
||||||
@@ -3394,6 +3399,32 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chat_body_omits_temperature_for_openai_gpt6_api_ids() {
|
||||||
|
for model in [
|
||||||
|
"gpt-6-astra",
|
||||||
|
"gpt-6-sol",
|
||||||
|
"gpt-6-luna",
|
||||||
|
"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_eq!(body["model"], model);
|
||||||
|
assert!(
|
||||||
|
body.get("temperature").is_none(),
|
||||||
|
"{model} must not receive temperature (issue #1101)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn chat_body_keeps_default_temperature_for_openai_non_gpt5_models() {
|
fn chat_body_keeps_default_temperature_for_openai_non_gpt5_models() {
|
||||||
for model in ["gpt-4o", "gpt-4o-mini", "gpt-4.1"] {
|
for model in ["gpt-4o", "gpt-4o-mini", "gpt-4.1"] {
|
||||||
@@ -3411,6 +3442,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]
|
#[test]
|
||||||
fn chat_body_keeps_custom_temperature_for_gpt5_on_custom_provider() {
|
fn chat_body_keeps_custom_temperature_for_gpt5_on_custom_provider() {
|
||||||
// custom 预设由用户显式配温度(issue #857 的绕过路径:custom + temperature=1),
|
// custom 预设由用户显式配温度(issue #857 的绕过路径:custom + temperature=1),
|
||||||
|
|||||||
Reference in new issue
Block a user