mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-10 03:30:59 +00:00
Preserve Gemini thought_signature on function calls
Gemini 3 models include a thoughtSignature field on function call parts that must be returned in subsequent conversation turns. Add provider_metadata to ToolCall to carry this through, and extract/emit it in both the non-streaming and streaming Gemini code paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7dddc49121
commit
3b1411a3ac
2 changed files with 34 additions and 11 deletions
|
|
@ -189,11 +189,12 @@ fn parse_part(part: &serde_json::Value) -> Option<ContentPart> {
|
|||
.get("args")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| serde_json::Value::Object(serde_json::Map::new()));
|
||||
return Some(ContentPart::ToolCall(ToolCall::new(
|
||||
uuid::Uuid::new_v4().to_string(),
|
||||
name,
|
||||
args,
|
||||
)));
|
||||
let mut tc = ToolCall::new(uuid::Uuid::new_v4().to_string(), name, args);
|
||||
// Preserve thought_signature for Gemini 3 models (sibling of functionCall in the part)
|
||||
if let Some(sig) = part.get("thoughtSignature") {
|
||||
tc.provider_metadata = Some(serde_json::json!({"thoughtSignature": sig}));
|
||||
}
|
||||
return Some(ContentPart::ToolCall(tc));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
@ -240,12 +241,23 @@ fn translate_messages(messages: &[&Message]) -> Vec<Content> {
|
|||
.iter()
|
||||
.filter_map(|part| match part {
|
||||
ContentPart::Text(text) => Some(serde_json::json!({"text": text})),
|
||||
ContentPart::ToolCall(tc) => Some(serde_json::json!({
|
||||
"functionCall": {
|
||||
"name": tc.name,
|
||||
"args": tc.arguments,
|
||||
ContentPart::ToolCall(tc) => {
|
||||
let mut part_json = serde_json::json!({
|
||||
"functionCall": {
|
||||
"name": tc.name,
|
||||
"args": tc.arguments,
|
||||
}
|
||||
});
|
||||
// Re-attach thought_signature as sibling of functionCall
|
||||
if let Some(sig) = tc
|
||||
.provider_metadata
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("thoughtSignature"))
|
||||
{
|
||||
part_json["thoughtSignature"] = sig.clone();
|
||||
}
|
||||
})),
|
||||
Some(part_json)
|
||||
}
|
||||
ContentPart::Image(img) => {
|
||||
img.url.as_ref().map_or_else(
|
||||
|| {
|
||||
|
|
@ -804,7 +816,12 @@ impl SseStreamState {
|
|||
.get("args")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| serde_json::Value::Object(serde_json::Map::new()));
|
||||
let tool_call = ToolCall::new(uuid::Uuid::new_v4().to_string(), name, args);
|
||||
let mut tool_call = ToolCall::new(uuid::Uuid::new_v4().to_string(), name, args);
|
||||
// Preserve thought_signature for Gemini 3 models (sibling of functionCall)
|
||||
if let Some(sig) = part.get("thoughtSignature") {
|
||||
tool_call.provider_metadata =
|
||||
Some(serde_json::json!({"thoughtSignature": sig}));
|
||||
}
|
||||
|
||||
// Gemini delivers function calls as complete objects in a single chunk.
|
||||
self.pending_events
|
||||
|
|
|
|||
|
|
@ -55,6 +55,11 @@ pub struct ToolCall {
|
|||
pub name: String,
|
||||
pub arguments: serde_json::Value,
|
||||
pub raw_arguments: Option<String>,
|
||||
/// Opaque provider-specific metadata (e.g. Gemini thought_signature).
|
||||
/// Preserved across round-trips so the provider can include it when
|
||||
/// sending conversation history back to the API.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub provider_metadata: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl ToolCall {
|
||||
|
|
@ -68,6 +73,7 @@ impl ToolCall {
|
|||
name: name.into(),
|
||||
arguments,
|
||||
raw_arguments: None,
|
||||
provider_metadata: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue