From 0aa4f94c86fb72d5a607a85e629cbe0e2a3e2016 Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Wed, 4 Mar 2026 05:57:13 -0500 Subject: [PATCH] fix(provider): omit null tool-call fields in compatible payloads --- src/providers/compatible.rs | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/providers/compatible.rs b/src/providers/compatible.rs index 4a350f845..268ebfb54 100644 --- a/src/providers/compatible.rs +++ b/src/providers/compatible.rs @@ -526,20 +526,23 @@ impl ResponseMessage { struct ToolCall { #[serde(skip_serializing_if = "Option::is_none")] id: Option, - #[serde(rename = "type")] - #[serde(default)] + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] kind: Option, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] function: Option, // Compatibility: Some providers (e.g., older GLM) may use 'name' directly - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] name: Option, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] arguments: Option, // Compatibility: DeepSeek sometimes wraps arguments differently - #[serde(rename = "parameters", default)] + #[serde( + rename = "parameters", + default, + skip_serializing_if = "Option::is_none" + )] parameters: Option, } @@ -2919,6 +2922,26 @@ mod tests { )); } + #[test] + fn convert_messages_for_native_omits_null_compatibility_tool_call_fields() { + let input = vec![ChatMessage::assistant( + r#"{"content":"Need tool","tool_calls":[{"id":"call_abc","name":"shell","arguments":"{\"command\":\"pwd\"}"}]}"#, + )]; + + let converted = OpenAiCompatibleProvider::convert_messages_for_native(&input, true); + assert_eq!(converted.len(), 1); + + let serialized = serde_json::to_value(&converted[0]).unwrap(); + let tool_call = &serialized["tool_calls"][0]; + assert_eq!(tool_call["id"], "call_abc"); + assert_eq!(tool_call["type"], "function"); + assert_eq!(tool_call["function"]["name"], "shell"); + assert_eq!(tool_call["function"]["arguments"], "{\"command\":\"pwd\"}"); + assert!(tool_call.get("name").is_none()); + assert!(tool_call.get("arguments").is_none()); + assert!(tool_call.get("parameters").is_none()); + } + #[test] fn flatten_system_messages_merges_into_first_user() { let input = vec![