From bc6a9cac2606bde868e9de9f7be08be07659ce4a Mon Sep 17 00:00:00 2001 From: mattie ruth backman Date: Thu, 30 Oct 2025 10:43:49 -0400 Subject: [PATCH] Add append_to_context boolean field to TextFrames This allows any given TextFrame to be marked in a way such that it does not get added to the context. Specifically, this fixes a problem with the new AggregatedTextFrames where we need to send LLM text both in an aggregated form as well as word-by-word but avoid duplicating the text in the context. --- src/pipecat/frames/frames.py | 3 +++ src/pipecat/processors/aggregators/llm_response.py | 3 ++- src/pipecat/processors/aggregators/llm_response_universal.py | 2 +- src/pipecat/services/tts_service.py | 4 +++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/pipecat/frames/frames.py b/src/pipecat/frames/frames.py index 13bf2eaa7..0fe41240a 100644 --- a/src/pipecat/frames/frames.py +++ b/src/pipecat/frames/frames.py @@ -337,11 +337,14 @@ class TextFrame(DataFrame): # mandatory fields of theirs to have defaults to preserve # non-default-before-default argument order) includes_inter_frame_spaces: bool = field(init=False) + # Whether this text frame should be appended to the LLM context. + append_to_context: bool = field(init=False) def __post_init__(self): super().__post_init__() self.skip_tts = False self.includes_inter_frame_spaces = False + self.append_to_context = True def __str__(self): pts = format_pts(self.pts) diff --git a/src/pipecat/processors/aggregators/llm_response.py b/src/pipecat/processors/aggregators/llm_response.py index afc091d5a..2b1fb0894 100644 --- a/src/pipecat/processors/aggregators/llm_response.py +++ b/src/pipecat/processors/aggregators/llm_response.py @@ -22,6 +22,7 @@ from pipecat.audio.interruptions.base_interruption_strategy import BaseInterrupt from pipecat.audio.turn.smart_turn.base_smart_turn import SmartTurnParams from pipecat.audio.vad.vad_analyzer import VADParams from pipecat.frames.frames import ( + AggregatedLLMTextFrame, BotStartedSpeakingFrame, BotStoppedSpeakingFrame, CancelFrame, @@ -1001,7 +1002,7 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator): await self.push_aggregation() async def _handle_text(self, frame: TextFrame): - if not self._started: + if not self._started or not frame.append_to_context: return if self._params.expect_stripped_words: diff --git a/src/pipecat/processors/aggregators/llm_response_universal.py b/src/pipecat/processors/aggregators/llm_response_universal.py index d4d9ad7da..460b00288 100644 --- a/src/pipecat/processors/aggregators/llm_response_universal.py +++ b/src/pipecat/processors/aggregators/llm_response_universal.py @@ -814,7 +814,7 @@ class LLMAssistantAggregator(LLMContextAggregator): await self.push_aggregation() async def _handle_text(self, frame: TextFrame): - if not self._started: + if not self._started or not frame.append_to_context: return # Make sure we really have text (spaces count, too!) diff --git a/src/pipecat/services/tts_service.py b/src/pipecat/services/tts_service.py index 93276254a..88d0c8847 100644 --- a/src/pipecat/services/tts_service.py +++ b/src/pipecat/services/tts_service.py @@ -523,7 +523,9 @@ class TTSService(AIService): # is being spoken. Here, we assume this flag is used when the TTS # provider supports word timestamps and the TTSTextFrames will be # generated in the word_task_handler. - await self.push_frame(AggregatedLLMTextFrame(text, aggregated_by=aggregated_by)) + frame = AggregatedLLMTextFrame(text, aggregated_by=aggregated_by) + frame.append_to_context = False + await self.push_frame(frame) await self.process_generator(self.run_tts(text)) await self.stop_processing_metrics()