Skip to content

Commit c4d1efe

Browse files
authored
fix: 🐛 修复 OpenAI 格式连续 tool 消息转 Anthropic 时未合并导致请求报错 (#219)
当用户以 OpenAI 格式请求 Claude 模型时,末尾连续多条 role:tool 消息会被各自 转换为独立的 user 消息,违反 Anthropic API 角色交替要求,导致 Improperly formed request 错误。在 convertMessages 中增加连续同角色消息合并逻辑,并提取 contentToBlocks 复用简化 convertToolMessage 中的内容转换代码。
1 parent 9c5452f commit c4d1efe

File tree

1 file changed

+26
-9
lines changed

1 file changed

+26
-9
lines changed

internal/transformer/outbound/authropic/messages.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -391,12 +391,36 @@ func convertMessages(req *model.InternalLLMRequest) []anthropicModel.MessagePara
391391
}
392392

393393
converted := convertSingleMessage(msg, req.Messages, processedIndexes)
394-
messages = append(messages, converted...)
394+
for _, convertedMsg := range converted {
395+
// Anthropic API 要求消息角色必须交替出现(user/assistant/user/assistant)。
396+
// 当 OpenAI 格式的多个连续 tool 消息被各自转换为独立的 user 消息时,
397+
// 会产生连续的同角色消息,需要合并以避免 "Improperly formed request" 错误。
398+
if n := len(messages); n > 0 && messages[n-1].Role == convertedMsg.Role {
399+
last := &messages[n-1]
400+
last.Content = anthropicModel.MessageContent{
401+
MultipleContent: append(contentToBlocks(last.Content), contentToBlocks(convertedMsg.Content)...),
402+
}
403+
} else {
404+
messages = append(messages, convertedMsg)
405+
}
406+
}
395407
}
396408

397409
return messages
398410
}
399411

412+
// contentToBlocks 将 MessageContent 统一展开为 MessageContentBlock 切片。
413+
func contentToBlocks(c anthropicModel.MessageContent) []anthropicModel.MessageContentBlock {
414+
if len(c.MultipleContent) > 0 {
415+
// 返回副本,避免后续 append 污染原 slice
416+
return append([]anthropicModel.MessageContentBlock(nil), c.MultipleContent...)
417+
}
418+
if c.Content != nil && *c.Content != "" {
419+
return []anthropicModel.MessageContentBlock{{Type: "text", Text: c.Content}}
420+
}
421+
return nil
422+
}
423+
400424
func convertSingleMessage(msg model.Message, allMessages []model.Message, processedIndexes map[int]bool) []anthropicModel.MessageParam {
401425
switch msg.Role {
402426
case "tool":
@@ -449,14 +473,7 @@ func convertToolMessage(msg model.Message, allMessages []model.Message, processe
449473
// original Anthropic request may also include additional user content alongside tool_result.
450474
if userMsg := findUserMessageByIndex(allMessages, *msg.MessageIndex); userMsg != nil {
451475
userContent := buildMessageContent(*userMsg)
452-
if len(userContent.MultipleContent) > 0 {
453-
contentBlocks = append(contentBlocks, userContent.MultipleContent...)
454-
} else if userContent.Content != nil && *userContent.Content != "" {
455-
contentBlocks = append(contentBlocks, anthropicModel.MessageContentBlock{
456-
Type: "text",
457-
Text: userContent.Content,
458-
})
459-
}
476+
contentBlocks = append(contentBlocks, contentToBlocks(userContent)...)
460477
}
461478

462479
processedIndexes[*msg.MessageIndex] = true

0 commit comments

Comments
 (0)