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()