fix deepseek-chat bug (#1066)
Co-authored-by: FantasticCode2019 <1443996278@qq.com>
This commit is contained in:
parent
9b1e73de9c
commit
7df7e0495c
2 changed files with 128 additions and 1 deletions
|
|
@ -605,7 +605,60 @@ func sanitizeHistoryForProvider(history []providers.Message) []providers.Message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sanitized
|
// Second pass: ensure every assistant message with tool_calls has matching
|
||||||
|
// tool result messages following it. This is required by strict providers
|
||||||
|
// like DeepSeek that enforce: "An assistant message with 'tool_calls' must
|
||||||
|
// be followed by tool messages responding to each 'tool_call_id'."
|
||||||
|
final := make([]providers.Message, 0, len(sanitized))
|
||||||
|
for i := 0; i < len(sanitized); i++ {
|
||||||
|
msg := sanitized[i]
|
||||||
|
if msg.Role == "assistant" && len(msg.ToolCalls) > 0 {
|
||||||
|
// Collect expected tool_call IDs
|
||||||
|
expected := make(map[string]bool, len(msg.ToolCalls))
|
||||||
|
for _, tc := range msg.ToolCalls {
|
||||||
|
expected[tc.ID] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check following messages for matching tool results
|
||||||
|
toolMsgCount := 0
|
||||||
|
for j := i + 1; j < len(sanitized); j++ {
|
||||||
|
if sanitized[j].Role != "tool" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
toolMsgCount++
|
||||||
|
if _, exists := expected[sanitized[j].ToolCallID]; exists {
|
||||||
|
expected[sanitized[j].ToolCallID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any tool_call_id is missing, drop this assistant message and its partial tool messages
|
||||||
|
allFound := true
|
||||||
|
for toolCallID, found := range expected {
|
||||||
|
if !found {
|
||||||
|
allFound = false
|
||||||
|
logger.DebugCF(
|
||||||
|
"agent",
|
||||||
|
"Dropping assistant message with incomplete tool results",
|
||||||
|
map[string]any{
|
||||||
|
"missing_tool_call_id": toolCallID,
|
||||||
|
"expected_count": len(expected),
|
||||||
|
"found_count": toolMsgCount,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allFound {
|
||||||
|
// Skip this assistant message and its tool messages
|
||||||
|
i += toolMsgCount
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final = append(final, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return final
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cb *ContextBuilder) AddToolResult(
|
func (cb *ContextBuilder) AddToolResult(
|
||||||
|
|
|
||||||
|
|
@ -207,3 +207,77 @@ func assertRoles(t *testing.T, msgs []providers.Message, expected ...string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSanitizeHistoryForProvider_IncompleteToolResults tests the forward validation
|
||||||
|
// that ensures assistant messages with tool_calls have ALL matching tool results.
|
||||||
|
// This fixes the DeepSeek error: "An assistant message with 'tool_calls' must be
|
||||||
|
// followed by tool messages responding to each 'tool_call_id'."
|
||||||
|
func TestSanitizeHistoryForProvider_IncompleteToolResults(t *testing.T) {
|
||||||
|
// Assistant expects tool results for both A and B, but only A is present
|
||||||
|
history := []providers.Message{
|
||||||
|
msg("user", "do two things"),
|
||||||
|
assistantWithTools("A", "B"),
|
||||||
|
toolResult("A"),
|
||||||
|
// toolResult("B") is missing - this would cause DeepSeek to fail
|
||||||
|
msg("user", "next question"),
|
||||||
|
msg("assistant", "answer"),
|
||||||
|
}
|
||||||
|
|
||||||
|
result := sanitizeHistoryForProvider(history)
|
||||||
|
// The assistant message with incomplete tool results should be dropped,
|
||||||
|
// along with its partial tool result. The remaining messages are:
|
||||||
|
// user ("do two things"), user ("next question"), assistant ("answer")
|
||||||
|
if len(result) != 3 {
|
||||||
|
t.Fatalf("expected 3 messages, got %d: %+v", len(result), roles(result))
|
||||||
|
}
|
||||||
|
assertRoles(t, result, "user", "user", "assistant")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSanitizeHistoryForProvider_MissingAllToolResults tests the case where
|
||||||
|
// an assistant message has tool_calls but no tool results follow at all.
|
||||||
|
func TestSanitizeHistoryForProvider_MissingAllToolResults(t *testing.T) {
|
||||||
|
history := []providers.Message{
|
||||||
|
msg("user", "do something"),
|
||||||
|
assistantWithTools("A"),
|
||||||
|
// No tool results at all
|
||||||
|
msg("user", "hello"),
|
||||||
|
msg("assistant", "hi"),
|
||||||
|
}
|
||||||
|
|
||||||
|
result := sanitizeHistoryForProvider(history)
|
||||||
|
// The assistant message with no tool results should be dropped.
|
||||||
|
// Remaining: user ("do something"), user ("hello"), assistant ("hi")
|
||||||
|
if len(result) != 3 {
|
||||||
|
t.Fatalf("expected 3 messages, got %d: %+v", len(result), roles(result))
|
||||||
|
}
|
||||||
|
assertRoles(t, result, "user", "user", "assistant")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSanitizeHistoryForProvider_PartialToolResultsInMiddle tests that
|
||||||
|
// incomplete tool results in the middle of a conversation are properly handled.
|
||||||
|
func TestSanitizeHistoryForProvider_PartialToolResultsInMiddle(t *testing.T) {
|
||||||
|
history := []providers.Message{
|
||||||
|
msg("user", "first"),
|
||||||
|
assistantWithTools("A"),
|
||||||
|
toolResult("A"),
|
||||||
|
msg("assistant", "done"),
|
||||||
|
msg("user", "second"),
|
||||||
|
assistantWithTools("B", "C"),
|
||||||
|
toolResult("B"),
|
||||||
|
// toolResult("C") is missing
|
||||||
|
msg("user", "third"),
|
||||||
|
assistantWithTools("D"),
|
||||||
|
toolResult("D"),
|
||||||
|
msg("assistant", "all done"),
|
||||||
|
}
|
||||||
|
|
||||||
|
result := sanitizeHistoryForProvider(history)
|
||||||
|
// First round is complete (user, assistant+tools, tool, assistant),
|
||||||
|
// second round is incomplete and dropped (assistant+tools, partial tool),
|
||||||
|
// third round is complete (user, assistant+tools, tool, assistant).
|
||||||
|
// Remaining: user, assistant, tool, assistant, user, user, assistant, tool, assistant
|
||||||
|
if len(result) != 9 {
|
||||||
|
t.Fatalf("expected 9 messages, got %d: %+v", len(result), roles(result))
|
||||||
|
}
|
||||||
|
assertRoles(t, result, "user", "assistant", "tool", "assistant", "user", "user", "assistant", "tool", "assistant")
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue