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
ca447569fd
commit
1967d385a3
2 changed files with 40 additions and 3 deletions
|
|
@ -259,11 +259,12 @@ func sanitizeHistoryForProvider(history []providers.Message) []providers.Message
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
last := sanitized[len(sanitized)-1]
|
last := sanitized[len(sanitized)-1]
|
||||||
if last.Role != "assistant" || len(last.ToolCalls) == 0 {
|
// 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{})
|
logger.DebugCF("agent", "Dropping orphaned tool message", map[string]any{})
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
sanitized = append(sanitized, msg)
|
|
||||||
|
|
||||||
case "assistant":
|
case "assistant":
|
||||||
if len(msg.ToolCalls) > 0 {
|
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)
|
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