mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-18 03:57:06 +08:00
fix: claude model can not access image
This commit is contained in:
@@ -156,6 +156,18 @@ func anthropicEndpointURL(baseURL string) string {
|
|||||||
return base + "/v1/messages"
|
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 代理。
|
// ApplyAnthropicCompatibleAuthHeaders 同时兼容 Anthropic 原生 x-api-key 和 Bearer token 代理。
|
||||||
func ApplyAnthropicCompatibleAuthHeaders(httpReq *http.Request, apiKey string) {
|
func ApplyAnthropicCompatibleAuthHeaders(httpReq *http.Request, apiKey string) {
|
||||||
if httpReq == nil {
|
if httpReq == nil {
|
||||||
@@ -215,8 +227,9 @@ func (adapter *AnthropicAdapter) Stream(ctx context.Context, req StreamRequest,
|
|||||||
body := cloneRequestBodyOverride(req.RequestBodyOverride)
|
body := cloneRequestBodyOverride(req.RequestBodyOverride)
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
thinkingConfig := buildAnthropicThinkingConfig(req)
|
thinkingConfig := buildAnthropicThinkingConfig(req)
|
||||||
|
relocateImages := shouldRelocateAnthropicImages(baseURL)
|
||||||
stableMessageCount := anthropicStableProviderMessageCount(req.Messages, req.StableMessageCount, thinkingConfig != nil)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1066,7 +1079,7 @@ func anthropicStableProviderMessageCount(input []Message, stableReplayMessageCou
|
|||||||
if len(stableReplayMessages) == 0 {
|
if len(stableReplayMessages) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
_, messages, err := normalizeAnthropicProviderMessages(stableReplayMessages, thinkingEnabled)
|
_, messages, err := normalizeAnthropicProviderMessages(stableReplayMessages, thinkingEnabled, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
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))
|
systemParts := make([]string, 0, len(input))
|
||||||
messages := make([]anthropicMessage, 0, len(input))
|
messages := make([]anthropicMessage, 0, len(input))
|
||||||
pendingToolResults := make([]map[string]any, 0, 2)
|
pendingToolResults := make([]map[string]any, 0, 2)
|
||||||
@@ -1208,9 +1221,70 @@ func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool) (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
flushToolResults()
|
flushToolResults()
|
||||||
|
if relocateImages {
|
||||||
|
messages = relocateAnthropicImagesToLastUserMessage(messages)
|
||||||
|
}
|
||||||
return systemParts, messages, nil
|
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) {
|
func anthropicProviderContentBlocks(message Message, thinkingEnabled bool) ([]map[string]any, error) {
|
||||||
blocks, err := anthropicContentBlocks(message)
|
blocks, err := anthropicContentBlocks(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user