fix(agent): drop malformed empty-id tool call rounds

This commit is contained in:
Alix-007 2026-04-03 17:04:59 +08:00
parent da6a862079
commit 2f885c6133
2 changed files with 33 additions and 4 deletions

View file

@ -696,8 +696,10 @@ func sanitizeHistoryForProvider(history []providers.Message) []providers.Message
// within the same assistant turn. // within the same assistant turn.
expectedOrder := make([]string, 0, len(msg.ToolCalls)) expectedOrder := make([]string, 0, len(msg.ToolCalls))
expectedSet := make(map[string]struct{}, len(msg.ToolCalls)) expectedSet := make(map[string]struct{}, len(msg.ToolCalls))
hasEmptyToolCallID := false
for _, tc := range msg.ToolCalls { for _, tc := range msg.ToolCalls {
if tc.ID == "" { if tc.ID == "" {
hasEmptyToolCallID = true
continue continue
} }
if _, exists := expectedSet[tc.ID]; exists { if _, exists := expectedSet[tc.ID]; exists {
@ -707,10 +709,21 @@ func sanitizeHistoryForProvider(history []providers.Message) []providers.Message
expectedOrder = append(expectedOrder, tc.ID) expectedOrder = append(expectedOrder, tc.ID)
} }
// If provider output is malformed and tool calls have no IDs, keep the // A tool-call round with any empty tool_call ID is malformed. Drop the
// assistant turn as-is and let provider-level validation handle it. // entire round, including any contiguous tool results, so we do not
if len(expectedOrder) == 0 { // preserve provider-invalid history.
final = append(final, msg) if hasEmptyToolCallID {
logger.DebugCF("agent", "Dropping assistant tool-call round with empty tool_call ID", map[string]any{
"tool_call_count": len(msg.ToolCalls),
})
j := i + 1
for ; j < len(sanitized); j++ {
if sanitized[j].Role != "tool" {
break
}
}
i = j - 1
continue continue
} }

View file

@ -335,3 +335,19 @@ func TestSanitizeHistoryForProvider_ReusedToolCallIDAcrossRounds(t *testing.T) {
t.Fatalf("expected both rounds to keep tool_call_id call_1, got %q and %q", result[2].ToolCallID, result[6].ToolCallID) t.Fatalf("expected both rounds to keep tool_call_id call_1, got %q and %q", result[2].ToolCallID, result[6].ToolCallID)
} }
} }
func TestSanitizeHistoryForProvider_DropsMalformedMixedToolCallIDs(t *testing.T) {
history := []providers.Message{
msg("user", "do two things"),
assistantWithTools("A", ""),
toolResult("A"),
msg("user", "next question"),
msg("assistant", "answer"),
}
result := sanitizeHistoryForProvider(history)
if len(result) != 3 {
t.Fatalf("expected 3 messages, got %d: %+v", len(result), roles(result))
}
assertRoles(t, result, "user", "user", "assistant")
}