diff --git a/run_agent.py b/run_agent.py index a2353c053..da8c8293b 100644 --- a/run_agent.py +++ b/run_agent.py @@ -670,6 +670,9 @@ class AIAgent: # Internal stream callback (set during streaming TTS). # Initialized here so _vprint can reference it before run_conversation. self._stream_callback = None + # Deferred paragraph break flag — set after tool iterations so a + # single "\n\n" is prepended to the next real text delta. + self._stream_needs_break = False # Optional current-turn user-message override used when the API-facing # user message intentionally differs from the persisted transcript @@ -3471,6 +3474,13 @@ class AIAgent: def _fire_stream_delta(self, text: str) -> None: """Fire all registered stream delta callbacks (display + TTS).""" + # If a tool iteration set the break flag, prepend a single paragraph + # break before the first real text delta. This prevents the original + # problem (text concatenation across tool boundaries) without stacking + # blank lines when multiple tool iterations run back-to-back. + if getattr(self, "_stream_needs_break", False) and text and text.strip(): + self._stream_needs_break = False + text = "\n\n" + text for cb in (self.stream_delta_callback, self._stream_callback): if cb is not None: try: @@ -6756,8 +6766,13 @@ class AIAgent: _msg_count_before_tools = len(messages) self._execute_tool_calls(assistant_message, messages, effective_task_id, api_call_count) - # Signal iteration boundary to stream consumers to prevent text concatenation - self._fire_stream_delta("\n\n") + # Signal that a paragraph break is needed before the next + # streamed text. We don't emit it immediately because + # multiple consecutive tool iterations would stack up + # redundant blank lines. Instead, _fire_stream_delta() + # will prepend a single "\n\n" the next time real text + # arrives. + self._stream_needs_break = True # Refund the iteration if the ONLY tool(s) called were # execute_code (programmatic tool calling). These are