fix: multi-tool-call sessions drop 2nd+ tool results
sanitizeHistoryForProvider only allowed tool messages when the preceding message was an assistant with tool_calls. For assistant turns with 2+ tool calls, the 2nd tool result's predecessor was the 1st tool result (role "tool"), causing it to be dropped. This made the API see an assistant with N tool_calls but <N results, triggering "tool call result does not follow tool call". Fix: also allow tool messages after sibling tool messages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1e9154f8a5
commit
deebe3e7e8
2 changed files with 40 additions and 3 deletions
|
|
@ -259,11 +259,12 @@ func sanitizeHistoryForProvider(history []providers.Message) []providers.Message
|
|||
continue
|
||||
}
|
||||
last := sanitized[len(sanitized)-1]
|
||||
if last.Role != "assistant" || len(last.ToolCalls) == 0 {
|
||||
logger.DebugCF("agent", "Dropping orphaned tool message", map[string]any{})
|
||||
continue
|
||||
}
|
||||
// Allow tool results after their assistant or after sibling tool results
|
||||
if last.Role == "tool" || (last.Role == "assistant" && len(last.ToolCalls) > 0) {
|
||||
sanitized = append(sanitized, msg)
|
||||
} else {
|
||||
logger.DebugCF("agent", "Dropping orphaned tool message", map[string]any{})
|
||||
}
|
||||
|
||||
case "assistant":
|
||||
if len(msg.ToolCalls) > 0 {
|
||||
|
|
|
|||
|
|
@ -1679,3 +1679,39 @@ func TestBuildRichStatus_LatestEntryNoInlineResult(t *testing.T) {
|
|||
t.Errorf("expected exactly 1 separator, got %d in:\n%s", sepCount, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeHistoryForProvider_MultiToolCall(t *testing.T) {
|
||||
// Regression: assistant with 2+ tool_calls had 2nd+ tool results dropped
|
||||
// because the check only allowed tool after assistant, not after sibling tool.
|
||||
history := []providers.Message{
|
||||
{Role: "user", Content: "hello"},
|
||||
{Role: "assistant", Content: "", ToolCalls: []providers.ToolCall{
|
||||
{ID: "a", Function: &providers.FunctionCall{Name: "exec"}},
|
||||
{ID: "b", Function: &providers.FunctionCall{Name: "read_file"}},
|
||||
}},
|
||||
{Role: "tool", Content: "ok", ToolCallID: "a"},
|
||||
{Role: "tool", Content: "ok", ToolCallID: "b"},
|
||||
{Role: "assistant", Content: "done"},
|
||||
}
|
||||
|
||||
got := sanitizeHistoryForProvider(history)
|
||||
|
||||
// All 5 messages must survive
|
||||
if len(got) != 5 {
|
||||
roles := make([]string, len(got))
|
||||
for i, m := range got {
|
||||
roles[i] = m.Role
|
||||
}
|
||||
t.Fatalf("expected 5 messages, got %d: %v", len(got), roles)
|
||||
}
|
||||
// Verify both tool results present
|
||||
toolCount := 0
|
||||
for _, m := range got {
|
||||
if m.Role == "tool" {
|
||||
toolCount++
|
||||
}
|
||||
}
|
||||
if toolCount != 2 {
|
||||
t.Errorf("expected 2 tool results, got %d", toolCount)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue