Merge pull request #186 from GGHansome/main

尝试修复使用claude模型无法识别图片问题
This commit is contained in:
leokun
2026-07-16 10:35:43 +08:00
committed by GitHub
+77 -3
View File
@@ -156,6 +156,18 @@ func anthropicEndpointURL(baseURL string) string {
return base + "/v1/messages"
}
// shouldRelocateAnthropicImages 判断是否需要把图片块搬运到末条 user 消息。
//
// 官方 Anthropic 端点(api.anthropic.com)可正确处理任意位置的图片,保持原样;
// 其余第三方中转站默认启用搬运,规避「非末尾图片被丢弃」的转换问题。
func shouldRelocateAnthropicImages(baseURL string) bool {
base := strings.ToLower(strings.TrimSpace(baseURL))
if base == "" {
return false
}
return !strings.Contains(base, "api.anthropic.com")
}
// ApplyAnthropicCompatibleAuthHeaders 同时兼容 Anthropic 原生 x-api-key 和 Bearer token 代理。
func ApplyAnthropicCompatibleAuthHeaders(httpReq *http.Request, apiKey string) {
if httpReq == nil {
@@ -215,8 +227,9 @@ func (adapter *AnthropicAdapter) Stream(ctx context.Context, req StreamRequest,
body := cloneRequestBodyOverride(req.RequestBodyOverride)
if len(body) == 0 {
thinkingConfig := buildAnthropicThinkingConfig(req)
relocateImages := shouldRelocateAnthropicImages(baseURL)
stableMessageCount := anthropicStableProviderMessageCount(req.Messages, req.StableMessageCount, thinkingConfig != nil)
systemParts, messages, err := normalizeAnthropicProviderMessages(req.Messages, thinkingConfig != nil)
systemParts, messages, err := normalizeAnthropicProviderMessages(req.Messages, thinkingConfig != nil, relocateImages)
if err != nil {
return err
}
@@ -1066,7 +1079,7 @@ func anthropicStableProviderMessageCount(input []Message, stableReplayMessageCou
if len(stableReplayMessages) == 0 {
return 0
}
_, messages, err := normalizeAnthropicProviderMessages(stableReplayMessages, thinkingEnabled)
_, messages, err := normalizeAnthropicProviderMessages(stableReplayMessages, thinkingEnabled, false)
if err != nil {
return 0
}
@@ -1122,7 +1135,7 @@ func isAnthropicCacheableBlock(block map[string]any) bool {
}
}
func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool) ([]string, []anthropicMessage, error) {
func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool, relocateImages bool) ([]string, []anthropicMessage, error) {
systemParts := make([]string, 0, len(input))
messages := make([]anthropicMessage, 0, len(input))
pendingToolResults := make([]map[string]any, 0, 2)
@@ -1208,9 +1221,70 @@ func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool) (
}
}
flushToolResults()
if relocateImages {
messages = relocateAnthropicImagesToLastUserMessage(messages)
}
return systemParts, messages, nil
}
// relocateAnthropicImagesToLastUserMessage 把所有 user 消息里的 image 块搬运到最后一条 user 消息的末尾。
//
// 背景:部分第三方中转站(如 Bedrock 代理)在 Anthropic→上游 的消息转换中,
// 会丢弃「后面还跟着大量文本/消息」的非末尾图片块。将图片统一移动到末条 user 消息
// 可规避该问题,同时保留图片信息本身。
//
// 数据流演变:
//
// [user_info] [query + IMG] [reminder] [reminder] [current_request]
// → [user_info] [query] [reminder] [reminder] [current_request + IMG]
//
// 搬运后若某条 user 消息 content 变空,则丢弃该消息,避免 Anthropic 拒绝空内容消息。
func relocateAnthropicImagesToLastUserMessage(messages []anthropicMessage) []anthropicMessage {
lastUserIndex := -1
for index := len(messages) - 1; index >= 0; index-- {
if strings.TrimSpace(messages[index].Role) == "user" {
lastUserIndex = index
break
}
}
if lastUserIndex < 0 {
return messages
}
relocated := make([]map[string]any, 0, 2)
for index := 0; index < len(messages); index++ {
if index == lastUserIndex || strings.TrimSpace(messages[index].Role) != "user" {
continue
}
kept := make([]map[string]any, 0, len(messages[index].Content))
for _, block := range messages[index].Content {
if isAnthropicImageBlock(block) {
relocated = append(relocated, block)
continue
}
kept = append(kept, block)
}
messages[index].Content = kept
}
if len(relocated) == 0 {
return messages
}
messages[lastUserIndex].Content = append(messages[lastUserIndex].Content, relocated...)
compacted := make([]anthropicMessage, 0, len(messages))
for index, message := range messages {
if index != lastUserIndex && strings.TrimSpace(message.Role) == "user" && len(message.Content) == 0 {
continue
}
compacted = append(compacted, message)
}
return compacted
}
func isAnthropicImageBlock(block map[string]any) bool {
return strings.TrimSpace(anthropicStringField(block, "type")) == "image"
}
func anthropicProviderContentBlocks(message Message, thinkingEnabled bool) ([]map[string]any, error) {
blocks, err := anthropicContentBlocks(message)
if err != nil {