From 3e018408258e8822729eb4d803463f9c7fed4b8f Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Thu, 2 Jul 2026 17:53:06 +0800 Subject: [PATCH] fix(ai): remove temperature from AI requests --- apps/desktop/src/lib/ai.ts | 21 ++-- apps/desktop/src/lib/tauri.ts | 1 - crates/dbx-core/src/agent_loop.rs | 8 -- crates/dbx-core/src/ai.rs | 153 +++++------------------------- crates/dbx-web/src/routes/ai.rs | 2 - src-tauri/src/commands/ai.rs | 1 - 6 files changed, 35 insertions(+), 151 deletions(-) diff --git a/apps/desktop/src/lib/ai.ts b/apps/desktop/src/lib/ai.ts index 88d2a56f9b..c47dd41b09 100644 --- a/apps/desktop/src/lib/ai.ts +++ b/apps/desktop/src/lib/ai.ts @@ -70,7 +70,7 @@ export interface AiRequestInput { context: AiContext; } -function buildAgentRequest(input: AiRequestInput, history?: api.AiMessage[]): { messages: api.AiMessage[]; systemPrompt: string; taskContract: api.AiTaskContract; maxTokens: number; temperature: number } { +function buildAgentRequest(input: AiRequestInput, history?: api.AiMessage[]): { messages: api.AiMessage[]; systemPrompt: string; taskContract: api.AiTaskContract; maxTokens: number } { const isZh = isChineseLocale(currentLocale()); const systemPrompt = buildSystemPrompt(input.action, input.context, input.mode); const userPrompt = buildUserPrompt(input.action, input.context, input.instruction, isZh); @@ -84,23 +84,22 @@ function buildAgentRequest(input: AiRequestInput, history?: api.AiMessage[]): { const params = actionParams(input.action); const maxTokens = input.config.enableThinking ? Math.max(params.maxTokens, 8192) : params.maxTokens; - return { messages, systemPrompt, taskContract, maxTokens, temperature: params.temperature }; + return { messages, systemPrompt, taskContract, maxTokens }; } export async function runAiAction(input: AiRequestInput, history?: api.AiMessage[]): Promise { - const { messages, systemPrompt, taskContract, maxTokens, temperature } = buildAgentRequest(input, history); + const { messages, systemPrompt, taskContract, maxTokens } = buildAgentRequest(input, history); return api.aiComplete({ config: input.config, systemPrompt, messages, taskContract, maxTokens, - temperature, }); } export async function runAiStream(input: AiRequestInput, history: api.AiMessage[] | undefined, onDelta: (delta: string) => void, sessionId?: string, onReasoningDelta?: (delta: string) => void): Promise { - const { messages, systemPrompt, taskContract, maxTokens, temperature } = buildAgentRequest(input, history); + const { messages, systemPrompt, taskContract, maxTokens } = buildAgentRequest(input, history); const sid = sessionId || uuid(); await api.aiStream( @@ -111,7 +110,6 @@ export async function runAiStream(input: AiRequestInput, history: api.AiMessage[ messages, taskContract, maxTokens, - temperature, }, (chunk) => { if (!chunk.done) { @@ -123,7 +121,7 @@ export async function runAiStream(input: AiRequestInput, history: api.AiMessage[ } export async function runAgentStream(input: AiRequestInput, history: api.AiMessage[] | undefined, onEvent: (event: AgentEvent) => void, sessionId?: string): Promise { - const { messages, systemPrompt, taskContract, maxTokens, temperature } = buildAgentRequest(input, history); + const { messages, systemPrompt, taskContract, maxTokens } = buildAgentRequest(input, history); const sid = sessionId || uuid(); return api.aiAgentStream( @@ -134,7 +132,6 @@ export async function runAgentStream(input: AiRequestInput, history: api.AiMessa messages, taskContract, maxTokens, - temperature, }, input.context.connectionId, input.context.database, @@ -155,14 +152,14 @@ export function buildUserPrompt(action: AiAction, context: AiContext, instructio return [`Action: ${action}`, skillInstruction, "", "User request:", userRequest].join("\n"); } -function actionParams(action: AiAction): { maxTokens: number; temperature: number } { +function actionParams(action: AiAction): { maxTokens: number } { switch (action) { case "explain": - return { maxTokens: 3200, temperature: 0.2 }; + return { maxTokens: 3200 }; case "sampleData": - return { maxTokens: 2400, temperature: 0.1 }; + return { maxTokens: 2400 }; default: - return { maxTokens: 2400, temperature: 0.15 }; + return { maxTokens: 2400 }; } } diff --git a/apps/desktop/src/lib/tauri.ts b/apps/desktop/src/lib/tauri.ts index dfc69db131..ba4b491783 100644 --- a/apps/desktop/src/lib/tauri.ts +++ b/apps/desktop/src/lib/tauri.ts @@ -275,7 +275,6 @@ export interface AiCompletionRequest { messages: AiMessage[]; taskContract?: AiTaskContract; maxTokens?: number; - temperature?: number; } export interface AiModelInfo { diff --git a/crates/dbx-core/src/agent_loop.rs b/crates/dbx-core/src/agent_loop.rs index 6c4fbf5ef9..6f18fc6141 100644 --- a/crates/dbx-core/src/agent_loop.rs +++ b/crates/dbx-core/src/agent_loop.rs @@ -82,7 +82,6 @@ pub async fn run_agent_loop( on_event: impl Fn(AgentEvent) + Send + Sync + Clone + 'static, cancelled: &Notify, max_tokens: Option, - temperature: Option, task_contract: Option<&AiTaskContract>, is_agent_mode: bool, ) -> Result { @@ -124,7 +123,6 @@ pub async fn run_agent_loop( on_event, cancelled, max_tokens, - temperature, task_contract, ) .await; @@ -181,7 +179,6 @@ pub async fn run_agent_loop( &conversation_messages, &tools, max_tokens, - temperature, task_contract.clone(), ); @@ -434,7 +431,6 @@ fn build_tool_request( messages: &[AiMessage], _tools: &[ToolDefinition], // Tools are injected in ai::stream_with_tools, not via AiCompletionRequest. max_tokens: Option, - temperature: Option, task_contract: Option, ) -> AiCompletionRequest { // Note: tools are passed via the body, not via AiCompletionRequest. @@ -445,7 +441,6 @@ fn build_tool_request( messages: messages.to_vec(), task_contract, max_tokens: max_tokens.or(Some(4096)), - temperature: temperature.or(Some(0.2)), } } @@ -652,7 +647,6 @@ async fn run_agent_loop_text_only( on_event: impl Fn(AgentEvent) + Send + Sync + 'static, _cancelled: &Notify, max_tokens: Option, - temperature: Option, task_contract: Option<&AiTaskContract>, ) -> Result { // Build a schema-enriched system prompt so the LLM can answer schema questions @@ -665,7 +659,6 @@ async fn run_agent_loop_text_only( messages: messages.to_vec(), task_contract: task_contract.cloned(), max_tokens: max_tokens.or(Some(4096)), - temperature: temperature.or(Some(0.2)), }; for attempt in 0..=MAX_CONTRACT_REPAIR_ATTEMPTS { @@ -898,7 +891,6 @@ async fn maybe_compact( }], task_contract: None, max_tokens: Some(1024), - temperature: Some(0.1), }; let summary = match cancelled.notified().now_or_never() { diff --git a/crates/dbx-core/src/ai.rs b/crates/dbx-core/src/ai.rs index 4bc57ba3c4..2d79bbf425 100644 --- a/crates/dbx-core/src/ai.rs +++ b/crates/dbx-core/src/ai.rs @@ -177,7 +177,6 @@ pub struct AiCompletionRequest { #[serde(default, skip_serializing_if = "Option::is_none")] pub task_contract: Option, pub max_tokens: Option, - pub temperature: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -495,8 +494,8 @@ fn set_chat_completion_token_limit(body: &mut serde_json::Value, config: &AiConf } } -/// Kimi K2.5+ models (including K2.7-Code) require fixed sampling parameters: -/// temperature=1.0, top_p=0.95, n=1. Any other value returns an error. +/// Kimi K2.5+ models (including K2.7-Code) handle thinking flags differently +/// and reject the OpenAI-compatible `extra_body.chat_template_kwargs` toggle. /// /// Matches `kimi-k2.5`, `kimi-k2.6`, `kimi-k2.7-code`, K3+, and future versions, /// while excluding older K2 variants (`kimi-k2`, `kimi-k2-thinking`, etc.). @@ -516,25 +515,6 @@ fn is_kimi_model(model: &str) -> bool { } } -pub fn supports_temperature(config: &AiConfig) -> bool { - !(is_kimi_model(&config.model) || uses_openai_max_completion_tokens(config)) -} - -fn add_temperature_if_supported_for_config(body: &mut serde_json::Value, config: &AiConfig, temperature: Option) { - if supports_temperature(config) { - body["temperature"] = temperature_value(temperature); - } -} - -pub fn add_temperature_if_supported(body: &mut serde_json::Value, request: &AiCompletionRequest) { - add_temperature_if_supported_for_config(body, &request.config, request.temperature); -} - -fn temperature_value(temperature: Option) -> serde_json::Value { - let value = ((temperature.unwrap_or(0.2) as f64) * 100.0).round() / 100.0; - json!(value) -} - fn responses_text(data: &serde_json::Value) -> String { if let Some(text) = data["output_text"].as_str().filter(|text| !text.is_empty()) { return text.to_string(); @@ -878,7 +858,6 @@ pub async fn call_claude(client: &reqwest::Client, request: AiCompletionRequest) let body = json!({ "model": request.config.model, "max_tokens": request.max_tokens.unwrap_or(2048), - "temperature": temperature_value(request.temperature), "system": claude_system_prompt(&request.system_prompt), "messages": request.messages, }); @@ -915,7 +894,6 @@ pub async fn call_openai_compatible(client: &reqwest::Client, request: AiComplet "messages": messages, }); set_chat_completion_token_limit(&mut body_obj, &request.config, request.max_tokens.unwrap_or(2048)); - add_temperature_if_supported(&mut body_obj, &request); if !request.config.enable_thinking && !is_kimi_model(&request.config.model) { body_obj["extra_body"] = json!({ "chat_template_kwargs": { "enable_thinking": false } @@ -942,12 +920,11 @@ pub async fn call_openai_compatible(client: &reqwest::Client, request: AiComplet pub async fn call_responses_api(client: &reqwest::Client, request: AiCompletionRequest) -> Result { let headers = maybe_bearer_headers(&request.config)?; - let mut body = json!({ + let body = json!({ "model": request.config.model, "input": build_responses_input(&request.system_prompt, &request.messages), "max_output_tokens": responses_max_output_tokens(request.max_tokens), }); - add_temperature_if_supported(&mut body, &request); let res = client .post(resolve_endpoint(&request.config)) @@ -983,7 +960,6 @@ pub async fn call_gemini(client: &reqwest::Client, request: AiCompletionRequest) "contents": contents, "generationConfig": { "maxOutputTokens": request.max_tokens.unwrap_or(2048), - "temperature": temperature_value(request.temperature), }, }); @@ -1100,7 +1076,6 @@ pub async fn test_connection_core(config: &AiConfig) -> Result Result Result Result Result<(), String> { let headers = maybe_bearer_headers(&request.config)?; - let mut body = json!({ + let body = json!({ "model": request.config.model, "input": build_responses_input(&request.system_prompt, &request.messages), "max_output_tokens": responses_max_output_tokens(request.max_tokens), "stream": true, }); - add_temperature_if_supported(&mut body, request); let res = client .post(resolve_endpoint(&request.config)) @@ -1608,7 +1578,6 @@ async fn stream_gemini( "contents": contents, "generationConfig": { "maxOutputTokens": request.max_tokens.unwrap_or(2048), - "temperature": temperature_value(request.temperature), }, }); @@ -1813,7 +1782,7 @@ async fn stream_claude_with_tools( let tool_json: Vec = tools.iter().map(|t| t.to_anthropic_tool()).collect(); - let mut body = json!({ + let body = json!({ "model": request.config.model, "max_tokens": request.max_tokens.unwrap_or(4096), "system": claude_system_prompt(&request.system_prompt), @@ -1821,7 +1790,6 @@ async fn stream_claude_with_tools( "tools": tool_json, "stream": true, }); - add_temperature_if_supported(&mut body, request); let res = client .post(resolve_endpoint(&request.config)) @@ -1995,7 +1963,6 @@ async fn stream_openai_with_tools( "stream_options": { "include_usage": true }, }); set_chat_completion_token_limit(&mut body, &request.config, request.max_tokens.unwrap_or(4096)); - add_temperature_if_supported(&mut body, request); let res = client .post(resolve_endpoint(&request.config)) @@ -2098,7 +2065,7 @@ async fn stream_responses_with_tools( let headers = maybe_bearer_headers(&request.config)?; let tool_json: Vec = tools.iter().map(responses_function_tool).collect(); - let mut body = json!({ + let body = json!({ "model": request.config.model, "input": build_responses_input_with_tools(&request.system_prompt, &request.messages), "max_output_tokens": responses_max_output_tokens(request.max_tokens), @@ -2106,7 +2073,6 @@ async fn stream_responses_with_tools( "tool_choice": "auto", "stream": true, }); - add_temperature_if_supported(&mut body, request); let res = client .post(resolve_endpoint(&request.config)) @@ -2477,14 +2443,14 @@ mod tests { use std::collections::{HashMap, HashSet}; use super::{ - add_temperature_if_supported_for_config, build_ai_http_client, build_responses_input_with_tools, - claude_headers, claude_system_prompt, drain_next_stream_line, emit_responses_function_call_item, gemini_text, - is_kimi_model, openai_response_text, openai_stream_reasoning, openai_stream_text, parse_model_list_response, - resolve_endpoint, resolve_model_list_endpoint, responses_function_tool, responses_max_output_tokens, - responses_stream_text, responses_text, responses_token_usage, set_chat_completion_token_limit, - stream_data_payload, supports_temperature, temperature_value, uses_anthropic_messages_api, validate_config, - AiApiStyle, AiAuthMethod, AiConfig, AiMessage, AiModelInfo, AiProvider, AiReasoningLevel, StreamToolEvent, - StreamingToolCallAccumulator, ToolCallRef, AUTHORIZATION, CLAUDE_DEFAULT_SYSTEM, TEST_PROMPT, + build_ai_http_client, build_responses_input_with_tools, claude_headers, claude_system_prompt, + drain_next_stream_line, emit_responses_function_call_item, gemini_text, is_kimi_model, openai_response_text, + openai_stream_reasoning, openai_stream_text, parse_model_list_response, resolve_endpoint, + resolve_model_list_endpoint, responses_function_tool, responses_max_output_tokens, responses_stream_text, + responses_text, responses_token_usage, set_chat_completion_token_limit, stream_data_payload, + uses_anthropic_messages_api, validate_config, AiApiStyle, AiAuthMethod, AiConfig, AiMessage, AiModelInfo, + AiProvider, AiReasoningLevel, StreamToolEvent, StreamingToolCallAccumulator, ToolCallRef, AUTHORIZATION, + CLAUDE_DEFAULT_SYSTEM, TEST_PROMPT, }; /// Reproduce the "Unknown tool:" bug: some OpenAI-compatible providers @@ -3078,82 +3044,17 @@ mod tests { } #[test] - fn temperature_value_rounds_f32_to_provider_safe_precision() { - assert_eq!(temperature_value(Some(0.15)), serde_json::json!(0.15)); - assert_eq!(temperature_value(Some(0.149)), serde_json::json!(0.15)); - assert_eq!(temperature_value(None), serde_json::json!(0.2)); - assert_eq!(serde_json::to_string(&temperature_value(Some(0.15))).unwrap(), "0.15"); - } + fn detects_kimi_models_that_skip_extra_body_thinking_toggle() { + assert!(is_kimi_model("kimi-k2.7-code")); + assert!(is_kimi_model("kimi-k2.6")); + assert!(is_kimi_model("kimi-k2.5")); + assert!(is_kimi_model("kimi-k3")); - #[test] - fn omits_temperature_for_openai_reasoning_models() { - let mut config = AiConfig { - provider: AiProvider::Openai, - api_key: "key".to_string(), - auth_method: AiAuthMethod::Bearer, - endpoint: "https://api.openai.com/v1/chat/completions".to_string(), - model: "gpt-5.5".to_string(), - api_style: AiApiStyle::Completions, - proxy_enabled: false, - proxy_url: String::new(), - enable_thinking: true, - reasoning_level: AiReasoningLevel::Default, - context_window: None, - codex_cli_path: None, - codex_cli_env: Default::default(), - }; - - assert!(!supports_temperature(&config)); - - config.model = "o4-mini".to_string(); - assert!(!supports_temperature(&config)); - - config.model = "gpt-4o".to_string(); - assert!(supports_temperature(&config)); - - config.endpoint = "http://localhost:11434/v1".to_string(); - config.model = "gpt-5-proxy".to_string(); - assert!(!supports_temperature(&config)); - - config.provider = AiProvider::OpenaiCompatible; - config.endpoint = "http://localhost:11434/v1".to_string(); - config.model = "gpt-5-local".to_string(); - assert!(supports_temperature(&config)); - - // Kimi K2.5+ models: temperature is forced to 1.0 by the API - config.model = "kimi-k2.7-code".to_string(); - assert!(!supports_temperature(&config)); - assert!(is_kimi_model(&config.model)); - - config.model = "kimi-k2.6".to_string(); - assert!(!supports_temperature(&config)); - assert!(is_kimi_model(&config.model)); - - config.model = "kimi-k2.5".to_string(); - assert!(!supports_temperature(&config)); - assert!(is_kimi_model(&config.model)); - - // K3+ should also be matched - config.model = "kimi-k3".to_string(); - assert!(!supports_temperature(&config)); - assert!(is_kimi_model(&config.model)); - - // Older K2 variants should NOT be matched (they support custom temperature) - config.model = "kimi-k2".to_string(); - assert!(supports_temperature(&config)); - assert!(!is_kimi_model(&config.model)); - - config.model = "kimi-k2-thinking".to_string(); - assert!(supports_temperature(&config)); - assert!(!is_kimi_model(&config.model)); - - config.model = "kimi-k2-0711-preview".to_string(); - assert!(supports_temperature(&config)); - assert!(!is_kimi_model(&config.model)); - - config.model = "kimi-k2.4".to_string(); - assert!(supports_temperature(&config)); - assert!(!is_kimi_model(&config.model)); + // Older K2 variants should not skip OpenAI-compatible thinking toggles. + assert!(!is_kimi_model("kimi-k2")); + assert!(!is_kimi_model("kimi-k2-thinking")); + assert!(!is_kimi_model("kimi-k2-0711-preview")); + assert!(!is_kimi_model("kimi-k2.4")); } #[test] @@ -3222,7 +3123,7 @@ mod tests { } #[test] - fn omits_temperature_for_kimi_test_connection_body() { + fn omits_extra_body_for_kimi_test_connection_body() { let config = AiConfig { provider: AiProvider::OpenaiCompatible, api_key: "key".to_string(), @@ -3245,14 +3146,12 @@ mod tests { "stream": true, }); - add_temperature_if_supported_for_config(&mut body, &config, Some(0.0)); if !config.enable_thinking && !is_kimi_model(&config.model) { body["extra_body"] = serde_json::json!({ "chat_template_kwargs": { "enable_thinking": false } }); } - assert!(body.get("temperature").is_none()); assert!(body.get("extra_body").is_none()); } diff --git a/crates/dbx-web/src/routes/ai.rs b/crates/dbx-web/src/routes/ai.rs index 4799c2ed6e..4e5ed208c2 100644 --- a/crates/dbx-web/src/routes/ai.rs +++ b/crates/dbx-web/src/routes/ai.rs @@ -273,7 +273,6 @@ pub async fn ai_agent_stream( let req_messages = request.messages; let req_task_contract = request.task_contract; let req_max_tokens = request.max_tokens; - let req_temperature = request.temperature; let is_agent_mode = body.mode == "agent"; let tx2 = tx.clone(); tokio::task::spawn_blocking(move || { @@ -291,7 +290,6 @@ pub async fn ai_agent_stream( }, &cancelled, req_max_tokens, - req_temperature, req_task_contract.as_ref(), is_agent_mode, ) diff --git a/src-tauri/src/commands/ai.rs b/src-tauri/src/commands/ai.rs index 917048dca0..c268191663 100644 --- a/src-tauri/src/commands/ai.rs +++ b/src-tauri/src/commands/ai.rs @@ -120,7 +120,6 @@ pub async fn ai_agent_stream( }, &cancelled, request.max_tokens, - request.temperature, request.task_contract.as_ref(), is_agent_mode, )