fix(ai): disable Ollama thinking with reasoning effort

This commit is contained in:
zipg 2026-07-10 23:05:08 +08:00 committed by GitHub
parent a54bcccbd5
commit cb5ed9baf8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 90 additions and 25 deletions

View File

@ -525,6 +525,22 @@ fn is_kimi_model(model: &str) -> bool {
}
}
fn apply_chat_completion_thinking_toggle(body: &mut serde_json::Value, config: &AiConfig) {
if config.enable_thinking {
return;
}
if matches!(config.provider, AiProvider::Ollama) {
// Ollama's OpenAI-compatible API uses reasoning_effort instead of
// forwarding provider-specific chat template arguments.
body["reasoning_effort"] = json!("none");
} else if !is_kimi_model(&config.model) {
body["extra_body"] = json!({
"chat_template_kwargs": { "enable_thinking": false }
});
}
}
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();
@ -904,11 +920,7 @@ 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));
if !request.config.enable_thinking && !is_kimi_model(&request.config.model) {
body_obj["extra_body"] = json!({
"chat_template_kwargs": { "enable_thinking": false }
});
}
apply_chat_completion_thinking_toggle(&mut body_obj, &request.config);
let res = client
.post(resolve_endpoint(&request.config))
@ -1162,10 +1174,8 @@ pub async fn test_connection_core(config: &AiConfig) -> Result<AiTestConnectionR
set_chat_completion_token_limit(&mut body, config, 16);
body
};
if config.api_style != AiApiStyle::Responses && !config.enable_thinking && !is_kimi_model(&config.model) {
body_obj["extra_body"] = json!({
"chat_template_kwargs": { "enable_thinking": false }
});
if config.api_style != AiApiStyle::Responses {
apply_chat_completion_thinking_toggle(&mut body_obj, config);
}
let ep = resolve_endpoint(config);
let res = client
@ -1417,11 +1427,7 @@ async fn stream_openai(
"stream": true,
});
set_chat_completion_token_limit(&mut body_obj, &request.config, request.max_tokens.unwrap_or(2048));
if !request.config.enable_thinking && !is_kimi_model(&request.config.model) {
body_obj["extra_body"] = json!({
"chat_template_kwargs": { "enable_thinking": false }
});
}
apply_chat_completion_thinking_toggle(&mut body_obj, &request.config);
let res = client
.post(resolve_endpoint(&request.config))
@ -1976,9 +1982,13 @@ async fn stream_openai_with_tools(
});
set_chat_completion_token_limit(&mut body, &request.config, request.max_tokens.unwrap_or(4096));
// DeepSeek API requires {"thinking": {"type": "disabled"}} to disable thinking
if !request.config.enable_thinking && matches!(request.config.provider, AiProvider::Deepseek) {
body["thinking"] = json!({ "type": "disabled" });
if !request.config.enable_thinking {
if matches!(request.config.provider, AiProvider::Ollama) {
apply_chat_completion_thinking_toggle(&mut body, &request.config);
} else if matches!(request.config.provider, AiProvider::Deepseek) {
// DeepSeek uses its own thinking field for tool-enabled requests.
body["thinking"] = json!({ "type": "disabled" });
}
}
let res = client
@ -2466,9 +2476,9 @@ mod tests {
use std::collections::{HashMap, HashSet};
use super::{
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,
apply_chat_completion_thinking_toggle, 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,
@ -3169,13 +3179,68 @@ mod tests {
"stream": true,
});
if !config.enable_thinking && !is_kimi_model(&config.model) {
body["extra_body"] = serde_json::json!({
"chat_template_kwargs": { "enable_thinking": false }
});
}
apply_chat_completion_thinking_toggle(&mut body, &config);
assert!(body.get("extra_body").is_none());
assert!(body.get("reasoning_effort").is_none());
}
#[test]
fn keeps_extra_body_thinking_toggle_for_other_compatible_providers() {
let config = AiConfig {
provider: AiProvider::OpenaiCompatible,
api_key: "key".to_string(),
auth_method: AiAuthMethod::Bearer,
endpoint: "https://example.com/v1".to_string(),
model: "qwen3".to_string(),
api_style: AiApiStyle::Completions,
proxy_enabled: false,
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
context_window: None,
codex_cli_path: None,
codex_cli_env: Default::default(),
};
let mut body = serde_json::json!({ "model": &config.model });
apply_chat_completion_thinking_toggle(&mut body, &config);
assert_eq!(
body.get("extra_body"),
Some(&serde_json::json!({
"chat_template_kwargs": { "enable_thinking": false }
}))
);
assert!(body.get("reasoning_effort").is_none());
}
#[test]
fn uses_reasoning_effort_to_disable_ollama_thinking() {
let config = AiConfig {
provider: AiProvider::Ollama,
api_key: String::new(),
auth_method: AiAuthMethod::Bearer,
endpoint: "http://localhost:11434/v1".to_string(),
model: "deepseek-r1:14b".to_string(),
api_style: AiApiStyle::Completions,
proxy_enabled: false,
proxy_url: String::new(),
enable_thinking: false,
reasoning_level: AiReasoningLevel::Default,
context_window: None,
codex_cli_path: None,
codex_cli_env: Default::default(),
};
let mut body = serde_json::json!({
"model": &config.model,
"messages": [{ "role": "user", "content": TEST_PROMPT }],
});
apply_chat_completion_thinking_toggle(&mut body, &config);
assert_eq!(body.get("reasoning_effort"), Some(&serde_json::json!("none")));
assert!(body.get("extra_body").is_none());
}
#[test]