Skip to content

Commit 18a349c

Browse files
authored
fix(summarizing_conversation_manager): use model stream to generate summary (#1653)
1 parent 3348099 commit 18a349c

2 files changed

Lines changed: 256 additions & 59 deletions

File tree

src/strands/agent/conversation_manager/summarizing_conversation_manager.py

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
from typing_extensions import override
77

8+
from ..._async import run_async
9+
from ...event_loop.streaming import process_stream
810
from ...tools._tool_helpers import noop_tool
911
from ...tools.registry import ToolRegistry
1012
from ...types.content import Message
@@ -176,36 +178,50 @@ def reduce_context(self, agent: "Agent", e: Exception | None = None, **kwargs: A
176178
def _generate_summary(self, messages: list[Message], agent: "Agent") -> Message:
177179
"""Generate a summary of the provided messages.
178180
181+
When a dedicated summarization_agent was provided at init time, it is invoked as before
182+
(full agent pipeline, tool execution, etc.).
183+
184+
In the default case (no summarization_agent), the parent agent's *model* is called
185+
directly via ``model.stream()``. This avoids re-entering the agent pipeline which
186+
would deadlock on ``_invocation_lock`` and corrupt metrics / traces / interrupt state.
187+
179188
Args:
180189
messages: The messages to summarize.
181-
agent: The agent instance to use for summarization.
190+
agent: The agent instance whose model will be used for summarization when no
191+
dedicated summarization_agent was configured.
182192
183193
Returns:
184194
A message containing the conversation summary.
185195
186196
Raises:
187197
Exception: If summary generation fails.
188198
"""
189-
# Choose which agent to use for summarization
190-
summarization_agent = self.summarization_agent if self.summarization_agent is not None else agent
199+
if self.summarization_agent is not None:
200+
return self._generate_summary_with_agent(messages)
201+
202+
return self._generate_summary_with_model(messages, agent)
203+
204+
# ------------------------------------------------------------------
205+
# Path 1 – dedicated summarization agent (backward-compatible)
206+
# ------------------------------------------------------------------
207+
208+
def _generate_summary_with_agent(self, messages: list[Message]) -> Message:
209+
"""Generate a summary using the dedicated summarization agent.
210+
211+
Args:
212+
messages: The messages to summarize.
213+
214+
Returns:
215+
A message containing the conversation summary.
216+
"""
217+
summarization_agent = self.summarization_agent
218+
assert summarization_agent is not None # guaranteed by caller
191219

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

197224
try:
198-
# Only override system prompt if no agent was provided during initialization
199-
if self.summarization_agent is None:
200-
# Use custom system prompt if provided, otherwise use default
201-
system_prompt = (
202-
self.summarization_system_prompt
203-
if self.summarization_system_prompt is not None
204-
else DEFAULT_SUMMARIZATION_PROMPT
205-
)
206-
# Temporarily set the system prompt for summarization
207-
summarization_agent.system_prompt = system_prompt
208-
209225
# Add no-op tool if agent has no tools to satisfy tool spec requirement
210226
if not summarization_agent.tool_names:
211227
tool_registry = ToolRegistry()
@@ -214,16 +230,61 @@ def _generate_summary(self, messages: list[Message], agent: "Agent") -> Message:
214230

215231
summarization_agent.messages = messages
216232

217-
# Use the agent to generate summary with rich content (can use tools if needed)
218233
result = summarization_agent("Please summarize this conversation.")
219234
return cast(Message, {**result.message, "role": "user"})
220235

221236
finally:
222-
# Restore original agent state
223237
summarization_agent.system_prompt = original_system_prompt
224238
summarization_agent.messages = original_messages
225239
summarization_agent.tool_registry = original_tool_registry
226240

241+
# ------------------------------------------------------------------
242+
# Path 2 – default case: call model.stream() directly
243+
# ------------------------------------------------------------------
244+
245+
def _generate_summary_with_model(self, messages: list[Message], agent: "Agent") -> Message:
246+
"""Generate a summary by calling the agent's model directly.
247+
248+
This bypasses the full agent pipeline (lock, metrics, traces, tool loop) and
249+
simply asks the underlying model to summarize the conversation.
250+
251+
Args:
252+
messages: The messages to summarize.
253+
agent: The parent agent whose model is used.
254+
255+
Returns:
256+
A message containing the conversation summary.
257+
"""
258+
system_prompt = (
259+
self.summarization_system_prompt
260+
if self.summarization_system_prompt is not None
261+
else DEFAULT_SUMMARIZATION_PROMPT
262+
)
263+
264+
# Build the message list: conversation history + summarization request
265+
summarization_messages = list(messages) + [
266+
{"role": "user", "content": [{"text": "Please summarize this conversation."}]}
267+
]
268+
269+
async def _call_model() -> Message:
270+
chunks = agent.model.stream(
271+
summarization_messages,
272+
tool_specs=None,
273+
system_prompt=system_prompt,
274+
)
275+
276+
result_message: Message | None = None
277+
async for event in process_stream(chunks):
278+
if "stop" in event:
279+
_, result_message, _, _ = event["stop"]
280+
281+
if result_message is None:
282+
raise RuntimeError("Failed to generate summary: no response from model")
283+
return result_message
284+
285+
message = run_async(_call_model)
286+
return cast(Message, {**message, "role": "user"})
287+
227288
def _adjust_split_point_for_tool_pairs(self, messages: list[Message], split_point: int) -> int:
228289
"""Adjust the split point to avoid breaking ToolUse/ToolResult pairs.
229290

0 commit comments

Comments
 (0)