fix(gemini): sanitize tool parameters to remove unsupported JSON Schema fields
- Recursively remove '$schema', 'additionalProperties', 'exclusiveMaximum', and 'exclusiveMinimum' from tool definitions. - These fields are frequently included by clients like opencode but are rejected by the Gemini API with 400 errors.
This commit is contained in:
@@ -367,10 +367,18 @@ impl GeminiProvider {
|
||||
request.tools.as_ref().map(|tools| {
|
||||
let declarations: Vec<GeminiFunctionDeclaration> = 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<GeminiToolConfig> {
|
||||
request.tool_choice.as_ref().map(|tc| {
|
||||
|
||||
Reference in New Issue
Block a user