fix(gemini): align thought_signature logic with Gemini API requirements
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

- Extract thought_signature from the Part level during response parsing.
- Provide thought_signature as a sibling to functionCall during request assembly.
- This fully resolves the 'Unknown name thoughtSignature at function_call' error.
This commit is contained in:
2026-03-05 16:45:18 +00:00
parent 154b7b3b77
commit 8a33b147f1

View File

@@ -57,6 +57,8 @@ struct GeminiPart {
function_response: Option<GeminiFunctionResponse>, function_response: Option<GeminiFunctionResponse>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
thought: Option<String>, thought: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
thought_signature: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -70,8 +72,6 @@ struct GeminiInlineData {
struct GeminiFunctionCall { struct GeminiFunctionCall {
name: String, name: String,
args: Value, args: Value,
#[serde(skip_serializing_if = "Option::is_none")]
thought_signature: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -231,6 +231,7 @@ impl GeminiProvider {
function_call: None, function_call: None,
function_response: None, function_response: None,
thought: None, thought: None,
thought_signature: None,
}); });
} }
} }
@@ -270,6 +271,7 @@ impl GeminiProvider {
response: response_value, response: response_value,
}), }),
thought: None, thought: None,
thought_signature: None,
}); });
} else if msg.role == "assistant" { } else if msg.role == "assistant" {
// Assistant messages: handle text, thought (reasoning), and tool_calls // Assistant messages: handle text, thought (reasoning), and tool_calls
@@ -282,6 +284,7 @@ impl GeminiProvider {
function_call: None, function_call: None,
function_response: None, function_response: None,
thought: None, thought: None,
thought_signature: None,
}); });
} }
} }
@@ -296,6 +299,7 @@ impl GeminiProvider {
function_call: None, function_call: None,
function_response: None, function_response: None,
thought: Some(reasoning.clone()), thought: Some(reasoning.clone()),
thought_signature: None,
}); });
} }
} }
@@ -318,10 +322,10 @@ impl GeminiProvider {
function_call: Some(GeminiFunctionCall { function_call: Some(GeminiFunctionCall {
name: tc.function.name.clone(), name: tc.function.name.clone(),
args, args,
thought_signature,
}), }),
function_response: None, function_response: None,
thought: None, thought: None,
thought_signature,
}); });
} }
} }
@@ -337,6 +341,7 @@ impl GeminiProvider {
function_call: None, function_call: None,
function_response: None, function_response: None,
thought: None, thought: None,
thought_signature: None,
}); });
} }
} }
@@ -355,6 +360,7 @@ impl GeminiProvider {
function_call: None, function_call: None,
function_response: None, function_response: None,
thought: None, thought: None,
thought_signature: None,
}); });
} }
} }
@@ -390,6 +396,7 @@ impl GeminiProvider {
function_call: None, function_call: None,
function_response: None, function_response: None,
thought: None, thought: None,
thought_signature: None,
}], }],
}); });
} }
@@ -516,10 +523,11 @@ impl GeminiProvider {
fn extract_tool_calls(parts: &[GeminiPart]) -> Option<Vec<ToolCall>> { fn extract_tool_calls(parts: &[GeminiPart]) -> Option<Vec<ToolCall>> {
let calls: Vec<ToolCall> = parts let calls: Vec<ToolCall> = parts
.iter() .iter()
.filter_map(|p| p.function_call.as_ref()) .filter(|p| p.function_call.is_some())
.map(|fc| { .map(|p| {
// CAPTURE: Use thought_signature as the ID if available let fc = p.function_call.as_ref().unwrap();
let id = fc.thought_signature.clone().unwrap_or_else(|| format!("call_{}", Uuid::new_v4().simple())); // CAPTURE: Use thought_signature from Part as the ID if available
let id = p.thought_signature.clone().unwrap_or_else(|| format!("call_{}", Uuid::new_v4().simple()));
ToolCall { ToolCall {
id, id,
@@ -539,11 +547,12 @@ impl GeminiProvider {
fn extract_tool_call_deltas(parts: &[GeminiPart]) -> Option<Vec<ToolCallDelta>> { fn extract_tool_call_deltas(parts: &[GeminiPart]) -> Option<Vec<ToolCallDelta>> {
let deltas: Vec<ToolCallDelta> = parts let deltas: Vec<ToolCallDelta> = parts
.iter() .iter()
.filter_map(|p| p.function_call.as_ref())
.enumerate() .enumerate()
.map(|(i, fc)| { .filter(|(_, p)| p.function_call.is_some())
// CAPTURE: Use thought_signature as the ID if available .map(|(i, p)| {
let id = fc.thought_signature.clone().unwrap_or_else(|| format!("call_{}", Uuid::new_v4().simple())); let fc = p.function_call.as_ref().unwrap();
// CAPTURE: Use thought_signature from Part as the ID if available
let id = p.thought_signature.clone().unwrap_or_else(|| format!("call_{}", Uuid::new_v4().simple()));
ToolCallDelta { ToolCallDelta {
index: i as u32, index: i as u32,