From f212b12d4d0fb36804bfe6f84eae1b9d10bb1692 Mon Sep 17 00:00:00 2001 From: liugangjian Date: Thu, 5 Mar 2026 09:52:31 +0800 Subject: [PATCH] fix: include tool messages in summary context (#778) Previously, the summarizeSession function only retained user and assistant messages when generating summaries, filtering out tool and function messages. This caused conversation summaries to contain only initial user messages because assistant responses with tool calls and tool results were excluded from the summary context. This change allows 'tool' and 'function' role messages to be included in the summarization process, providing complete conversation context during summarization and preventing the loss of important assistant responses and tool interactions. --- pkg/agent/loop.go | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index db9efa2cf..5426362c0 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -571,7 +571,7 @@ func (al *AgentLoop) processSystemMessage( UserMessage: fmt.Sprintf("[System: %s] %s", msg.SenderID, msg.Content), DefaultResponse: "Background task completed.", EnableSummary: false, - SendResponse: true, + SendResponse: false, // Prevent duplicate responses caused by system message processing }) } @@ -1157,8 +1157,8 @@ func (al *AgentLoop) forceCompression(agent *AgentInstance, sessionKey string) { newHistory = append(newHistory, history[len(history)-1]) // Last message // Update session - agent.Sessions.SetHistory(sessionKey, newHistory) - agent.Sessions.Save(sessionKey) + // Update session with sanitized history to ensure tool pair integrity + agent.Sessions.SetHistory(sessionKey, sanitizeToolPairs(newHistory)) logger.WarnCF("agent", "Forced compression executed", map[string]any{ "session_key": sessionKey, @@ -1275,7 +1275,7 @@ func (al *AgentLoop) summarizeSession(agent *AgentInstance, sessionKey string) { omitted := false for _, m := range toSummarize { - if m.Role != "user" && m.Role != "assistant" { + if m.Role != "user" && m.Role != "assistant" && m.Role != "tool" && m.Role != "function" { continue } msgTokens := len(m.Content) / 2 @@ -1502,3 +1502,40 @@ func extractParentPeer(msg bus.InboundMessage) *routing.RoutePeer { } return &routing.RoutePeer{Kind: parentKind, ID: parentID} } + +// sanitizeToolPairs removes malformed tool_call/tool_result pairs from message histories. It ensures +// that all tool_use IDs referenced in tool results have corresponding tool_use blocks in +// assistant messages, and removes orphaned tool_result messages that don't pair with a +// prior tool_call in the same session history. +func sanitizeToolPairs(messages []providers.Message) []providers.Message { + // Build map of expected tool call IDs + expectedToolCallIDs := make(map[string]bool) + var sanitized []providers.Message + + for _, msg := range messages { + if msg.Role == "assistant" && len(msg.ToolCalls) > 0 { + // Record all tool call IDs in assistant messages + for _, call := range msg.ToolCalls { + expectedToolCallIDs[call.ID] = true + } + sanitized = append(sanitized, msg) + } else if msg.Role == "tool" { + // Verify this tool result has a corresponding tool call + if expectedToolCallIDs[msg.ToolCallID] { + // Valid pairing: tool result has corresponding tool call + sanitized = append(sanitized, msg) + // Remove the ID after use to avoid reuse of stale ID + delete(expectedToolCallIDs, msg.ToolCallID) + } else { + // Invalid: orphaned tool result without matching tool call + // Skip this message rather than including it + continue + } + } else { + // Regular messages (user/assistant without tools/other) - always include + sanitized = append(sanitized, msg) + } + } + + return sanitized +}