fix(ai): disable Ollama thinking with reasoning effort
This commit is contained in:
1 file changed
+90
-25
+90
-25
@@ -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 {
|
fn responses_text(data: &serde_json::Value) -> String {
|
||||||
if let Some(text) = data["output_text"].as_str().filter(|text| !text.is_empty()) {
|
if let Some(text) = data["output_text"].as_str().filter(|text| !text.is_empty()) {
|
||||||
return text.to_string();
|
return text.to_string();
|
||||||
@@ -904,11 +920,7 @@ pub async fn call_openai_compatible(client: &reqwest::Client, request: AiComplet
|
|||||||
"messages": messages,
|
"messages": messages,
|
||||||
});
|
});
|
||||||
set_chat_completion_token_limit(&mut body_obj, &request.config, request.max_tokens.unwrap_or(2048));
|
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) {
|
apply_chat_completion_thinking_toggle(&mut body_obj, &request.config);
|
||||||
body_obj["extra_body"] = json!({
|
|
||||||
"chat_template_kwargs": { "enable_thinking": false }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = client
|
let res = client
|
||||||
.post(resolve_endpoint(&request.config))
|
.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);
|
set_chat_completion_token_limit(&mut body, config, 16);
|
||||||
body
|
body
|
||||||
};
|
};
|
||||||
if config.api_style != AiApiStyle::Responses && !config.enable_thinking && !is_kimi_model(&config.model) {
|
if config.api_style != AiApiStyle::Responses {
|
||||||
body_obj["extra_body"] = json!({
|
apply_chat_completion_thinking_toggle(&mut body_obj, config);
|
||||||
"chat_template_kwargs": { "enable_thinking": false }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
let ep = resolve_endpoint(config);
|
let ep = resolve_endpoint(config);
|
||||||
let res = client
|
let res = client
|
||||||
@@ -1417,11 +1427,7 @@ async fn stream_openai(
|
|||||||
"stream": true,
|
"stream": true,
|
||||||
});
|
});
|
||||||
set_chat_completion_token_limit(&mut body_obj, &request.config, request.max_tokens.unwrap_or(2048));
|
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) {
|
apply_chat_completion_thinking_toggle(&mut body_obj, &request.config);
|
||||||
body_obj["extra_body"] = json!({
|
|
||||||
"chat_template_kwargs": { "enable_thinking": false }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = client
|
let res = client
|
||||||
.post(resolve_endpoint(&request.config))
|
.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));
|
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 {
|
||||||
if !request.config.enable_thinking && matches!(request.config.provider, AiProvider::Deepseek) {
|
if matches!(request.config.provider, AiProvider::Ollama) {
|
||||||
body["thinking"] = json!({ "type": "disabled" });
|
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
|
let res = client
|
||||||
@@ -2466,9 +2476,9 @@ mod tests {
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
build_ai_http_client, build_responses_input_with_tools, claude_headers, claude_system_prompt,
|
apply_chat_completion_thinking_toggle, build_ai_http_client, build_responses_input_with_tools, claude_headers,
|
||||||
drain_next_stream_line, emit_responses_function_call_item, gemini_text, is_kimi_model, openai_response_text,
|
claude_system_prompt, drain_next_stream_line, emit_responses_function_call_item, gemini_text, is_kimi_model,
|
||||||
openai_stream_reasoning, openai_stream_text, parse_model_list_response, resolve_endpoint,
|
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,
|
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,
|
responses_text, responses_token_usage, set_chat_completion_token_limit, stream_data_payload,
|
||||||
uses_anthropic_messages_api, validate_config, AiApiStyle, AiAuthMethod, AiConfig, AiMessage, AiModelInfo,
|
uses_anthropic_messages_api, validate_config, AiApiStyle, AiAuthMethod, AiConfig, AiMessage, AiModelInfo,
|
||||||
@@ -3169,13 +3179,68 @@ mod tests {
|
|||||||
"stream": true,
|
"stream": true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if !config.enable_thinking && !is_kimi_model(&config.model) {
|
apply_chat_completion_thinking_toggle(&mut body, &config);
|
||||||
body["extra_body"] = serde_json::json!({
|
|
||||||
"chat_template_kwargs": { "enable_thinking": false }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(body.get("extra_body").is_none());
|
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]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user