Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

from typing_extensions import override

from ...tools import tool
from ...tools.registry import ToolRegistry
from ...types.content import Message
from ...types.exceptions import ContextWindowOverflowException
from .conversation_manager import ConversationManager
Expand All @@ -23,6 +25,10 @@
- You MUST create a structured and concise summary in bullet-point format.
- You MUST NOT respond conversationally.
- You MUST NOT address the user directly.
- You MUST NOT comment on tool availability.

Assumptions:
- You MUST NOT assume tool executions failed unless otherwise stated.

Task:
Your task is to create a structured summary document:
Expand Down Expand Up @@ -182,9 +188,10 @@ def _generate_summary(self, messages: List[Message], agent: "Agent") -> Message:
# Choose which agent to use for summarization
summarization_agent = self.summarization_agent if self.summarization_agent is not None else agent

# Save original system prompt and messages to restore later
# Save original system prompt, messages, and tool registry to restore later
original_system_prompt = summarization_agent.system_prompt
original_messages = summarization_agent.messages.copy()
original_tool_registry = summarization_agent.tool_registry

try:
# Only override system prompt if no agent was provided during initialization
Expand All @@ -197,6 +204,13 @@ def _generate_summary(self, messages: List[Message], agent: "Agent") -> Message:
)
# Temporarily set the system prompt for summarization
summarization_agent.system_prompt = system_prompt

# Add no-op tool if agent has no tools to satisfy tool spec requirement
if not summarization_agent.tool_names:
tool_registry = ToolRegistry()
tool_registry.register_tool(self._noop_tool)
summarization_agent.tool_registry = tool_registry

summarization_agent.messages = messages

# Use the agent to generate summary with rich content (can use tools if needed)
Expand All @@ -207,6 +221,7 @@ def _generate_summary(self, messages: List[Message], agent: "Agent") -> Message:
# Restore original agent state
summarization_agent.system_prompt = original_system_prompt
summarization_agent.messages = original_messages
summarization_agent.tool_registry = original_tool_registry

def _adjust_split_point_for_tool_pairs(self, messages: List[Message], split_point: int) -> int:
"""Adjust the split point to avoid breaking ToolUse/ToolResult pairs.
Expand Down Expand Up @@ -249,3 +264,13 @@ def _adjust_split_point_for_tool_pairs(self, messages: List[Message], split_poin
raise ContextWindowOverflowException("Unable to trim conversation context!")

return split_point

@tool(name="noop", description="MUST NOT call or summarize")
def _noop_tool(self) -> None:
"""No-op tool to satisfy tool spec requirement when tool messages are present.

Some model provides (e.g., Bedrock) will return an error response if tool uses and tool results are present in
messages without any tool specs configured. Consequently, if the summarization agent has no registered tools,
summarization will fail. As a workaround, we register the no-op tool.
"""
pass
29 changes: 29 additions & 0 deletions tests/strands/agent/test_summarizing_conversation_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ def __init__(self, summary_response="This is a summary of the conversation."):
self.messages = []
self.model = Mock()
self.call_tracker = Mock()
self.tool_registry = Mock()
self.tool_names = []

def __call__(self, prompt):
"""Mock agent call that returns a summary."""
Expand Down Expand Up @@ -608,3 +610,30 @@ def test_summarizing_conversation_manager_properly_records_removed_message_count
# so we dont count this toward the total:
# 4 (Previously removed messages) + 2 (removed messages) - 1 (Previous summary message) = 5
assert manager.removed_message_count == 5


@patch("strands.agent.conversation_manager.summarizing_conversation_manager.ToolRegistry")
def test_summarizing_conversation_manager_generate_summary_with_noop_tool(mock_registry_cls, summarizing_manager):
mock_registry = mock_registry_cls.return_value

messages = [{"role": "user", "content": [{"text": "test"}]}]
agent = create_mock_agent()

original_tool_registry = agent.tool_registry
summarizing_manager._generate_summary(messages, agent)

assert original_tool_registry == agent.tool_registry
mock_registry.register_tool.assert_called_once()


@patch("strands.agent.conversation_manager.summarizing_conversation_manager.ToolRegistry")
def test_summarizing_conversation_manager_generate_summary_with_tools(mock_registry_cls, summarizing_manager):
mock_registry = mock_registry_cls.return_value

messages = [{"role": "user", "content": [{"text": "test"}]}]
agent = create_mock_agent()
agent.tool_names = ["test_tool"]

summarizing_manager._generate_summary(messages, agent)

mock_registry.register_tool.assert_not_called()
36 changes: 36 additions & 0 deletions tests_integ/test_summarizing_conversation_manager_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,3 +372,39 @@ def test_dedicated_summarization_agent(model, summarization_model):
break

assert summary_text


def test_summarization_with_tool_messages_and_no_tools():
agent = Agent(
messages=[
{"role": "user", "content": [{"text": "What is the current time?"}]},
{
"role": "assistant",
"content": [{"toolUse": {"toolUseId": "t1", "name": "time_tool", "input": {}}}],
},
{
"role": "user",
"content": [
{
"toolResult": {
"toolUseId": "t1",
"content": [{"text": "12:00"}],
"status": "success",
}
}
],
},
{"role": "assistant", "content": [{"text": "The current time is 12:00."}]},
{"role": "user", "content": [{"text": "Thank you"}]},
{"role": "assistant", "content": [{"text": "You are welcome."}]},
],
)

conversation_manager = SummarizingConversationManager(summary_ratio=1, preserve_recent_messages=2)
conversation_manager.reduce_context(agent)

assert len(agent.tool_names) == 0
assert len(agent.messages) == 3

summary = str(agent.messages[0]).lower()
assert "12:00" in summary