mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 20:30:51 +08:00
refactor(translator): consolidate Claude content handling logic - Unified logic for text and image content conversion to improve maintainability. - Introduced `convertClaudeContentPart` utility for consistent content transformation. - Replaced redundant string operations with streamlined JSON modifications. - Adjusted validation checks for message content generation.
This commit is contained in:
@@ -8,7 +8,6 @@ package claude
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
@@ -79,7 +78,9 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
|||||||
if system.IsArray() {
|
if system.IsArray() {
|
||||||
systemResults := system.Array()
|
systemResults := system.Array()
|
||||||
for i := 0; i < len(systemResults); i++ {
|
for i := 0; i < len(systemResults); i++ {
|
||||||
systemMsgJSON, _ = sjson.SetRaw(systemMsgJSON, "content.-1", systemResults[i].Raw)
|
if contentItem, ok := convertClaudeContentPart(systemResults[i]); ok {
|
||||||
|
systemMsgJSON, _ = sjson.SetRaw(systemMsgJSON, "content.-1", contentItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,29 +95,16 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
|||||||
|
|
||||||
// Handle content
|
// Handle content
|
||||||
if contentResult.Exists() && contentResult.IsArray() {
|
if contentResult.Exists() && contentResult.IsArray() {
|
||||||
var textParts []string
|
var contentItems []string
|
||||||
var toolCalls []interface{}
|
var toolCalls []interface{}
|
||||||
|
|
||||||
contentResult.ForEach(func(_, part gjson.Result) bool {
|
contentResult.ForEach(func(_, part gjson.Result) bool {
|
||||||
partType := part.Get("type").String()
|
partType := part.Get("type").String()
|
||||||
|
|
||||||
switch partType {
|
switch partType {
|
||||||
case "text":
|
case "text", "image":
|
||||||
textParts = append(textParts, part.Get("text").String())
|
if contentItem, ok := convertClaudeContentPart(part); ok {
|
||||||
|
contentItems = append(contentItems, contentItem)
|
||||||
case "image":
|
|
||||||
// Convert Anthropic image format to OpenAI format
|
|
||||||
if source := part.Get("source"); source.Exists() {
|
|
||||||
sourceType := source.Get("type").String()
|
|
||||||
if sourceType == "base64" {
|
|
||||||
mediaType := source.Get("media_type").String()
|
|
||||||
data := source.Get("data").String()
|
|
||||||
imageURL := "data:" + mediaType + ";base64," + data
|
|
||||||
|
|
||||||
// For now, add as text since OpenAI image handling is complex
|
|
||||||
// In a real implementation, you'd need to handle this properly
|
|
||||||
textParts = append(textParts, "[Image: "+imageURL+"]")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "tool_use":
|
case "tool_use":
|
||||||
@@ -149,13 +137,17 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Create main message if there's text content or tool calls
|
// Create main message if there's text content or tool calls
|
||||||
if len(textParts) > 0 || len(toolCalls) > 0 {
|
if len(contentItems) > 0 || len(toolCalls) > 0 {
|
||||||
msgJSON := `{"role":"","content":""}`
|
msgJSON := `{"role":"","content":""}`
|
||||||
msgJSON, _ = sjson.Set(msgJSON, "role", role)
|
msgJSON, _ = sjson.Set(msgJSON, "role", role)
|
||||||
|
|
||||||
// Set content
|
// Set content
|
||||||
if len(textParts) > 0 {
|
if len(contentItems) > 0 {
|
||||||
msgJSON, _ = sjson.Set(msgJSON, "content", strings.Join(textParts, ""))
|
contentArrayJSON := "[]"
|
||||||
|
for _, contentItem := range contentItems {
|
||||||
|
contentArrayJSON, _ = sjson.SetRaw(contentArrayJSON, "-1", contentItem)
|
||||||
|
}
|
||||||
|
msgJSON, _ = sjson.SetRaw(msgJSON, "content", contentArrayJSON)
|
||||||
} else {
|
} else {
|
||||||
msgJSON, _ = sjson.Set(msgJSON, "content", "")
|
msgJSON, _ = sjson.Set(msgJSON, "content", "")
|
||||||
}
|
}
|
||||||
@@ -166,7 +158,20 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
|||||||
msgJSON, _ = sjson.SetRaw(msgJSON, "tool_calls", string(toolCallsJSON))
|
msgJSON, _ = sjson.SetRaw(msgJSON, "tool_calls", string(toolCallsJSON))
|
||||||
}
|
}
|
||||||
|
|
||||||
if gjson.Get(msgJSON, "content").String() != "" || len(toolCalls) != 0 {
|
contentValue := gjson.Get(msgJSON, "content")
|
||||||
|
hasContent := false
|
||||||
|
switch {
|
||||||
|
case !contentValue.Exists():
|
||||||
|
hasContent = false
|
||||||
|
case contentValue.Type == gjson.String:
|
||||||
|
hasContent = contentValue.String() != ""
|
||||||
|
case contentValue.IsArray():
|
||||||
|
hasContent = len(contentValue.Array()) > 0
|
||||||
|
default:
|
||||||
|
hasContent = contentValue.Raw != "" && contentValue.Raw != "null"
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasContent || len(toolCalls) != 0 {
|
||||||
messagesJSON, _ = sjson.Set(messagesJSON, "-1", gjson.Parse(msgJSON).Value())
|
messagesJSON, _ = sjson.Set(messagesJSON, "-1", gjson.Parse(msgJSON).Value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,3 +242,53 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
|||||||
|
|
||||||
return []byte(out)
|
return []byte(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertClaudeContentPart(part gjson.Result) (string, bool) {
|
||||||
|
partType := part.Get("type").String()
|
||||||
|
|
||||||
|
switch partType {
|
||||||
|
case "text":
|
||||||
|
if !part.Get("text").Exists() {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
textContent := `{"type":"text","text":""}`
|
||||||
|
textContent, _ = sjson.Set(textContent, "text", part.Get("text").String())
|
||||||
|
return textContent, true
|
||||||
|
|
||||||
|
case "image":
|
||||||
|
var imageURL string
|
||||||
|
|
||||||
|
if source := part.Get("source"); source.Exists() {
|
||||||
|
sourceType := source.Get("type").String()
|
||||||
|
switch sourceType {
|
||||||
|
case "base64":
|
||||||
|
mediaType := source.Get("media_type").String()
|
||||||
|
if mediaType == "" {
|
||||||
|
mediaType = "application/octet-stream"
|
||||||
|
}
|
||||||
|
data := source.Get("data").String()
|
||||||
|
if data != "" {
|
||||||
|
imageURL = "data:" + mediaType + ";base64," + data
|
||||||
|
}
|
||||||
|
case "url":
|
||||||
|
imageURL = source.Get("url").String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if imageURL == "" {
|
||||||
|
imageURL = part.Get("url").String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if imageURL == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
imageContent := `{"type":"image_url","image_url":{"url":""}}`
|
||||||
|
imageContent, _ = sjson.Set(imageContent, "image_url.url", imageURL)
|
||||||
|
|
||||||
|
return imageContent, true
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user