fix: support response parts with extra fields like thoughtSignature
Some checks failed
Rust / build (push) Has been cancelled
Some checks failed
Rust / build (push) Has been cancelled
Implement custom Deserialize for Part to tolerate additional response fields (such as thoughtSignature or thought) returned by thinking models.
This commit is contained in:
@@ -83,7 +83,7 @@ impl FromStr for Role {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Part {
|
||||
Text(String),
|
||||
@@ -100,3 +100,66 @@ pub enum Part {
|
||||
args: HashMap<String, String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PartHelper {
|
||||
text: Option<String>,
|
||||
inline_data: Option<InlineDataHelper>,
|
||||
file_data: Option<FileDataHelper>,
|
||||
function_call: Option<FunctionCallHelper>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct InlineDataHelper {
|
||||
mime_type: String,
|
||||
data: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FileDataHelper {
|
||||
mime_type: String,
|
||||
file_uri: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FunctionCallHelper {
|
||||
name: String,
|
||||
args: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Part {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let helper = PartHelper::deserialize(deserializer)?;
|
||||
if let Some(text) = helper.text {
|
||||
return Ok(Part::Text(text));
|
||||
}
|
||||
if let Some(inline_data) = helper.inline_data {
|
||||
return Ok(Part::InlineData {
|
||||
mime_type: inline_data.mime_type,
|
||||
data: inline_data.data,
|
||||
});
|
||||
}
|
||||
if let Some(file_data) = helper.file_data {
|
||||
return Ok(Part::FileData {
|
||||
mime_type: file_data.mime_type,
|
||||
file_uri: file_data.file_uri,
|
||||
});
|
||||
}
|
||||
if let Some(function_call) = helper.function_call {
|
||||
return Ok(Part::FunctionCall {
|
||||
name: function_call.name,
|
||||
args: function_call.args,
|
||||
});
|
||||
}
|
||||
Err(serde::de::Error::custom(
|
||||
"Part does not contain any recognizable variant",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,4 +531,34 @@ mod tests {
|
||||
}"#;
|
||||
serde_json::from_str::<GenerateContentResponse>(input).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_response_with_thought_signature() {
|
||||
let input = r#"{
|
||||
"candidates": [
|
||||
{
|
||||
"content": {
|
||||
"role": "model",
|
||||
"parts": [
|
||||
{
|
||||
"text": "Hello world",
|
||||
"thoughtSignature": "AY89a1+twFsQ+oYDReN5PU76yp0ciDinNh3MgyCbe/BLvyh93lje7qHOCJuNxaPULnqmgvKvtuQnjyf2wwD20Dl2rWnxbdZZAeqhRJEdvKOc4LQ="
|
||||
}
|
||||
]
|
||||
},
|
||||
"finishReason": "STOP"
|
||||
}
|
||||
],
|
||||
"usageMetadata": {
|
||||
"promptTokenCount": 877,
|
||||
"candidatesTokenCount": 547,
|
||||
"totalTokenCount": 1424,
|
||||
"trafficType": "ON_DEMAND"
|
||||
},
|
||||
"modelVersion": "gemini-3.5-flash-lite",
|
||||
"createTime": "2026-09-06T10:36:16.330983Z",
|
||||
"responseId": "IEKdaueZFKyrz_IPp8Sc4QQ"
|
||||
}"#;
|
||||
serde_json::from_str::<GenerateContentResponse>(input).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user