Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 13 additions & 31 deletions src-tauri/src/proxy/mappers/claude/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1723,13 +1723,6 @@ fn build_tools(

let mut tool_list = Vec::new();

// [优化] Gemini 2.0+ 及 3.0 系列模型通常支持混合工具调用 (Function Calling + Google Search)
// 只有针对老旧模型或特定受限环境才需要互斥。
let model_lower = mapped_model.to_lowercase();
let supports_mixed_tools = model_lower.contains("gemini-2.0")
|| model_lower.contains("gemini-2.5")
|| model_lower.contains("gemini-3");

if !function_declarations.is_empty() {
let mut func_obj = serde_json::Map::new();
func_obj.insert(
Expand All @@ -1739,21 +1732,13 @@ fn build_tools(
tool_list.push(json!(func_obj));

if has_google_search {
if supports_mixed_tools {
tracing::info!(
"[Claude-Request] Enabling MIXED tool calling for {}: Function Calling + Google Search.",
mapped_model
);
let mut search_obj = serde_json::Map::new();
search_obj.insert("googleSearch".to_string(), json!({}));
tool_list.push(json!(search_obj));
} else {
tracing::info!(
"[Claude-Request] Skipping googleSearch injection for {} due to existing function declarations. \
Older Gemini models may not support mixed tool types.",
mapped_model
);
}
// [FIX] cloudcode-pa (v1internal) rejects googleSearch combined with
// functionDeclarations and ignores tool_config.includeServerSideToolInvocations
// (verified upstream 400). Never mix: drop googleSearch when function tools exist.
tracing::info!(
"[Claude-Request] Skipping googleSearch injection for {}: function declarations present (v1internal cannot mix built-in + function tools).",
mapped_model
);
}
} else if has_google_search {
let mut search_obj = serde_json::Map::new();
Expand Down Expand Up @@ -3005,9 +2990,9 @@ mod tests {
}

#[test]
fn test_mixed_tools_injection_for_gemini_2_0() {
// [场景] 使用 Gemini 2.0 模型,同时提供自定义工具和启用全网搜索
// 期望: 转换后的请求应同时包含 googleSearch functionDeclarations
fn test_no_mixed_tools_for_gemini_2_0() {
// [场景] Gemini 2.0 模型, 同时提供自定义工具和 web search
// 期望: v1internal 无法混合, googleSearch 被丢弃, 仅保留 functionDeclarations
let req = ClaudeRequest {
model: "claude-sonnet-4-6".to_string(), // 映射到 gemini-2.0-flash-exp
messages: vec![Message {
Expand Down Expand Up @@ -3054,13 +3039,10 @@ mod tests {
.any(|t| t.get("functionDeclarations").is_some());

assert!(
has_google_search,
"Gemini 2.0 should support mixed Google Search"
);
assert!(
has_functions,
"Gemini 2.0 should support mixed function declarations"
!has_google_search,
"v1internal cannot mix googleSearch with functionDeclarations; googleSearch must be dropped"
);
assert!(has_functions, "function declarations must be preserved");
}

#[test]
Expand Down
32 changes: 30 additions & 2 deletions src-tauri/src/proxy/mappers/common_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub fn resolve_request_config(
// 检测是否有联网工具定义 (内置功能调用)
let has_networking_tool = detects_networking_tool(tools);
// 检测是否包含非联网工具 (如 MCP 本地工具)
let _has_non_networking = contains_non_networking_tool(tools);
let has_non_networking = contains_non_networking_tool(tools);

// Strip -online suffix from original model if present (to detect networking intent)
let is_online_suffix = original_model.ends_with("-online");
Expand All @@ -101,7 +101,11 @@ pub fn resolve_request_config(
// Determine if we should enable networking
// [FIX] 禁用基于模型的自动联网逻辑,防止图像请求被联网搜索结果覆盖。
// 仅在用户显式请求联网时启用:1) -online 后缀 2) 携带联网工具定义
let enable_networking = is_online_suffix || has_networking_tool;
// [FIX] cloudcode-pa (v1internal) rejects googleSearch mixed with functionDeclarations
// and does NOT honor tool_config.includeServerSideToolInvocations (verified: upstream
// returns HTTP 400 even with the flag set). So when real (non-networking) function tools
// are present, stay in agent mode rather than entering web-search mode and 400-ing.
let enable_networking = (is_online_suffix || has_networking_tool) && !has_non_networking;

// The final model to send upstream should be the MAPPED model,
// but if searching, we MUST ensure the model name is one the backend associates with search.
Expand Down Expand Up @@ -605,6 +609,30 @@ mod tests {
assert!(!config.inject_google_search);
}

#[test]
fn test_networking_disabled_when_function_tools_present() {
// A coding/function tool present alongside a web_search tool: v1internal cannot mix,
// so networking must stay off (agent mode) instead of injecting googleSearch.
let tools = Some(vec![
json!({ "name": "get_weather" }),
json!({ "name": "web_search" }),
]);
let config =
resolve_request_config("gemini-3-flash", "gemini-3-flash", &tools, None, None, None, None);
assert_eq!(config.request_type, "agent");
assert!(!config.inject_google_search);
}

#[test]
fn test_networking_enabled_for_pure_web_search() {
// Only a web_search tool (no other function tools): networking should be enabled.
let tools = Some(vec![json!({ "name": "web_search" })]);
let config =
resolve_request_config("gemini-3-flash", "gemini-3-flash", &tools, None, None, None, None);
assert!(config.inject_google_search);
assert_eq!(config.request_type, "web_search");
}

#[test]
fn test_image_model_excluded() {
let config = resolve_request_config(
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/src/proxy/mappers/gemini/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1193,8 +1193,8 @@ mod tests {

assert!(has_functions, "Should contain functionDeclarations");
assert!(
has_google_search,
"Should contain googleSearch (Gemini 2.0+ supports mixed tools)"
!has_google_search,
"v1internal cannot mix; googleSearch must not be injected when functionDeclarations exist"
);
}
}
4 changes: 2 additions & 2 deletions src-tauri/src/proxy/mappers/openai/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1268,8 +1268,8 @@ mod tests {

assert!(has_functions, "Should contain functionDeclarations");
assert!(
has_google_search,
"Should contain googleSearch (Gemini 2.0+ supports mixed tools)"
!has_google_search,
"function tools present: networking disabled, googleSearch must not be injected"
);
}
}