55
66from typing_extensions import override
77
8+ from ..._async import run_async
9+ from ...event_loop .streaming import process_stream
810from ...tools ._tool_helpers import noop_tool
911from ...tools .registry import ToolRegistry
1012from ...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