Merge pull request #3794 from omChauhanDev/fix/context-summarization-llm-specific-message

skipping provider-specific messages during summarization
This commit is contained in:
Filipi da Silva Fuchter
2026-02-27 10:57:34 -05:00
committed by GitHub
4 changed files with 117 additions and 7 deletions

View File

@@ -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 :]

View File

@@ -15,7 +15,7 @@ from typing import List, Optional
from loguru import logger
from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.aggregators.llm_context import LLMContext, LLMSpecificMessage
# Token estimation constants
CHARS_PER_TOKEN = 4 # Industry-standard heuristic: 1 token ≈ 4 characters
@@ -188,6 +188,11 @@ 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
# Role and structure overhead
total += TOKEN_OVERHEAD_PER_MESSAGE
@@ -248,6 +253,12 @@ 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
role = msg.get("role")
# Check for tool calls in assistant messages
@@ -296,9 +307,17 @@ 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 for i, msg in enumerate(messages) if msg.get("role") == "system"), -1
(
i
for i, msg in enumerate(messages)
if not isinstance(msg, LLMSpecificMessage) and msg.get("role") == "system"
),
-1,
)
# Messages to summarize are between first system and recent messages
@@ -356,6 +375,14 @@ 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
role = msg.get("role", "unknown")
content = msg.get("content", "")