TTSServices: for now just specify a single text aggregator

This commit is contained in:
Aleix Conchillo Flaqué
2025-03-19 11:02:29 -07:00
parent fc0f404d26
commit 336e2f1579
5 changed files with 15 additions and 32 deletions

View File

@@ -21,8 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added new `BaseTextAggregator`. Text aggregators are used by the TTS service
to aggregate LLM tokens and decide when the aggregated text should be pushed
to the TTS service. They also allow for the text to be manipulated while it's
being aggregated. Multiple text aggregators can be passed with
`text_aggregators` to the TTS service.
being aggregated. A text aggregator can be passed via `text_aggregator` to the
TTS service.
- Added new `UltravoxSTTService`.
(see https://github.com/fixie-ai/ultravox)

View File

@@ -119,7 +119,7 @@ async def main():
tts = CartesiaTTSService(
api_key=os.getenv("CARTESIA_API_KEY"),
voice_id=VOICE_IDS["narrator"],
text_aggregators=[pattern_aggregator],
text_aggregator=pattern_aggregator,
)
# Initialize LLM

View File

@@ -239,7 +239,7 @@ class TTSService(AIService):
# TTS output sample rate
sample_rate: Optional[int] = None,
# Text aggregator to aggregate incoming tokens and decide when to push to the TTS.
text_aggregators: Sequence[BaseTextAggregator] = [],
text_aggregator: Optional[BaseTextAggregator] = None,
# Text filter executed after text has been aggregated.
text_filters: Sequence[BaseTextFilter] = [],
text_filter: Optional[BaseTextFilter] = None,
@@ -257,10 +257,7 @@ class TTSService(AIService):
self._sample_rate = 0
self._voice_id: str = ""
self._settings: Dict[str, Any] = {}
# Ensure there's at least one text aggregator.
self._text_aggregators: Sequence[BaseTextAggregator] = text_aggregators or [
SimpleTextAggregator()
]
self._text_aggregator: BaseTextAggregator = text_aggregator or SimpleTextAggregator()
self._text_filters: Sequence[BaseTextFilter] = text_filters
if text_filter:
import warnings
@@ -358,8 +355,8 @@ class TTSService(AIService):
# pause to avoid audio overlapping.
await self._maybe_pause_frame_processing()
sentence = self._text_aggregators[-1].text
self._reset_aggregators()
sentence = self._text_aggregator.text
self._text_aggregator.reset()
self._processing_text = False
await self._push_tts_frames(sentence)
if isinstance(frame, LLMFullResponseEndFrame):
@@ -405,8 +402,7 @@ class TTSService(AIService):
async def _handle_interruption(self, frame: StartInterruptionFrame, direction: FrameDirection):
self._processing_text = False
for aggregator in self._text_aggregators:
aggregator.handle_interruption()
self._text_aggregator.handle_interruption()
for filter in self._text_filters:
filter.handle_interruption()
@@ -418,25 +414,12 @@ class TTSService(AIService):
if self._pause_frame_processing:
await self.resume_processing_frames()
def _reset_aggregators(self):
for aggregator in self._text_aggregators:
aggregator.reset()
async def _process_text_frame(self, frame: TextFrame):
text: Optional[str] = None
if not self._aggregate_sentences:
text = frame.text
else:
current_text = frame.text
# Process all aggregators except the last one.
for aggregator in self._text_aggregators[:-1]:
aggregator.aggregate(current_text)
current_text = aggregator.text
# The last aggregator decides whether we are sending text to the
# TTS or not.
text = self._text_aggregators[-1].aggregate(current_text)
text = self._text_aggregator.aggregate(frame.text)
if text:
await self._push_tts_frames(text)

View File

@@ -7,7 +7,7 @@
import base64
import json
import uuid
from typing import AsyncGenerator, List, Optional, Sequence, Union
from typing import AsyncGenerator, List, Optional, Union
from loguru import logger
from pydantic import BaseModel
@@ -91,7 +91,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
encoding: str = "pcm_s16le",
container: str = "raw",
params: InputParams = InputParams(),
text_aggregators: Sequence[BaseTextAggregator] = [],
text_aggregator: Optional[BaseTextAggregator] = None,
**kwargs,
):
# Aggregating sentences still gives cleaner-sounding results and fewer
@@ -109,7 +109,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
push_text_frames=False,
pause_frame_processing=True,
sample_rate=sample_rate,
text_aggregators=text_aggregators or [SkipTagsAggregator([("<spell>", "</spell>")])],
text_aggregator=text_aggregator or SkipTagsAggregator([("<spell>", "</spell>")]),
**kwargs,
)

View File

@@ -7,7 +7,7 @@
import base64
import json
import uuid
from typing import AsyncGenerator, Optional, Sequence
from typing import AsyncGenerator, Optional
import aiohttp
from loguru import logger
@@ -80,7 +80,7 @@ class RimeTTSService(AudioContextWordTTSService):
model: str = "mistv2",
sample_rate: Optional[int] = None,
params: InputParams = InputParams(),
text_aggregators: Sequence[BaseTextAggregator] = [],
text_aggregator: Optional[BaseTextAggregator] = None,
**kwargs,
):
"""Initialize Rime TTS service.
@@ -100,7 +100,7 @@ class RimeTTSService(AudioContextWordTTSService):
push_stop_frames=True,
pause_frame_processing=True,
sample_rate=sample_rate,
text_aggregators=text_aggregators or [SkipTagsAggregator([("spell(", ")")])],
text_aggregator=text_aggregator or SkipTagsAggregator([("spell(", ")")]),
**kwargs,
)