mirror of
https://gh-proxy.org/https://github.com/klinsmaya/CLIProxyAPI.git
synced 2026-11-08 01:16:47 +08:00
feat(translator): consolidate mid-conversation system messages into initial system content
- Updated `ConvertClaudeRequestToOpenAI` to move mid-conversation `system` messages to the initial system content. - Added a new test case `TestConvertClaudeRequestToOpenAI_MidConversationSystemMessagesMoveToInitialSystem` to validate functionality. - Refactored logic for appending system message content for improved readability and maintainability. Closes: #3815
This commit is contained in:
@@ -103,25 +103,41 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
// Handle system message first
|
||||
systemMsgJSON := []byte(`{"role":"system","content":[]}`)
|
||||
hasSystemContent := false
|
||||
if system := root.Get("system"); system.Exists() {
|
||||
if system.Type == gjson.String {
|
||||
if system.String() != "" && !util.IsClaudeCodeAttributionSystemText(system.String()) {
|
||||
oldSystem := []byte(`{"type":"text","text":""}`)
|
||||
oldSystem, _ = sjson.SetBytes(oldSystem, "text", system.String())
|
||||
systemMsgJSON, _ = sjson.SetRawBytes(systemMsgJSON, "content.-1", oldSystem)
|
||||
hasSystemContent = true
|
||||
}
|
||||
} else if system.Type == gjson.JSON {
|
||||
if system.IsArray() {
|
||||
systemResults := system.Array()
|
||||
for i := 0; i < len(systemResults); i++ {
|
||||
if contentItem, ok := convertClaudeContentPart(systemResults[i]); ok {
|
||||
systemMsgJSON, _ = sjson.SetRawBytes(systemMsgJSON, "content.-1", []byte(contentItem))
|
||||
hasSystemContent = true
|
||||
}
|
||||
}
|
||||
}
|
||||
appendSystemContent := func(content gjson.Result) {
|
||||
if !content.Exists() {
|
||||
return
|
||||
}
|
||||
if content.Type == gjson.String {
|
||||
if content.String() == "" || util.IsClaudeCodeAttributionSystemText(content.String()) {
|
||||
return
|
||||
}
|
||||
oldSystem := []byte(`{"type":"text","text":""}`)
|
||||
oldSystem, _ = sjson.SetBytes(oldSystem, "text", content.String())
|
||||
systemMsgJSON, _ = sjson.SetRawBytes(systemMsgJSON, "content.-1", oldSystem)
|
||||
hasSystemContent = true
|
||||
return
|
||||
}
|
||||
if content.IsArray() {
|
||||
content.ForEach(func(_, item gjson.Result) bool {
|
||||
if contentItem, ok := convertClaudeContentPart(item); ok {
|
||||
systemMsgJSON, _ = sjson.SetRawBytes(systemMsgJSON, "content.-1", []byte(contentItem))
|
||||
hasSystemContent = true
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if system := root.Get("system"); system.Exists() {
|
||||
appendSystemContent(system)
|
||||
}
|
||||
if messages := root.Get("messages"); messages.Exists() && messages.IsArray() {
|
||||
messages.ForEach(func(_, message gjson.Result) bool {
|
||||
if message.Get("role").String() == "system" {
|
||||
appendSystemContent(message.Get("content"))
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
// Only add system message if it has content
|
||||
if hasSystemContent {
|
||||
@@ -132,6 +148,9 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
if messages := root.Get("messages"); messages.Exists() && messages.IsArray() {
|
||||
messages.ForEach(func(_, message gjson.Result) bool {
|
||||
role := message.Get("role").String()
|
||||
if role == "system" {
|
||||
return true
|
||||
}
|
||||
contentResult := message.Get("content")
|
||||
|
||||
// Handle content
|
||||
|
||||
@@ -2,6 +2,7 @@ package claude
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
@@ -356,6 +357,47 @@ func validGPTChatReasoningSignature() string {
|
||||
return base64.URLEncoding.EncodeToString(raw)
|
||||
}
|
||||
|
||||
func TestConvertClaudeRequestToOpenAI_MidConversationSystemMessagesMoveToInitialSystem(t *testing.T) {
|
||||
inputJSON := `{
|
||||
"model": "claude-sonnet-4-5",
|
||||
"system": [{"type": "text", "text": "Top-level rules"}],
|
||||
"messages": [
|
||||
{"role": "user", "content": [{"type": "text", "text": "Hello"}]},
|
||||
{"role": "system", "content": "String mid-conversation rule"},
|
||||
{"role": "assistant", "content": [{"type": "text", "text": "Hi there"}]},
|
||||
{"role": "system", "content": [{"type": "text", "text": "Array mid-conversation rule"}]},
|
||||
{"role": "user", "content": [{"type": "text", "text": "Follow up"}]}
|
||||
]
|
||||
}`
|
||||
|
||||
result := ConvertClaudeRequestToOpenAI("gpt-5", []byte(inputJSON), false)
|
||||
resultJSON := gjson.ParseBytes(result)
|
||||
messages := resultJSON.Get("messages").Array()
|
||||
|
||||
if len(messages) != 4 {
|
||||
t.Fatalf("Expected 4 messages, got %d: %s", len(messages), resultJSON.Get("messages").Raw)
|
||||
}
|
||||
|
||||
roles := make([]string, 0, len(messages))
|
||||
for _, message := range messages {
|
||||
roles = append(roles, message.Get("role").String())
|
||||
}
|
||||
if got, want := roles, []string{"system", "user", "assistant", "user"}; fmt.Sprintf("%v", got) != fmt.Sprintf("%v", want) {
|
||||
t.Fatalf("Unexpected message roles: got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
systemContent := messages[0].Get("content").Array()
|
||||
if len(systemContent) != 3 {
|
||||
t.Fatalf("Expected 3 system content items, got %d: %s", len(systemContent), messages[0].Get("content").Raw)
|
||||
}
|
||||
wantTexts := []string{"Top-level rules", "String mid-conversation rule", "Array mid-conversation rule"}
|
||||
for i, want := range wantTexts {
|
||||
if got := systemContent[i].Get("text").String(); got != want {
|
||||
t.Fatalf("system content[%d] = %q, want %q", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertClaudeRequestToOpenAI_SystemMessageScenarios(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user