Skip to content

Commit 82e9607

Browse files
fix(google): add comprehensive schema sanitization for Gemini API compatibility (#2908)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent a55e2a8 commit 82e9607

2 files changed

Lines changed: 1365 additions & 10 deletions

File tree

crates/forge_app/src/dto/google/request.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,9 @@ impl From<Context> for Request {
387387
}),
388388
response_schema: context.response_format.and_then(|rf| match rf {
389389
forge_domain::ResponseFormat::JsonSchema(schema) => {
390-
// Convert schema to JSON and strip $schema field (Google API doesn't accept it)
391390
let mut schema_value = serde_json::to_value(*schema).ok()?;
392-
if let Some(obj) = schema_value.as_object_mut() {
393-
obj.remove("$schema");
394-
}
391+
// Sanitize schema for Gemini API compatibility
392+
crate::utils::sanitize_gemini_schema(&mut schema_value);
395393
Some(schema_value)
396394
}
397395
_ => None,
@@ -452,14 +450,13 @@ impl From<forge_domain::ToolChoice> for ToolConfig {
452450

453451
impl From<forge_domain::ToolDefinition> for FunctionDeclaration {
454452
fn from(tool: forge_domain::ToolDefinition) -> Self {
455-
// Convert input_schema to JSON value and strip $schema field
456453
let mut parameters =
457454
serde_json::to_value(tool.input_schema).unwrap_or(serde_json::json!({}));
458455

459-
// Remove $schema field if present (Google API doesn't accept it)
460-
if let Some(obj) = parameters.as_object_mut() {
461-
obj.remove("$schema");
462-
}
456+
// Sanitize schema for Gemini API compatibility (strips $schema,
457+
// removes additionalProperties, converts integer enums, ensures
458+
// arrays have items, removes properties from non-objects, etc.)
459+
crate::utils::sanitize_gemini_schema(&mut parameters);
463460

464461
FunctionDeclaration {
465462
name: tool.name.to_string(),
@@ -830,11 +827,18 @@ mod tests {
830827
assert_eq!(decl.name, "test_tool");
831828
assert_eq!(decl.description.unwrap(), "A test tool");
832829

833-
// Check schema stripping
830+
// Check Gemini schema sanitization
834831
let params = decl.parameters;
835832
assert!(params.is_object());
836833
assert!(!params.as_object().unwrap().contains_key("$schema"));
837834
assert!(params.as_object().unwrap().contains_key("type"));
835+
// additionalProperties should be removed by Gemini sanitization
836+
assert!(
837+
!params
838+
.as_object()
839+
.unwrap()
840+
.contains_key("additionalProperties")
841+
);
838842
}
839843

840844
#[test]
@@ -934,6 +938,12 @@ mod tests {
934938
|| obj.contains_key("title"),
935939
"Schema should still contain other properties"
936940
);
941+
942+
// Verify additionalProperties is also removed by Gemini sanitization
943+
assert!(
944+
!obj.contains_key("additionalProperties"),
945+
"additionalProperties should be removed by Gemini sanitization"
946+
);
937947
} else {
938948
panic!("response_schema should be an object");
939949
}

0 commit comments

Comments
 (0)