From 1956d06f1b5448b172342fc5d7e45c91e15a9967 Mon Sep 17 00:00:00 2001 From: "shenpeng.sp0916" Date: Mon, 15 Jun 2026 00:03:53 +0800 Subject: [PATCH] fix(anthropic): iterate all message parts in handleAIMessage and handleToolMessage Previously both functions only checked Parts[0], silently dropping text content when a tool call was present, or dropping subsequent tool calls in multi-part messages. This caused Anthropic API errors like "unexpected tool_use_id found in tool_result blocks" when the AI response contained both text and tool_use content. Iterate all parts to support mixed content (text + tool_use) and multiple parallel tool calls/results in a single message. Fixes #1468 Co-Authored-By: Claude Opus 4.7 --- llms/anthropic/anthropicllm.go | 77 +++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/llms/anthropic/anthropicllm.go b/llms/anthropic/anthropicllm.go index 417de1b25..c147e6a21 100644 --- a/llms/anthropic/anthropicllm.go +++ b/llms/anthropic/anthropicllm.go @@ -384,34 +384,37 @@ func handleHumanMessage(msg llms.MessageContent) (anthropicclient.ChatMessage, e } func handleAIMessage(msg llms.MessageContent) (anthropicclient.ChatMessage, error) { - if toolCall, ok := msg.Parts[0].(llms.ToolCall); ok { - var inputStruct map[string]interface{} - err := json.Unmarshal([]byte(toolCall.FunctionCall.Arguments), &inputStruct) - if err != nil { - return anthropicclient.ChatMessage{}, fmt.Errorf("anthropic: failed to unmarshal tool call arguments: %w", err) - } - toolUse := anthropicclient.ToolUseContent{ - Type: "tool_use", - ID: toolCall.ID, - Name: toolCall.FunctionCall.Name, - Input: inputStruct, + var contents []anthropicclient.Content + for _, part := range msg.Parts { + switch p := part.(type) { + case llms.ToolCall: + var inputStruct map[string]interface{} + err := json.Unmarshal([]byte(p.FunctionCall.Arguments), &inputStruct) + if err != nil { + return anthropicclient.ChatMessage{}, fmt.Errorf("anthropic: failed to unmarshal tool call arguments: %w", err) + } + contents = append(contents, anthropicclient.ToolUseContent{ + Type: "tool_use", + ID: p.ID, + Name: p.FunctionCall.Name, + Input: inputStruct, + }) + case llms.TextContent: + contents = append(contents, &anthropicclient.TextContent{ + Type: "text", + Text: p.Text, + }) + default: + return anthropicclient.ChatMessage{}, fmt.Errorf("anthropic: %w for AI message", ErrInvalidContentType) } - - return anthropicclient.ChatMessage{ - Role: RoleAssistant, - Content: []anthropicclient.Content{toolUse}, - }, nil } - if textContent, ok := msg.Parts[0].(llms.TextContent); ok { - return anthropicclient.ChatMessage{ - Role: RoleAssistant, - Content: []anthropicclient.Content{&anthropicclient.TextContent{ - Type: "text", - Text: textContent.Text, - }}, - }, nil + if len(contents) == 0 { + return anthropicclient.ChatMessage{}, fmt.Errorf("anthropic: %w for AI message", ErrInvalidContentType) } - return anthropicclient.ChatMessage{}, fmt.Errorf("anthropic: %w for AI message", ErrInvalidContentType) + return anthropicclient.ChatMessage{ + Role: RoleAssistant, + Content: contents, + }, nil } type ToolResult struct { @@ -421,19 +424,25 @@ type ToolResult struct { } func handleToolMessage(msg llms.MessageContent) (anthropicclient.ChatMessage, error) { - if toolCallResponse, ok := msg.Parts[0].(llms.ToolCallResponse); ok { - toolContent := anthropicclient.ToolResultContent{ + var contents []anthropicclient.Content + for _, part := range msg.Parts { + toolCallResponse, ok := part.(llms.ToolCallResponse) + if !ok { + return anthropicclient.ChatMessage{}, fmt.Errorf("anthropic: %w for tool message", ErrInvalidContentType) + } + contents = append(contents, anthropicclient.ToolResultContent{ Type: "tool_result", ToolUseID: toolCallResponse.ToolCallID, Content: toolCallResponse.Content, - } - - return anthropicclient.ChatMessage{ - Role: RoleUser, - Content: []anthropicclient.Content{toolContent}, - }, nil + }) + } + if len(contents) == 0 { + return anthropicclient.ChatMessage{}, fmt.Errorf("anthropic: %w for tool message", ErrInvalidContentType) } - return anthropicclient.ChatMessage{}, fmt.Errorf("anthropic: %w for tool message", ErrInvalidContentType) + return anthropicclient.ChatMessage{ + Role: RoleUser, + Content: contents, + }, nil } // SupportsReasoning implements the ReasoningModel interface.