From b28276446d15e565645f0a024a01b2893711b09a Mon Sep 17 00:00:00 2001 From: Mark Backman Date: Tue, 18 Mar 2025 07:47:18 -0400 Subject: [PATCH] Code review feedback --- CHANGELOG.md | 5 +- ....py => 35-pattern-pair-voice-switching.py} | 56 ++++++++++++++++--- .../utils/text/pattern_pair_aggregator.py | 2 +- 3 files changed, 52 insertions(+), 11 deletions(-) rename examples/foundational/{35-voice-switching.py => 35-pattern-pair-voice-switching.py} (77%) diff --git a/CHANGELOG.md b/CHANGELOG.md index c43971da8..6d200ab87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -130,7 +130,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update the `34-audio-recording.py` example to include an STT processor. - Added foundational example `35-voice-switching.py` showing how to use the new - `PatternPairAggregator`. + `PatternPairAggregator`. This example shows how to encode information for the + LLM to instruct TTS voice changes, but this can be used to encode any + information into the LLM response, which you want to parse and use in other + parts of your application. - Added a Pipecat Cloud deployment example to the `examples` directory. diff --git a/examples/foundational/35-voice-switching.py b/examples/foundational/35-pattern-pair-voice-switching.py similarity index 77% rename from examples/foundational/35-voice-switching.py rename to examples/foundational/35-pattern-pair-voice-switching.py index 5dd986bc3..bb9587706 100644 --- a/examples/foundational/35-voice-switching.py +++ b/examples/foundational/35-pattern-pair-voice-switching.py @@ -4,6 +4,46 @@ # SPDX-License-Identifier: BSD 2-Clause License # +"""Pattern Pair Voice Switching Example with Pipecat. + +This example demonstrates how to use the PatternPairAggregator to dynamically switch +between different voices in a storytelling application. It showcases how pattern matching +can be used to control TTS behavior in streaming text from an LLM. + +The example: + 1. Sets up a storytelling bot with three distinct voices (narrator, male, female) + 2. Uses pattern pairs (name) to trigger voice switching + 3. Processes the patterns in real-time as text streams from the LLM + 4. Removes the pattern tags before sending text to TTS + +The PatternPairAggregator: + - Buffers text until complete patterns are detected + - Identifies content between start/end pattern pairs + - Triggers callbacks when patterns are matched + - Processes patterns that may span across multiple text chunks + - Returns processed text at sentence boundaries + +Example usage (run from pipecat root directory): + $ pip install "pipecat-ai[daily,openai,cartesia,silero]" + $ pip install -r dev-requirements.txt + $ python examples/foundational/35-pattern-pair-voice-switching.py + +Requirements: + - OpenAI API key (for GPT-4o) + - Cartesia API key (for text-to-speech) + - Daily API key (for video/audio transport) + + Environment variables (.env file): + OPENAI_API_KEY=your_openai_key + CARTESIA_API_KEY=your_cartesia_key + DAILY_API_KEY=your_daily_key + +Note: + This example shows one application of PatternPairAggregator (voice switching), + but the same approach can be used for various pattern-based text processing needs, + such as formatting instructions, command recognition, or structured data extraction. +""" + import asyncio import os import sys @@ -43,7 +83,7 @@ async def main(): transport = DailyTransport( room_url, token, - "Storytelling Bot", + "Multi-voice storyteller", DailyParams( audio_out_enabled=True, transcription_enabled=True, @@ -52,12 +92,6 @@ async def main(): ), ) - # Initialize TTS with narrator voice as default - tts = CartesiaTTSService( - api_key=os.getenv("CARTESIA_API_KEY"), - voice_id=VOICE_IDS["narrator"], - ) - # Create pattern pair aggregator for voice switching pattern_aggregator = PatternPairAggregator() @@ -81,8 +115,12 @@ async def main(): pattern_aggregator.on_pattern_match("voice_tag", on_voice_tag) - # Set the pattern aggregator on the TTS service - tts._text_aggregator = pattern_aggregator + # Initialize TTS with narrator voice as default + tts = CartesiaTTSService( + api_key=os.getenv("CARTESIA_API_KEY"), + voice_id=VOICE_IDS["narrator"], + text_aggregator=pattern_aggregator, + ) # Initialize LLM llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o") diff --git a/src/pipecat/utils/text/pattern_pair_aggregator.py b/src/pipecat/utils/text/pattern_pair_aggregator.py index 734e6a9bd..86f87103b 100644 --- a/src/pipecat/utils/text/pattern_pair_aggregator.py +++ b/src/pipecat/utils/text/pattern_pair_aggregator.py @@ -5,7 +5,7 @@ # import re -from typing import Callable, Dict, Optional, Pattern, Tuple, Union +from typing import Callable, Optional, Tuple from loguru import logger