From a3962e3b4749e86fae02aeb363336ea134732c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleix=20Conchillo=20Flaqu=C3=A9?= Date: Mon, 1 Dec 2025 12:26:32 -0800 Subject: [PATCH] LLMTextFrame: don't override skip_tts --- CHANGELOG.md | 3 +++ src/pipecat/frames/frames.py | 12 ++++++------ .../processors/aggregators/llm_text_processor.py | 2 +- src/pipecat/services/llm_service.py | 5 +++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3819cb5e3..bb279ea98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fixed an issue where `LLMTextFrame.skip_tts` was being overwritten by LLM + services. + - Fixed an issue in `SarvamTTSService` where the last sentence was not being spoken. Now, audio is flushed when the TTS services receives the `LLMFullResponseEndFrame` or `EndFrame`. diff --git a/src/pipecat/frames/frames.py b/src/pipecat/frames/frames.py index 60530e39a..488685fc3 100644 --- a/src/pipecat/frames/frames.py +++ b/src/pipecat/frames/frames.py @@ -330,7 +330,7 @@ class TextFrame(DataFrame): """ text: str - skip_tts: bool = field(init=False) + skip_tts: Optional[bool] = field(init=False) # Whether any necessary inter-frame (leading/trailing) spaces are already # included in the text. # NOTE: Ideally this would be available at init time with a default value, @@ -343,7 +343,7 @@ class TextFrame(DataFrame): def __post_init__(self): super().__post_init__() - self.skip_tts = False + self.skip_tts = None self.includes_inter_frame_spaces = False self.append_to_context = True @@ -1632,22 +1632,22 @@ class LLMFullResponseStartFrame(ControlFrame): more TextFrames and a final LLMFullResponseEndFrame. """ - skip_tts: bool = field(init=False) + skip_tts: Optional[bool] = field(init=False) def __post_init__(self): super().__post_init__() - self.skip_tts = False + self.skip_tts = None @dataclass class LLMFullResponseEndFrame(ControlFrame): """Frame indicating the end of an LLM response.""" - skip_tts: bool = field(init=False) + skip_tts: Optional[bool] = field(init=False) def __post_init__(self): super().__post_init__() - self.skip_tts = False + self.skip_tts = None @dataclass diff --git a/src/pipecat/processors/aggregators/llm_text_processor.py b/src/pipecat/processors/aggregators/llm_text_processor.py index 44a8dc24e..0d9636fd8 100644 --- a/src/pipecat/processors/aggregators/llm_text_processor.py +++ b/src/pipecat/processors/aggregators/llm_text_processor.py @@ -92,7 +92,7 @@ class LLMTextProcessor(FrameProcessor): out_frame.skip_tts = in_frame.skip_tts await self.push_frame(out_frame) - async def _handle_llm_end(self, skip_tts: bool = False): + async def _handle_llm_end(self, skip_tts: Optional[bool] = None): # Flush any remaining aggregated text at the end of the LLM response aggregation = self._text_aggregator.text await self._text_aggregator.reset() diff --git a/src/pipecat/services/llm_service.py b/src/pipecat/services/llm_service.py index 9bc72b0f8..d82d6020c 100644 --- a/src/pipecat/services/llm_service.py +++ b/src/pipecat/services/llm_service.py @@ -186,7 +186,7 @@ class LLMService(AIService): self._function_call_tasks: Dict[Optional[asyncio.Task], FunctionCallRunnerItem] = {} self._sequential_runner_task: Optional[asyncio.Task] = None self._tracing_enabled: bool = False - self._skip_tts: bool = False + self._skip_tts: Optional[bool] = None self._register_event_handler("on_function_calls_started") self._register_event_handler("on_completion_timeout") @@ -297,7 +297,8 @@ class LLMService(AIService): direction: The direction of frame pushing. """ if isinstance(frame, (LLMTextFrame, LLMFullResponseStartFrame, LLMFullResponseEndFrame)): - frame.skip_tts = self._skip_tts + if self._skip_tts is not None: + frame.skip_tts = self._skip_tts await super().push_frame(frame, direction)