From 1c6a1900464a73d5dee801b038f08415924393c0 Mon Sep 17 00:00:00 2001 From: Joao Moura Date: Thu, 14 May 2026 17:01:36 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20revert=20summary=20tag=20handling=20?= =?UTF-8?q?=E2=80=94=20strip=20instead=20of=20extract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "extract inner content" approach broke agents badly: after context summarization, the LLM sometimes echoes the block from the summary user message. Extracting the inner content treated that echo as the "real response", causing the agent to dump its entire system prompt and conversation history to the user. Reverted to the original stripping approach: blocks are removed from the response. Also reverted the streaming filter from the preflight-buffer design (which broke streaming for non-summary models by accumulating everything until task end) back to the simple character-level suppression filter that streams normally and only suppresses ... spans. Co-Authored-By: Claude Opus 4.6 --- lib/crewai/src/crewai/new_agent/executor.py | 99 ++++++--------------- 1 file changed, 25 insertions(+), 74 deletions(-) diff --git a/lib/crewai/src/crewai/new_agent/executor.py b/lib/crewai/src/crewai/new_agent/executor.py index 640d32e1b..35b6c03dd 100644 --- a/lib/crewai/src/crewai/new_agent/executor.py +++ b/lib/crewai/src/crewai/new_agent/executor.py @@ -625,22 +625,14 @@ class ConversationalAgentExecutor(BaseModel): pass return "" - _SUMMARY_EXTRACT_RE = re.compile( - r"\s*(.*?)\s*", re.DOTALL + _INTERNAL_TAG_RE = re.compile( + r".*?", re.DOTALL ) def _strip_internal_tags(self, text: str) -> str: - """Handle tags leaked from the summarization prompt. - - Some models (e.g. gpt-4.1) wrap their actual response in - tags, treating the preceding text as chain-of-thought. When summary - tags are present we extract the inner content as the real answer. - """ - match = self._SUMMARY_EXTRACT_RE.search(text) - if match: - inner = match.group(1).strip() - return inner if inner else text - return text + """Strip blocks that leak from the summarization prompt.""" + cleaned = self._INTERNAL_TAG_RE.sub("", text).strip() + return cleaned if cleaned else text def _detect_artifacts(self, tool_name: str, result_str: str) -> list[Artifact]: """GAP-67: Detect artifacts from tool results. @@ -2413,77 +2405,38 @@ class ConversationalAgentExecutor(BaseModel): invoke_task = asyncio.create_task(self.ainvoke(user_message)) _streamed_chars = 0 _last_status_time = time.monotonic() - - # States: "preflight" (buffering, no seen yet), - # "inside" ( found, yielding inner content), - # "done" ( seen, suppress the rest), - # "passthrough" (no summary tags — yield everything) - _state = "preflight" - _preflight_buf: list[str] = [] _tag_buf = "" + _suppressing = False def _filter_chunk(raw: str) -> str: - nonlocal _state, _tag_buf - out: list[str] = [] + """Suppress ... blocks from streamed chunks.""" + nonlocal _tag_buf, _suppressing + out = [] for ch in raw: - if _state == "done": - break - - if _state == "passthrough": - out.append(ch) + if _suppressing: + _tag_buf += ch + if _tag_buf.endswith(""): + _suppressing = False + _tag_buf = "" continue - - # Accumulate into tag buffer when we might be mid-tag if _tag_buf: _tag_buf += ch - - if _state == "preflight": - target = "" - if target[: len(_tag_buf)] == _tag_buf: - if _tag_buf == target: - _state = "inside" - _preflight_buf.clear() - _tag_buf = "" + if len(_tag_buf) <= len(""): + if ""[: len(_tag_buf)] == _tag_buf: + if _tag_buf == "": + _suppressing = True continue - # Not a match — dump tag_buf to preflight buffer - _preflight_buf.append(_tag_buf) - _tag_buf = "" - - elif _state == "inside": - target = "" - if target[: len(_tag_buf)] == _tag_buf: - if _tag_buf == target: - _state = "done" - _tag_buf = "" - continue - # Not a closing tag — flush buffered chars out.append(_tag_buf) _tag_buf = "" - continue - - if ch == "<": + else: + out.append(_tag_buf) + _tag_buf = "" + elif ch == "<": _tag_buf = ch - continue - - if _state == "preflight": - _preflight_buf.append(ch) - elif _state == "inside": + else: out.append(ch) - return "".join(out) - def _flush_preflight() -> str: - """If we never saw , flush buffered text.""" - nonlocal _state - if _state == "preflight": - _state = "passthrough" - result = "".join(_preflight_buf) - _preflight_buf.clear() - if _tag_buf: - result += _tag_buf - return result - return "" - try: while not invoke_task.done(): try: @@ -2507,10 +2460,8 @@ class ConversationalAgentExecutor(BaseModel): _streamed_chars += len(filtered) yield filtered - leftover = _flush_preflight() - if leftover: - _streamed_chars += len(leftover) - yield leftover + if _tag_buf and not _suppressing: + yield _tag_buf result = invoke_task.result() self._last_stream_result = result