diff --git a/backend/app/agent_runtime/memory.py b/backend/app/agent_runtime/memory.py index 88250c6..98cf030 100644 --- a/backend/app/agent_runtime/memory.py +++ b/backend/app/agent_runtime/memory.py @@ -514,6 +514,10 @@ class AgentMemory: def trim_messages(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """ 裁剪消息列表:保留最近的 N 条,但始终保留第一条 system 消息。 + + 同时保证 assistant(tool_calls) 与 tool 消息的配对完整性: + 如果裁剪边界落在 assistant(tool_calls) 和其 tool 结果之间, + 则向前扩展窗口包含该 assistant 消息,避免孤立的 tool 消息。 """ if len(messages) <= self.max_history: return messages @@ -521,7 +525,37 @@ class AgentMemory: system_msgs = [m for m in messages if m.get("role") == "system"] other_msgs = [m for m in messages if m.get("role") != "system"] - trimmed = other_msgs[-(self.max_history - len(system_msgs)):] + max_keep = max(1, self.max_history - len(system_msgs)) + start_idx = max(0, len(other_msgs) - max_keep) + + # 如果裁剪后第一条是 tool 消息,向前找到其父 assistant(tool_calls) + if start_idx > 0 and start_idx < len(other_msgs) and other_msgs[start_idx].get("role") == "tool": + # 收集从 start_idx 开始连续的所有 tool 消息 + tool_count = 0 + for i in range(start_idx, len(other_msgs)): + if other_msgs[i].get("role") == "tool": + tool_count += 1 + else: + break + # 向前查找对应的 assistant(tool_calls),一个 assistant 可包含多个 tool_calls + needed = tool_count + cursor = start_idx - 1 + while cursor >= 0 and needed > 0: + role = other_msgs[cursor].get("role") + if role == "assistant" and other_msgs[cursor].get("tool_calls"): + needed -= len(other_msgs[cursor]["tool_calls"]) + elif role == "user": + # 遇到 user 说明上一轮已结束,放弃扩展 + break + cursor -= 1 + if needed <= 0: + # 找到了所有父 assistant 消息,扩展窗口 + start_idx = cursor + 1 + + trimmed = other_msgs[start_idx:] + # 最终安全检查:移除开头仍存在的孤立 tool 消息 + while trimmed and trimmed[0].get("role") == "tool": + trimmed.pop(0) return system_msgs + trimmed @staticmethod