diff --git a/src-tauri/src/proxy/providers/transform.rs b/src-tauri/src/proxy/providers/transform.rs index 2cdc93c79..2b660f334 100644 --- a/src-tauri/src/proxy/providers/transform.rs +++ b/src-tauri/src/proxy/providers/transform.rs @@ -113,6 +113,7 @@ pub fn anthropic_to_openai(body: Value, cache_key: Option<&str>) -> Result) -> Result) { + let system_count = messages + .iter() + .filter(|message| message.get("role").and_then(|value| value.as_str()) == Some("system")) + .count(); + + if system_count == 0 { + return; + } + + if system_count == 1 { + if let Some(index) = messages.iter().position(|message| { + message.get("role").and_then(|value| value.as_str()) == Some("system") + }) { + if index > 0 { + let message = messages.remove(index); + messages.insert(0, message); + } + } + return; + } + + let mut parts = Vec::new(); + messages.retain(|message| { + if message.get("role").and_then(|value| value.as_str()) != Some("system") { + return true; + } + + match message.get("content") { + Some(Value::String(text)) if !text.is_empty() => parts.push(text.clone()), + Some(Value::Array(content_parts)) => { + let text = content_parts + .iter() + .filter_map(|part| part.get("text").and_then(|value| value.as_str())) + .collect::>() + .join("\n"); + if !text.is_empty() { + parts.push(text); + } + } + _ => {} + } + + false + }); + + if !parts.is_empty() { + messages.insert(0, json!({"role": "system", "content": parts.join("\n")})); + } +} + /// 转换单条消息到 OpenAI 格式(可能产生多条消息) fn convert_message_to_openai( role: &str, @@ -560,6 +612,31 @@ mod tests { assert_eq!(result["tools"][0]["function"]["name"], "get_weather"); } + #[test] + fn test_anthropic_to_openai_normalizes_fragmented_system_messages() { + let input = json!({ + "model": "claude-3-sonnet", + "max_tokens": 1024, + "system": [ + {"type": "text", "text": "You are Claude Code."}, + {"type": "text", "text": "Be concise."} + ], + "messages": [ + {"role": "system", "content": "Follow repo conventions."}, + {"role": "user", "content": "Hello"} + ] + }); + + let result = anthropic_to_openai(input, None).unwrap(); + assert_eq!(result["messages"].as_array().unwrap().len(), 2); + assert_eq!(result["messages"][0]["role"], "system"); + assert_eq!( + result["messages"][0]["content"], + "You are Claude Code.\nBe concise.\nFollow repo conventions." + ); + assert_eq!(result["messages"][1]["role"], "user"); + } + #[test] fn test_anthropic_to_openai_tool_use() { let input = json!({