From aa2eac97410f03ea1d42ba6a789ecd3c2cd330f1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:16:59 +0000 Subject: [PATCH] fix(providers/anthropic): drop empty assistant messages When using the Anthropic API, it expects `tool_result` blocks in a `user` message to immediately follow the `tool_use` block from the preceding `assistant` message. However, the agent's logic can sometimes produce empty `assistant` messages (with no text and no tool calls) between consecutive tool calls. This commit updates `buildRequestBody` in the Anthropic Messages provider to skip empty `assistant` messages, ensuring that the merged `user` tool results correctly follow their originating `assistant` tool uses without interruption. Co-authored-by: TanLuong <28281768+TanLuong@users.noreply.github.com> --- pkg/providers/anthropic_messages/provider.go | 8 +++++ .../anthropic_messages/provider_test.go | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/pkg/providers/anthropic_messages/provider.go b/pkg/providers/anthropic_messages/provider.go index 4093f6a74..0debffc1b 100644 --- a/pkg/providers/anthropic_messages/provider.go +++ b/pkg/providers/anthropic_messages/provider.go @@ -245,6 +245,14 @@ func buildRequestBody( content = append(content, toolUse) } + // Skip empty assistant messages that have no content and no valid tool calls. + // This prevents breaking the Anthropic API requirement where a user message + // containing tool results must immediately follow the assistant message + // containing the corresponding tool calls. + if len(content) == 0 { + continue + } + apiMessages = append(apiMessages, map[string]any{ "role": "assistant", "content": content, diff --git a/pkg/providers/anthropic_messages/provider_test.go b/pkg/providers/anthropic_messages/provider_test.go index 39bc48117..09ba1942b 100644 --- a/pkg/providers/anthropic_messages/provider_test.go +++ b/pkg/providers/anthropic_messages/provider_test.go @@ -506,6 +506,25 @@ func TestBuildRequestBodyEdgeCases(t *testing.T) { }, wantErr: false, }, + { + name: "skip empty assistant messages", + messages: []Message{ + {Role: "user", Content: "hello"}, + {Role: "assistant", Content: "", ToolCalls: []ToolCall{}}, // Should be skipped + {Role: "assistant", Content: "valid message", ToolCalls: []ToolCall{}}, + {Role: "assistant", Content: "", ToolCalls: []ToolCall{ + {ID: "tool-valid", Name: "test_tool", Arguments: map[string]any{"arg": "value"}}, + }}, + {Role: "assistant", Content: "", ToolCalls: []ToolCall{ + {ID: "tool-empty", Name: "", Arguments: map[string]any{"ignored": true}}, + }}, // Should be skipped because tool call is empty and content is empty + }, + model: "test-model", + options: map[string]any{ + "max_tokens": 8192, + }, + wantErr: false, + }, } for _, tt := range tests { @@ -558,6 +577,16 @@ func TestBuildRequestBodyEdgeCases(t *testing.T) { t.Fatalf("tool_use id = %v, want %q", gotID, "tool-valid") } } + + if tt.name == "skip empty assistant messages" { + messages, ok := got["messages"].([]any) + if !ok { + t.Fatalf("messages is not []any") + } + if len(messages) != 3 { + t.Fatalf("expected 3 API messages (user, assistant with text, assistant with tool_use), got %d: %#v", len(messages), messages) + } + } }) } }