From aa6d3b38b38793f948c1aa8ac5fb3b1b8d644e5d Mon Sep 17 00:00:00 2001 From: filipi87 Date: Fri, 27 Feb 2026 12:53:25 -0300 Subject: [PATCH] Add explanatory comments for LLMSpecificMessage guards in context summarization, amd fixed the missing guard in LLMContextSummarizer._apply_summary when searching for the first system message. --- .../aggregators/llm_context_summarizer.py | 16 +++++++++++++--- .../utils/context/llm_context_summarization.py | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/pipecat/processors/aggregators/llm_context_summarizer.py b/src/pipecat/processors/aggregators/llm_context_summarizer.py index a1a613ccc..7886fcf12 100644 --- a/src/pipecat/processors/aggregators/llm_context_summarizer.py +++ b/src/pipecat/processors/aggregators/llm_context_summarizer.py @@ -18,7 +18,7 @@ from pipecat.frames.frames import ( LLMContextSummaryResultFrame, LLMFullResponseStartFrame, ) -from pipecat.processors.aggregators.llm_context import LLMContext +from pipecat.processors.aggregators.llm_context import LLMContext, LLMSpecificMessage from pipecat.utils.asyncio.task_manager import BaseTaskManager from pipecat.utils.base_object import BaseObject from pipecat.utils.context.llm_context_summarization import ( @@ -290,8 +290,18 @@ class LLMContextSummarizer(BaseObject): """ messages = self._context.messages - # Find the first system message to preserve - first_system_msg = next((msg for msg in messages if msg.get("role") == "system"), None) + # Find the first system message to preserve. LLMSpecificMessage instances are excluded + # because they are not dict-like and never represent a system message; they hold + # service-specific metadata (e.g. thinking blocks) that is always paired with a + # standard message. + first_system_msg = next( + ( + msg + for msg in messages + if not isinstance(msg, LLMSpecificMessage) and msg.get("role") == "system" + ), + None, + ) # Get recent messages to keep recent_messages = messages[last_summarized_index + 1 :] diff --git a/src/pipecat/utils/context/llm_context_summarization.py b/src/pipecat/utils/context/llm_context_summarization.py index 06551e3bb..537cc91ab 100644 --- a/src/pipecat/utils/context/llm_context_summarization.py +++ b/src/pipecat/utils/context/llm_context_summarization.py @@ -188,6 +188,8 @@ class LLMContextSummarizationUtil: total = 0 for message in context.messages: + # LLMSpecificMessage holds service-specific data (e.g. thinking blocks, + # thought signatures). Skipping them here for now. if isinstance(message, LLMSpecificMessage): continue @@ -251,6 +253,9 @@ class LLMContextSummarizationUtil: for i in range(start_idx, len(messages)): msg = messages[i] + # LLMSpecificMessage instances (e.g. thinking blocks) never carry tool_call or + # tool_call_id fields, so they cannot affect the pending-call tracking. Skipping + # them avoids an AttributeError. if isinstance(msg, LLMSpecificMessage): continue @@ -302,7 +307,10 @@ class LLMContextSummarizationUtil: if len(messages) <= min_messages_to_keep: return LLMMessagesToSummarize(messages=[], last_summarized_index=-1) - # Find first system message index + # Find first system message index. LLMSpecificMessage instances are excluded because + # they are not dict-like and never represent a system message; they hold + # service-specific metadata (e.g. thinking blocks) that is always paired with a + # standard message. first_system_index = next( ( i @@ -367,6 +375,11 @@ class LLMContextSummarizationUtil: transcript_parts = [] for msg in messages: + # LLMSpecificMessage holds service-specific internal data (e.g. Anthropic thinking + # blocks, Gemini thought signatures). This data is not meaningful as plain text for + # a summarization transcript, and the summarizer LLM would not know how to interpret + # it. The conversational content of those turns is already captured by the + # accompanying standard assistant message. if isinstance(msg, LLMSpecificMessage): continue