diff --git a/src/providers/gemini.rs b/src/providers/gemini.rs index 54e48c94..1b123b8d 100644 --- a/src/providers/gemini.rs +++ b/src/providers/gemini.rs @@ -367,10 +367,18 @@ impl GeminiProvider { request.tools.as_ref().map(|tools| { let declarations: Vec = tools .iter() - .map(|t| GeminiFunctionDeclaration { - name: t.function.name.clone(), - description: t.function.description.clone(), - parameters: t.function.parameters.clone(), + .map(|t| { + let mut parameters = t.function.parameters.clone().unwrap_or(serde_json::json!({ + "type": "object", + "properties": {} + })); + Self::sanitize_schema(&mut parameters); + + GeminiFunctionDeclaration { + name: t.function.name.clone(), + description: t.function.description.clone(), + parameters: Some(parameters), + } }) .collect(); vec![GeminiTool { @@ -379,6 +387,55 @@ impl GeminiProvider { }) } + /// Recursively remove unsupported JSON Schema fields that Gemini's API rejects. + fn sanitize_schema(value: &mut Value) { + if let Value::Object(map) = value { + // Remove unsupported fields at this level + map.remove("$schema"); + map.remove("additionalProperties"); + map.remove("exclusiveMaximum"); + map.remove("exclusiveMinimum"); + + // Recursively sanitize all object properties + if let Some(properties) = map.get_mut("properties") { + if let Value::Object(props_map) = properties { + for prop_value in props_map.values_mut() { + Self::sanitize_schema(prop_value); + } + } + } + + // Recursively sanitize array items + if let Some(items) = map.get_mut("items") { + Self::sanitize_schema(items); + } + + // Gemini 1.5/2.0+ supports anyOf in some contexts, but it's often + // the source of additionalProperties errors when nested. + if let Some(any_of) = map.get_mut("anyOf") { + if let Value::Array(arr) = any_of { + for item in arr { + Self::sanitize_schema(item); + } + } + } + if let Some(one_of) = map.get_mut("oneOf") { + if let Value::Array(arr) = one_of { + for item in arr { + Self::sanitize_schema(item); + } + } + } + if let Some(all_of) = map.get_mut("allOf") { + if let Value::Array(arr) = all_of { + for item in arr { + Self::sanitize_schema(item); + } + } + } + } + } + /// Convert OpenAI tool_choice to Gemini tool_config. fn convert_tool_config(request: &UnifiedRequest) -> Option { request.tool_choice.as_ref().map(|tc| {