From e74930b9545178c60931e0eb0d4d0a95356734d3 Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Tue, 31 Mar 2026 19:12:33 -0400 Subject: [PATCH] Remove deprecated text_aggregator and text_filter params from TTS Remove the deprecated text_aggregator parameter from TTSService, CartesiaTTSService, and RimeTTSService, and the deprecated text_filter parameter from TTSService. Users should use LLMTextProcessor before the TTS service instead. Update the voice-switching example to use LLMTextProcessor with PatternPairAggregator. --- .../features-pattern-pair-voice-switching.py | 28 ++++++++------ src/pipecat/services/cartesia/tts.py | 25 ++++--------- src/pipecat/services/rime/tts.py | 24 ++++-------- src/pipecat/services/tts_service.py | 37 +------------------ 4 files changed, 34 insertions(+), 80 deletions(-) diff --git a/examples/features/features-pattern-pair-voice-switching.py b/examples/features/features-pattern-pair-voice-switching.py index c246ce599..38926d915 100644 --- a/examples/features/features-pattern-pair-voice-switching.py +++ b/examples/features/features-pattern-pair-voice-switching.py @@ -45,7 +45,7 @@ from dotenv import load_dotenv from loguru import logger from pipecat.audio.vad.silero import SileroVADAnalyzer -from pipecat.frames.frames import LLMRunFrame +from pipecat.frames.frames import LLMRunFrame, TTSUpdateSettingsFrame from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.task import PipelineParams, PipelineTask @@ -54,6 +54,7 @@ from pipecat.processors.aggregators.llm_response_universal import ( LLMContextAggregatorPair, LLMUserAggregatorParams, ) +from pipecat.processors.aggregators.llm_text_processor import LLMTextProcessor from pipecat.runner.types import RunnerArguments from pipecat.runner.utils import create_transport from pipecat.services.cartesia.tts import CartesiaTTSService @@ -100,39 +101,43 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): logger.info(f"Starting bot") # Create pattern pair aggregator for voice switching - pattern_aggregator = PatternPairAggregator() + llm_text_aggregator = PatternPairAggregator() # Add pattern for voice switching - pattern_aggregator.add_pattern( + llm_text_aggregator.add_pattern( type="voice", start_pattern="", end_pattern="", - action=MatchAction.REMOVE, # Remove tags from final text + action=MatchAction.AGGREGATE, ) # Register handler for voice switching async def on_voice_tag(match: PatternMatch): voice_name = match.text.strip().lower() if voice_name in VOICE_IDS: - # First flush any existing audio to finish the current context - await tts.flush_audio() - # Then set the new voice - await tts.set_voice(VOICE_IDS[voice_name]) + await llm_text_processor.push_frame( + TTSUpdateSettingsFrame( + delta=CartesiaTTSService.Settings(voice=VOICE_IDS[voice_name]) + ) + ) logger.info(f"Switched to {voice_name} voice") else: logger.warning(f"Unknown voice: {voice_name}") - pattern_aggregator.on_pattern_match("voice", on_voice_tag) + llm_text_aggregator.on_pattern_match("voice", on_voice_tag) stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY")) + # Process LLM text through the pattern aggregator before TTS + llm_text_processor = LLMTextProcessor(text_aggregator=llm_text_aggregator) + # Initialize TTS with narrator voice as default tts = CartesiaTTSService( api_key=os.getenv("CARTESIA_API_KEY"), settings=CartesiaTTSService.Settings( voice=VOICE_IDS["narrator"], ), - text_aggregator=pattern_aggregator, + skip_aggregator_types=["voice"], # Skip voice tags in TTS speech ) # System prompt for storytelling with voice switching @@ -204,7 +209,8 @@ Remember: Use narrator voice for EVERYTHING except the actual quoted dialogue."" stt, user_aggregator, llm, - tts, # TTS with pattern aggregator + llm_text_processor, + tts, transport.output(), assistant_aggregator, ] diff --git a/src/pipecat/services/cartesia/tts.py b/src/pipecat/services/cartesia/tts.py index d879ff694..d6202cef7 100644 --- a/src/pipecat/services/cartesia/tts.py +++ b/src/pipecat/services/cartesia/tts.py @@ -28,7 +28,6 @@ from pipecat.frames.frames import ( from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven from pipecat.services.tts_service import TextAggregationMode, TTSService, WebsocketTTSService from pipecat.transcriptions.language import Language, resolve_language -from pipecat.utils.text.base_text_aggregator import BaseTextAggregator from pipecat.utils.text.skip_tags_aggregator import SkipTagsAggregator from pipecat.utils.tracing.service_decorators import traced_tts @@ -240,7 +239,6 @@ class CartesiaTTSService(WebsocketTTSService): container: str = "raw", params: Optional[InputParams] = None, settings: Optional[Settings] = None, - text_aggregator: Optional[BaseTextAggregator] = None, text_aggregation_mode: Optional[TextAggregationMode] = None, aggregate_sentences: Optional[bool] = None, **kwargs, @@ -271,11 +269,6 @@ class CartesiaTTSService(WebsocketTTSService): settings: Runtime-updatable settings. When provided alongside deprecated parameters, ``settings`` values take precedence. - text_aggregator: Custom text aggregator for processing input text. - - .. deprecated:: 0.0.95 - Use an LLMTextProcessor before the TTSService for custom text aggregation. - text_aggregation_mode: How to aggregate incoming text before synthesis. aggregate_sentences: Whether to aggregate sentences within the TTSService. @@ -337,20 +330,18 @@ class CartesiaTTSService(WebsocketTTSService): pause_frame_processing=False, sample_rate=sample_rate, push_start_frame=True, - text_aggregator=text_aggregator, settings=default_settings, **kwargs, ) - if not text_aggregator: - # Always skip tags added for spelled-out text - # Note: This is primarily to support backwards compatibility. - # The preferred way of taking advantage of Cartesia SSML Tags is - # to use an LLMTextProcessor and/or a text_transformer to identify - # and insert these tags for the purpose of the TTS service alone. - self._text_aggregator = SkipTagsAggregator( - [("", "")], aggregation_type=self._text_aggregation_mode - ) + # Always skip tags added for spelled-out text + # Note: This is primarily to support backwards compatibility. + # The preferred way of taking advantage of Cartesia SSML Tags is + # to use an LLMTextProcessor and/or a text_transformer to identify + # and insert these tags for the purpose of the TTS service alone. + self._text_aggregator = SkipTagsAggregator( + [("", "")], aggregation_type=self._text_aggregation_mode + ) self._api_key = api_key self._cartesia_version = cartesia_version diff --git a/src/pipecat/services/rime/tts.py b/src/pipecat/services/rime/tts.py index ead56a37c..41045688c 100644 --- a/src/pipecat/services/rime/tts.py +++ b/src/pipecat/services/rime/tts.py @@ -37,7 +37,6 @@ from pipecat.services.tts_service import ( WebsocketTTSService, ) from pipecat.transcriptions.language import Language, resolve_language -from pipecat.utils.text.base_text_aggregator import BaseTextAggregator from pipecat.utils.text.skip_tags_aggregator import SkipTagsAggregator from pipecat.utils.tracing.service_decorators import traced_tts @@ -176,7 +175,6 @@ class RimeTTSService(WebsocketTTSService): sample_rate: Optional[int] = None, params: Optional[InputParams] = None, settings: Optional[Settings] = None, - text_aggregator: Optional[BaseTextAggregator] = None, text_aggregation_mode: Optional[TextAggregationMode] = None, aggregate_sentences: Optional[bool] = None, **kwargs, @@ -204,11 +202,6 @@ class RimeTTSService(WebsocketTTSService): settings: Runtime-updatable settings. When provided alongside deprecated parameters, ``settings`` values take precedence. - text_aggregator: Custom text aggregator for processing input text. - - .. deprecated:: 0.0.95 - Use an LLMTextProcessor before the TTSService for custom text aggregation. - text_aggregation_mode: How to aggregate incoming text before synthesis. aggregate_sentences: Deprecated. Use text_aggregation_mode instead. @@ -282,15 +275,14 @@ class RimeTTSService(WebsocketTTSService): self._audio_format = "pcm" self._sampling_rate = 0 # updated in start() - if not text_aggregator: - # Always skip tags added for spelled-out text - # Note: This is primarily to support backwards compatibility. - # The preferred way of taking advantage of Rime spelling is - # to use an LLMTextProcessor and/or a text_transformer to identify - # and insert these tags for the purpose of the TTS service alone. - self._text_aggregator = SkipTagsAggregator( - [("spell(", ")")], aggregation_type=self._text_aggregation_mode - ) + # Always skip tags added for spelled-out text + # Note: This is primarily to support backwards compatibility. + # The preferred way of taking advantage of Rime spelling is + # to use an LLMTextProcessor and/or a text_transformer to identify + # and insert these tags for the purpose of the TTS service alone. + self._text_aggregator = SkipTagsAggregator( + [("spell(", ")")], aggregation_type=self._text_aggregation_mode + ) # Store service configuration self._api_key = api_key diff --git a/src/pipecat/services/tts_service.py b/src/pipecat/services/tts_service.py index 72830078b..034e2fcd4 100644 --- a/src/pipecat/services/tts_service.py +++ b/src/pipecat/services/tts_service.py @@ -58,7 +58,6 @@ from pipecat.services.ai_service import AIService from pipecat.services.settings import TTSSettings, is_given from pipecat.services.websocket_service import WebsocketService from pipecat.transcriptions.language import Language -from pipecat.utils.text.base_text_aggregator import BaseTextAggregator from pipecat.utils.text.base_text_filter import BaseTextFilter from pipecat.utils.text.simple_text_aggregator import SimpleTextAggregator from pipecat.utils.time import seconds_to_nanoseconds @@ -168,8 +167,6 @@ class TTSService(AIService): append_trailing_space: bool = False, # TTS output sample rate sample_rate: Optional[int] = None, - # Text aggregator to aggregate incoming tokens and decide when to push to the TTS. - text_aggregator: Optional[BaseTextAggregator] = None, # Types of text aggregations that should not be spoken. skip_aggregator_types: Optional[List[str]] = [], # A list of callables to transform text before just before sending it to TTS. @@ -182,7 +179,6 @@ class TTSService(AIService): ] = None, # Text filter executed after text has been aggregated. text_filters: Optional[Sequence[BaseTextFilter]] = None, - text_filter: Optional[BaseTextFilter] = None, # Audio transport destination of the generated frames. transport_destination: Optional[str] = None, settings: Optional[TTSSettings] = None, @@ -215,11 +211,6 @@ class TTSService(AIService): append_trailing_space: Whether to append a trailing space to text before sending to TTS. This helps prevent some TTS services from vocalizing trailing punctuation (e.g., "dot"). sample_rate: Output sample rate for generated audio. - text_aggregator: Custom text aggregator for processing incoming text. - - .. deprecated:: 0.0.95 - Use an LLMTextProcessor before the TTSService for custom text aggregation. - skip_aggregator_types: List of aggregation types that should not be spoken. text_transforms: A list of callables to transform text before just before sending it to TTS. Each callable takes the aggregated text and its type, and returns the @@ -227,11 +218,6 @@ class TTSService(AIService): (aggregation_type | '*', transform_function). text_filters: Sequence of text filters to apply after aggregation. - text_filter: Single text filter (deprecated, use text_filters). - - .. deprecated:: 0.0.59 - Use `text_filters` instead, which allows multiple filters. - transport_destination: Destination for generated audio frames. settings: The runtime-updatable settings for the TTS service. reuse_context_id_within_turn: Whether the service should reuse context IDs within the @@ -300,18 +286,7 @@ class TTSService(AIService): self._append_trailing_space: bool = append_trailing_space self._init_sample_rate = sample_rate self._sample_rate = 0 - self._text_aggregator: BaseTextAggregator = text_aggregator or SimpleTextAggregator( - aggregation_type=self._text_aggregation_mode - ) - if text_aggregator: - import warnings - - with warnings.catch_warnings(): - warnings.simplefilter("always") - warnings.warn( - "Parameter 'text_aggregator' is deprecated. Use an LLMTextProcessor before the TTSService for custom text aggregation.", - DeprecationWarning, - ) + self._text_aggregator = SimpleTextAggregator(aggregation_type=self._text_aggregation_mode) self._skip_aggregator_types: List[str] = skip_aggregator_types or [] self._text_transforms: List[ @@ -320,16 +295,6 @@ class TTSService(AIService): # TODO: Deprecate _text_filters when added to LLMTextProcessor self._text_filters: Sequence[BaseTextFilter] = text_filters or [] self._transport_destination: Optional[str] = transport_destination - if text_filter: - import warnings - - with warnings.catch_warnings(): - warnings.simplefilter("always") - warnings.warn( - "Parameter 'text_filter' is deprecated, use 'text_filters' instead.", - DeprecationWarning, - ) - self._text_filters = [text_filter] self._resampler = create_stream_resampler()