feat(anthropic): support both string and array content formats

- Add MessageContent type to handle both string and array content formats
- Implement UnmarshalJSON to automatically detect content format
- Add ToContentArray() method for unified content processing
- Update Message and Response structs to use MessageContent
- Fix all content processing logic in main.go and controller
- Resolve JSON parsing errors for different content formats

支持Anthropic协议字符串和数组内容格式:
- 添加MessageContent类型处理字符串和数组两种内容格式
- 实现UnmarshalJSON自动检测内容格式
- 添加ToContentArray()方法统一内容处理
- 更新Message和Response结构体使用MessageContent
- 修复main.go和controller中的所有内容处理逻辑
- 解决不同内容格式的JSON解析错误
This commit is contained in:
Deadwalk
2025-09-29 16:52:19 +08:00
parent 7b9631408f
commit a587ac145a
2 changed files with 74 additions and 21 deletions

View File

@@ -107,10 +107,12 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
claudeMessage := Message{
Role: message.Role,
}
var content Content
var contents []Content
if message.IsStringContent() {
content.Type = "text"
content.Text = message.StringContent()
content := Content{
Type: "text",
Text: message.StringContent(),
}
if message.Role == "tool" {
claudeMessage.Role = "user"
content.Type = "tool_result"
@@ -118,21 +120,22 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
content.Text = ""
content.ToolUseId = message.ToolCallId
}
claudeMessage.Content = append(claudeMessage.Content, content)
contents = append(contents, content)
for i := range message.ToolCalls {
inputParam := make(map[string]any)
_ = json.Unmarshal([]byte(message.ToolCalls[i].Function.Arguments.(string)), &inputParam)
claudeMessage.Content = append(claudeMessage.Content, Content{
contents = append(contents, Content{
Type: "tool_use",
Id: message.ToolCalls[i].Id,
Name: message.ToolCalls[i].Function.Name,
Input: inputParam,
})
}
claudeMessage.Content = MessageContent{value: contents}
claudeRequest.Messages = append(claudeRequest.Messages, claudeMessage)
continue
}
var contents []Content
contents = []Content{} // Reset the slice for reuse
openaiContent := message.ParseContent()
for _, part := range openaiContent {
var content Content
@@ -150,7 +153,7 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
}
contents = append(contents, content)
}
claudeMessage.Content = contents
claudeMessage.Content = MessageContent{value: contents}
claudeRequest.Messages = append(claudeRequest.Messages, claudeMessage)
}
return &claudeRequest
@@ -220,11 +223,12 @@ func StreamResponseClaude2OpenAI(claudeResponse *StreamResponse) (*openai.ChatCo
func ResponseClaude2OpenAI(claudeResponse *Response) *openai.TextResponse {
var responseText string
if len(claudeResponse.Content) > 0 {
responseText = claudeResponse.Content[0].Text
contentArray := claudeResponse.Content.ToContentArray()
if len(contentArray) > 0 {
responseText = contentArray[0].Text
}
tools := make([]model.Tool, 0)
for _, v := range claudeResponse.Content {
for _, v := range contentArray {
if v.Type == "tool_use" {
args, _ := json.Marshal(v.Input)
tools = append(tools, model.Tool{

View File

@@ -29,9 +29,58 @@ type Content struct {
ToolUseId string `json:"tool_use_id,omitempty"`
}
// MessageContent can handle both string and array formats for the content field
type MessageContent struct {
value interface{}
}
// UnmarshalJSON implements json.Unmarshaler to handle both string and array formats
func (m *MessageContent) UnmarshalJSON(data []byte) error {
// Try to unmarshal as string first
var str string
if err := json.Unmarshal(data, &str); err == nil {
m.value = str
return nil
}
// If that fails, try to unmarshal as array of Content
var arr []Content
if err := json.Unmarshal(data, &arr); err == nil {
m.value = arr
return nil
}
return fmt.Errorf("message content must be either a string or an array of content blocks")
}
// MarshalJSON implements json.Marshaler
func (m MessageContent) MarshalJSON() ([]byte, error) {
return json.Marshal(m.value)
}
// ToContentArray converts the message content to a []Content array
func (m MessageContent) ToContentArray() []Content {
if m.value == nil {
return []Content{}
}
switch v := m.value.(type) {
case string:
// Convert string to a single text content block
return []Content{{
Type: "text",
Text: v,
}}
case []Content:
return v
default:
return []Content{}
}
}
type Message struct {
Role string `json:"role"`
Content []Content `json:"content"`
Role string `json:"role"`
Content MessageContent `json:"content"`
}
type Tool struct {
@@ -146,15 +195,15 @@ type Error struct {
}
type Response struct {
Id string `json:"id"`
Type string `json:"type"`
Role string `json:"role"`
Content []Content `json:"content"`
Model string `json:"model"`
StopReason *string `json:"stop_reason"`
StopSequence *string `json:"stop_sequence"`
Usage Usage `json:"usage"`
Error Error `json:"error"`
Id string `json:"id"`
Type string `json:"type"`
Role string `json:"role"`
Content MessageContent `json:"content"`
Model string `json:"model"`
StopReason *string `json:"stop_reason"`
StopSequence *string `json:"stop_sequence"`
Usage Usage `json:"usage"`
Error Error `json:"error"`
}
type Delta struct {