fix(gemini): resolve 400 errors by strictly adhering to JSON schema
Some checks failed
CI / Check (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Formatting (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Release Build (push) Has been cancelled

- Remove  from  as it is rejected by the API inside .
- Ensure  is always a JSON object (google.protobuf.Struct), wrapping non-object tool results in .
- Update extraction logic to only look for  in  sibling fields.
This commit is contained in:
2026-03-05 17:39:50 +00:00
parent 811885274b
commit febfcafed4

View File

@@ -74,8 +74,6 @@ struct GeminiInlineData {
struct GeminiFunctionCall {
name: String,
args: Value,
#[serde(skip_serializing_if = "Option::is_none", rename = "thought_signature")]
thought_signature: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -264,8 +262,15 @@ impl GeminiProvider {
.unwrap_or_default();
let name = msg.name.clone().or_else(|| msg.tool_call_id.clone()).unwrap_or_else(|| "unknown_function".to_string());
let response_value = serde_json::from_str::<Value>(&text_content)
// Gemini API requires 'response' to be a JSON object (google.protobuf.Struct).
// If it is an array or primitive, wrap it in an object.
let mut response_value = serde_json::from_str::<Value>(&text_content)
.unwrap_or_else(|_| serde_json::json!({ "result": text_content }));
if !response_value.is_object() {
response_value = serde_json::json!({ "result": response_value });
}
parts.push(GeminiPart {
text: None,
@@ -328,7 +333,6 @@ impl GeminiProvider {
function_call: Some(GeminiFunctionCall {
name: tc.function.name.clone(),
args,
thought_signature: thought_signature.clone(),
}),
function_response: None,
thought: None,
@@ -537,10 +541,9 @@ impl GeminiProvider {
.filter(|p| p.function_call.is_some())
.map(|p| {
let fc = p.function_call.as_ref().unwrap();
// CAPTURE: Try extracting thought_signature from multiple possible locations
// CAPTURE: Try extracting thought_signature from sibling fields
let id = p.thought_signature_camel.clone()
.or_else(|| p.thought_signature_snake.clone())
.or_else(|| fc.thought_signature.clone())
.unwrap_or_else(|| format!("call_{}", Uuid::new_v4().simple()));
ToolCall {
@@ -886,10 +889,9 @@ impl super::Provider for GeminiProvider {
if let Some(fc) = &p.function_call {
let tool_call_idx = p_idx as u32;
// Attempt to find a signature in any possible field
// Attempt to find a signature in sibling fields
let signature = p.thought_signature_camel.clone()
.or_else(|| p.thought_signature_snake.clone())
.or_else(|| fc.thought_signature.clone());
.or_else(|| p.thought_signature_snake.clone());
// Ensure the ID remains stable for this tool call index.
// If we found a real signature now, we update it; otherwise use the existing or new random ID.