diff --git a/src/strands/agent/conversation_manager/sliding_window_conversation_manager.py b/src/strands/agent/conversation_manager/sliding_window_conversation_manager.py index f4c75cf1a..caaab4676 100644 --- a/src/strands/agent/conversation_manager/sliding_window_conversation_manager.py +++ b/src/strands/agent/conversation_manager/sliding_window_conversation_manager.py @@ -58,13 +58,8 @@ def __init__(self, window_size: int = 40, should_truncate_results: bool = True): def apply_management(self, agent: "Agent", **kwargs: Any) -> None: """Apply the sliding window to the agent's messages array to maintain a manageable history size. - This method is called after every event loop cycle, as the messages array may have been modified with tool - results and assistant responses. It first removes any dangling messages that might create an invalid - conversation state, then applies the sliding window if the message count exceeds the window size. - - Special handling is implemented to ensure we don't leave a user message with toolResult - as the first message in the array. It also ensures that all toolUse blocks have corresponding toolResult - blocks to maintain conversation coherence. + This method is called after every event loop cycle to apply a sliding window if the message count + exceeds the window size. Args: agent: The agent whose messages will be managed. @@ -72,7 +67,6 @@ def apply_management(self, agent: "Agent", **kwargs: Any) -> None: **kwargs: Additional keyword arguments for future extensibility. """ messages = agent.messages - self._remove_dangling_messages(messages) if len(messages) <= self.window_size: logger.debug( @@ -81,37 +75,6 @@ def apply_management(self, agent: "Agent", **kwargs: Any) -> None: return self.reduce_context(agent) - def _remove_dangling_messages(self, messages: Messages) -> None: - """Remove dangling messages that would create an invalid conversation state. - - After the event loop cycle is executed, we expect the messages array to end with either an assistant tool use - request followed by the pairing user tool result or an assistant response with no tool use request. If the - event loop cycle fails, we may end up in an invalid message state, and so this method will remove problematic - messages from the end of the array. - - This method handles two specific cases: - - - User with no tool result: Indicates that event loop failed to generate an assistant tool use request - - Assistant with tool use request: Indicates that event loop failed to generate a pairing user tool result - - Args: - messages: The messages to clean up. - This list is modified in-place. - """ - # remove any dangling user messages with no ToolResult - if len(messages) > 0 and is_user_message(messages[-1]): - if not any("toolResult" in content for content in messages[-1]["content"]): - messages.pop() - - # remove any dangling assistant messages with ToolUse - if len(messages) > 0 and is_assistant_message(messages[-1]): - if any("toolUse" in content for content in messages[-1]["content"]): - messages.pop() - # remove remaining dangling user messages with no ToolResult after we popped off an assistant message - if len(messages) > 0 and is_user_message(messages[-1]): - if not any("toolResult" in content for content in messages[-1]["content"]): - messages.pop() - def reduce_context(self, agent: "Agent", e: Optional[Exception] = None, **kwargs: Any) -> None: """Trim the oldest messages to reduce the conversation context size. diff --git a/tests/strands/agent/test_conversation_manager.py b/tests/strands/agent/test_conversation_manager.py index 7d43199e8..db2e2cfb3 100644 --- a/tests/strands/agent/test_conversation_manager.py +++ b/tests/strands/agent/test_conversation_manager.py @@ -58,21 +58,21 @@ def conversation_manager(request): {"role": "user", "content": [{"toolResult": {"toolUseId": "123", "content": [], "status": "success"}}]}, ], ), - # 2 - Remove dangling user message with no tool result + # 2 - Keep user message ( {"window_size": 2}, [ {"role": "user", "content": [{"text": "Hello"}]}, ], - [], + [{"role": "user", "content": [{"text": "Hello"}]}], ), - # 3 - Remove dangling assistant message with tool use + # 3 - Keep dangling assistant message with tool use ( {"window_size": 3}, [ {"role": "assistant", "content": [{"toolUse": {"toolUseId": "123", "name": "tool1", "input": {}}}]}, ], - [], + [{"role": "assistant", "content": [{"toolUse": {"toolUseId": "123", "name": "tool1", "input": {}}}]}], ), # 4 - Remove dangling assistant message with tool use - User tool result remains ( @@ -83,6 +83,7 @@ def conversation_manager(request): ], [ {"role": "user", "content": [{"toolResult": {"toolUseId": "123", "content": [], "status": "success"}}]}, + {"role": "assistant", "content": [{"toolUse": {"toolUseId": "123", "name": "tool1", "input": {}}}]}, ], ), # 5 - Remove dangling assistant message with tool use and user message without tool result @@ -95,8 +96,9 @@ def conversation_manager(request): {"role": "assistant", "content": [{"toolUse": {"toolUseId": "123", "name": "tool1", "input": {}}}]}, ], [ - {"role": "user", "content": [{"text": "First"}]}, {"role": "assistant", "content": [{"text": "First response"}]}, + {"role": "user", "content": [{"text": "Use a tool"}]}, + {"role": "assistant", "content": [{"toolUse": {"toolUseId": "123", "name": "tool1", "input": {}}}]}, ], ), # 6 - Message count above max window size - Basic drop