| |
| |
|
|
| use serde_json::{json, Value}; |
|
|
| |
| #[derive(Debug, Clone)] |
| pub struct RequestConfig { |
| |
| pub request_type: String, |
| |
| pub inject_google_search: bool, |
| |
| pub final_model: String, |
| |
| pub image_config: Option<Value>, |
| } |
|
|
| pub fn resolve_request_config( |
| original_model: &str, |
| mapped_model: &str, |
| tools: &Option<Vec<Value>> |
| ) -> RequestConfig { |
| |
| if mapped_model.starts_with("gemini-3-pro-image") { |
| let (image_config, parsed_base_model) = parse_image_config(original_model); |
| |
| return RequestConfig { |
| request_type: "image_gen".to_string(), |
| inject_google_search: false, |
| final_model: parsed_base_model, |
| image_config: Some(image_config), |
| }; |
| } |
|
|
| |
| let has_networking_tool = detects_networking_tool(tools); |
| |
| let has_non_networking = contains_non_networking_tool(tools); |
|
|
| |
| let is_online_suffix = original_model.ends_with("-online"); |
| |
| |
| let is_high_quality_model = mapped_model == "gemini-2.5-flash" |
| || mapped_model == "gemini-1.5-pro" |
| || mapped_model.starts_with("gemini-1.5-pro-") |
| || mapped_model.starts_with("gemini-2.5-flash-") |
| || mapped_model.starts_with("gemini-2.0-flash") |
| || mapped_model.starts_with("gemini-3-") |
| || mapped_model.contains("claude-3-5-sonnet") |
| || mapped_model.contains("claude-3-opus") |
| || mapped_model.contains("claude-sonnet") |
| || mapped_model.contains("claude-opus") |
| || mapped_model.contains("claude-4"); |
|
|
| |
| |
| |
| let enable_networking = is_online_suffix || has_networking_tool; |
|
|
| |
| |
| |
| let mut final_model = mapped_model.trim_end_matches("-online").to_string(); |
| if enable_networking { |
| |
| |
| if final_model.contains("thinking") || !final_model.starts_with("gemini-") { |
| final_model = "gemini-2.5-flash".to_string(); |
| } |
| } |
|
|
| RequestConfig { |
| request_type: if enable_networking { |
| "web_search".to_string() |
| } else { |
| "agent".to_string() |
| }, |
| inject_google_search: enable_networking, |
| final_model, |
| image_config: None, |
| } |
| } |
|
|
| |
| |
| fn parse_image_config(model_name: &str) -> (Value, String) { |
| let mut aspect_ratio = "1:1"; |
| let _image_size = "1024x1024"; |
|
|
| if model_name.contains("-16x9") { aspect_ratio = "16:9"; } |
| else if model_name.contains("-9x16") { aspect_ratio = "9:16"; } |
| else if model_name.contains("-4x3") { aspect_ratio = "4:3"; } |
| else if model_name.contains("-3x4") { aspect_ratio = "3:4"; } |
| else if model_name.contains("-1x1") { aspect_ratio = "1:1"; } |
|
|
| let is_hd = model_name.contains("-4k") || model_name.contains("-hd"); |
|
|
| let mut config = serde_json::Map::new(); |
| config.insert("aspectRatio".to_string(), json!(aspect_ratio)); |
| |
| if is_hd { |
| config.insert("imageSize".to_string(), json!("4K")); |
| } |
|
|
| |
| (serde_json::Value::Object(config), "gemini-3-pro-image".to_string()) |
| } |
|
|
| |
| pub fn inject_google_search_tool(body: &mut Value) { |
| if let Some(obj) = body.as_object_mut() { |
| let tools_entry = obj.entry("tools").or_insert_with(|| json!([])); |
| if let Some(tools_arr) = tools_entry.as_array_mut() { |
| |
| |
| let has_functions = tools_arr.iter().any(|t| { |
| t.as_object().map_or(false, |o| o.contains_key("functionDeclarations")) |
| }); |
|
|
| if has_functions { |
| tracing::info!("Skipping googleSearch injection due to existing functionDeclarations"); |
| return; |
| } |
|
|
| |
| tools_arr.retain(|t| { |
| if let Some(o) = t.as_object() { |
| !(o.contains_key("googleSearch") || o.contains_key("googleSearchRetrieval")) |
| } else { |
| true |
| } |
| }); |
|
|
| |
| tools_arr.push(json!({ |
| "googleSearch": {} |
| })); |
| } |
| } |
| } |
|
|
| |
| pub fn deep_clean_undefined(value: &mut Value) { |
| match value { |
| Value::Object(map) => { |
| |
| map.retain(|_, v| { |
| if let Some(s) = v.as_str() { |
| s != "[undefined]" |
| } else { |
| true |
| } |
| }); |
| |
| for v in map.values_mut() { |
| deep_clean_undefined(v); |
| } |
| } |
| Value::Array(arr) => { |
| for v in arr.iter_mut() { |
| deep_clean_undefined(v); |
| } |
| } |
| _ => {} |
| } |
| } |
|
|
| |
| |
| pub fn detects_networking_tool(tools: &Option<Vec<Value>>) -> bool { |
| if let Some(list) = tools { |
| for tool in list { |
| |
| if let Some(n) = tool.get("name").and_then(|v| v.as_str()) { |
| if n == "web_search" || n == "google_search" || n == "web_search_20250305" || n == "google_search_retrieval" { |
| return true; |
| } |
| } |
|
|
| if let Some(t) = tool.get("type").and_then(|v| v.as_str()) { |
| if t == "web_search_20250305" || t == "google_search" || t == "web_search" || t == "google_search_retrieval" { |
| return true; |
| } |
| } |
|
|
| |
| if let Some(func) = tool.get("function") { |
| if let Some(n) = func.get("name").and_then(|v| v.as_str()) { |
| let keywords = ["web_search", "google_search", "web_search_20250305", "google_search_retrieval"]; |
| if keywords.contains(&n) { |
| return true; |
| } |
| } |
| } |
|
|
| |
| if let Some(decls) = tool.get("functionDeclarations").and_then(|v| v.as_array()) { |
| for decl in decls { |
| if let Some(n) = decl.get("name").and_then(|v| v.as_str()) { |
| if n == "web_search" || n == "google_search" || n == "google_search_retrieval" { |
| return true; |
| } |
| } |
| } |
| } |
|
|
| |
| if tool.get("googleSearch").is_some() || tool.get("googleSearchRetrieval").is_some() { |
| return true; |
| } |
| } |
| } |
| false |
| } |
|
|
| |
| pub fn contains_non_networking_tool(tools: &Option<Vec<Value>>) -> bool { |
| if let Some(list) = tools { |
| for tool in list { |
| let mut is_networking = false; |
| |
| |
| if let Some(n) = tool.get("name").and_then(|v| v.as_str()) { |
| let keywords = ["web_search", "google_search", "web_search_20250305", "google_search_retrieval"]; |
| if keywords.contains(&n) { is_networking = true; } |
| } else if let Some(func) = tool.get("function") { |
| if let Some(n) = func.get("name").and_then(|v| v.as_str()) { |
| let keywords = ["web_search", "google_search", "web_search_20250305", "google_search_retrieval"]; |
| if keywords.contains(&n) { is_networking = true; } |
| } |
| } else if tool.get("googleSearch").is_some() || tool.get("googleSearchRetrieval").is_some() { |
| is_networking = true; |
| } else if tool.get("functionDeclarations").is_some() { |
| |
| if let Some(decls) = tool.get("functionDeclarations").and_then(|v| v.as_array()) { |
| for decl in decls { |
| if let Some(n) = decl.get("name").and_then(|v| v.as_str()) { |
| let keywords = ["web_search", "google_search", "google_search_retrieval"]; |
| if !keywords.contains(&n) { |
| return true; |
| } |
| } |
| } |
| } |
| is_networking = true; |
| } |
|
|
| if !is_networking { |
| return true; |
| } |
| } |
| } |
| false |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn test_high_quality_model_auto_grounding() { |
| let config = resolve_request_config("gpt-4o", "gemini-2.5-flash", &None); |
| assert_eq!(config.request_type, "web_search"); |
| assert!(config.inject_google_search); |
| assert_eq!(config.final_model, "gemini-2.5-flash"); |
| } |
|
|
| #[test] |
| fn test_gemini_native_tool_detection() { |
| let tools = Some(vec![json!({ |
| "functionDeclarations": [ |
| { "name": "web_search", "parameters": {} } |
| ] |
| })]); |
| assert!(detects_networking_tool(&tools)); |
| } |
|
|
| #[test] |
| fn test_online_suffix_force_grounding() { |
| let config = resolve_request_config("gemini-3-flash-online", "gemini-3-flash", &None); |
| assert_eq!(config.request_type, "web_search"); |
| assert!(config.inject_google_search); |
| assert_eq!(config.final_model, "gemini-3-flash"); |
| } |
|
|
| #[test] |
| fn test_default_no_grounding() { |
| let config = resolve_request_config("claude-sonnet", "gemini-3-flash", &None); |
| assert_eq!(config.request_type, "agent"); |
| assert!(!config.inject_google_search); |
| } |
|
|
| #[test] |
| fn test_image_model_excluded() { |
| let config = resolve_request_config("gemini-3-pro-image", "gemini-3-pro-image", &None); |
| assert_eq!(config.request_type, "image_gen"); |
| assert!(!config.inject_google_search); |
| } |
| } |
|
|