Clean up CartesiaTTSSettings: separate init-only vs runtime-updatable fields

Move output_container, output_encoding, output_sample_rate out of
CartesiaTTSSettings into plain instance attributes since they cannot
change at runtime without breaking the audio pipeline. Remove deprecated
speed/emotion fields and their dead references in _build_msg() and
run_tts(). Remove the from_mapping override that only existed to
destructure those now-removed output format fields.
This commit is contained in:
Mark Backman
2026-03-03 20:29:35 -05:00
parent 07f1d0cd96
commit f31bfcf4ec

View File

@@ -8,14 +8,13 @@
import base64 import base64
import json import json
import warnings
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from typing import Any, AsyncGenerator, List, Literal, Mapping, Optional, Self from typing import AsyncGenerator, List, Optional
import aiohttp import aiohttp
from loguru import logger from loguru import logger
from pydantic import BaseModel, Field from pydantic import BaseModel
from pipecat.frames.frames import ( from pipecat.frames.frames import (
CancelFrame, CancelFrame,
@@ -192,35 +191,16 @@ class CartesiaTTSSettings(TTSSettings):
"""Settings for Cartesia TTS services. """Settings for Cartesia TTS services.
Parameters: Parameters:
output_container: Audio container format (e.g. "raw").
output_encoding: Audio encoding format (e.g. "pcm_s16le").
output_sample_rate: Audio sample rate in Hz.
speed: Voice speed control for non-Sonic-3 models (literal values).
emotion: List of emotion controls for non-Sonic-3 models.
generation_config: Generation configuration for Sonic-3 models. Includes volume, generation_config: Generation configuration for Sonic-3 models. Includes volume,
speed (numeric), and emotion (string) parameters. speed (numeric), and emotion (string) parameters.
pronunciation_dict_id: The ID of the pronunciation dictionary to use for pronunciation_dict_id: The ID of the pronunciation dictionary to use for
custom pronunciations. custom pronunciations.
""" """
output_container: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) generation_config: GenerationConfig | None | _NotGiven = field(
output_encoding: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN) default_factory=lambda: NOT_GIVEN
output_sample_rate: int | _NotGiven = field(default_factory=lambda: NOT_GIVEN) )
speed: Literal["slow", "normal", "fast"] | _NotGiven = field(default_factory=lambda: NOT_GIVEN) pronunciation_dict_id: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
emotion: List[str] | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
generation_config: GenerationConfig | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
pronunciation_dict_id: str | _NotGiven = field(default_factory=lambda: NOT_GIVEN)
@classmethod
def from_mapping(cls, settings: Mapping[str, Any]) -> Self:
"""Construct settings from a plain dict, destructuring legacy nested ``output_format``."""
flat = dict(settings)
nested = flat.pop("output_format", None)
if isinstance(nested, dict):
flat.setdefault("output_container", nested.get("container"))
flat.setdefault("output_encoding", nested.get("encoding"))
flat.setdefault("output_sample_rate", nested.get("sample_rate"))
return super().from_mapping(flat)
class CartesiaTTSService(AudioContextTTSService): class CartesiaTTSService(AudioContextTTSService):
@@ -228,7 +208,7 @@ class CartesiaTTSService(AudioContextTTSService):
Provides text-to-speech using Cartesia's streaming WebSocket API. Provides text-to-speech using Cartesia's streaming WebSocket API.
Supports word-level timestamps, audio context management, and various voice Supports word-level timestamps, audio context management, and various voice
customization options including speed and emotion controls. customization options including generation configuration.
""" """
_settings: CartesiaTTSSettings _settings: CartesiaTTSSettings
@@ -238,20 +218,12 @@ class CartesiaTTSService(AudioContextTTSService):
Parameters: Parameters:
language: Language to use for synthesis. language: Language to use for synthesis.
speed: Voice speed control for non-Sonic-3 models (literal values).
emotion: List of emotion controls for non-Sonic-3 models.
.. deprecated:: 0.0.68
The `emotion` parameter is deprecated and will be removed in a future version.
generation_config: Generation configuration for Sonic-3 models. Includes volume, generation_config: Generation configuration for Sonic-3 models. Includes volume,
speed (numeric), and emotion (string) parameters. speed (numeric), and emotion (string) parameters.
pronunciation_dict_id: The ID of the pronunciation dictionary to use for custom pronunciations. pronunciation_dict_id: The ID of the pronunciation dictionary to use for custom pronunciations.
""" """
language: Optional[Language] = Language.EN language: Optional[Language] = Language.EN
speed: Optional[Literal["slow", "normal", "fast"]] = None
emotion: Optional[List[str]] = []
generation_config: Optional[GenerationConfig] = None generation_config: Optional[GenerationConfig] = None
pronunciation_dict_id: Optional[str] = None pronunciation_dict_id: Optional[str] = None
@@ -279,14 +251,14 @@ class CartesiaTTSService(AudioContextTTSService):
api_key: Cartesia API key for authentication. api_key: Cartesia API key for authentication.
voice_id: ID of the voice to use for synthesis. voice_id: ID of the voice to use for synthesis.
.. deprecated:: 1.0.0 .. deprecated:: 0.0.105
Use ``settings=CartesiaTTSSettings(voice=...)`` instead. Use ``settings=CartesiaTTSSettings(voice=...)`` instead.
cartesia_version: API version string for Cartesia service. cartesia_version: API version string for Cartesia service.
url: WebSocket URL for Cartesia TTS API. url: WebSocket URL for Cartesia TTS API.
model: TTS model to use (e.g., "sonic-3"). model: TTS model to use (e.g., "sonic-3").
.. deprecated:: 1.0.0 .. deprecated:: 0.0.105
Use ``settings=CartesiaTTSSettings(model=...)`` instead. Use ``settings=CartesiaTTSSettings(model=...)`` instead.
sample_rate: Audio sample rate. If None, uses default. sample_rate: Audio sample rate. If None, uses default.
@@ -294,7 +266,7 @@ class CartesiaTTSService(AudioContextTTSService):
container: Audio container format. container: Audio container format.
params: Additional input parameters for voice customization. params: Additional input parameters for voice customization.
.. deprecated:: 1.0.0 .. deprecated:: 0.0.105
Use ``settings=CartesiaTTSSettings(...)`` instead. Use ``settings=CartesiaTTSSettings(...)`` instead.
settings: Runtime-updatable settings. When provided alongside deprecated settings: Runtime-updatable settings. When provided alongside deprecated
@@ -329,9 +301,9 @@ class CartesiaTTSService(AudioContextTTSService):
# 1. Initialize default_settings with hardcoded defaults # 1. Initialize default_settings with hardcoded defaults
default_settings = CartesiaTTSSettings( default_settings = CartesiaTTSSettings(
model="sonic-3", model="sonic-3",
output_container=container, language=language_to_cartesia_language(Language.EN),
output_encoding=encoding, generation_config=None,
output_sample_rate=0, pronunciation_dict_id=None,
) )
# 2. Apply direct init arg overrides (deprecated) # 2. Apply direct init arg overrides (deprecated)
@@ -348,10 +320,6 @@ class CartesiaTTSService(AudioContextTTSService):
if not settings: if not settings:
if params.language is not None: if params.language is not None:
default_settings.language = self.language_to_service_language(params.language) default_settings.language = self.language_to_service_language(params.language)
if params.speed is not None:
default_settings.speed = params.speed
if params.emotion is not None:
default_settings.emotion = params.emotion
if params.generation_config is not None: if params.generation_config is not None:
default_settings.generation_config = params.generation_config default_settings.generation_config = params.generation_config
if params.pronunciation_dict_id is not None: if params.pronunciation_dict_id is not None:
@@ -387,6 +355,11 @@ class CartesiaTTSService(AudioContextTTSService):
self._cartesia_version = cartesia_version self._cartesia_version = cartesia_version
self._url = url self._url = url
# Audio output format — init-only, not runtime-updatable
self._output_container = container
self._output_encoding = encoding
self._output_sample_rate = 0 # Set in start() from self.sample_rate
self._receive_task = None self._receive_task = None
def can_generate_metrics(self) -> bool: def can_generate_metrics(self) -> bool:
@@ -484,17 +457,6 @@ class CartesiaTTSService(AudioContextTTSService):
voice_config["mode"] = "id" voice_config["mode"] = "id"
voice_config["id"] = self._settings.voice voice_config["id"] = self._settings.voice
if self._settings.emotion:
with warnings.catch_warnings():
warnings.simplefilter("always")
warnings.warn(
"The 'emotion' parameter in __experimental_controls is deprecated and will be removed in a future version.",
DeprecationWarning,
stacklevel=2,
)
voice_config["__experimental_controls"] = {}
voice_config["__experimental_controls"]["emotion"] = self._settings.emotion
msg = { msg = {
"transcript": text, "transcript": text,
"continue": continue_transcript, "continue": continue_transcript,
@@ -502,9 +464,9 @@ class CartesiaTTSService(AudioContextTTSService):
"model_id": self._settings.model, "model_id": self._settings.model,
"voice": voice_config, "voice": voice_config,
"output_format": { "output_format": {
"container": self._settings.output_container, "container": self._output_container,
"encoding": self._settings.output_encoding, "encoding": self._output_encoding,
"sample_rate": self._settings.output_sample_rate, "sample_rate": self._output_sample_rate,
}, },
"add_timestamps": add_timestamps, "add_timestamps": add_timestamps,
"use_original_timestamps": False if self._settings.model == "sonic" else True, "use_original_timestamps": False if self._settings.model == "sonic" else True,
@@ -513,9 +475,6 @@ class CartesiaTTSService(AudioContextTTSService):
if self._settings.language: if self._settings.language:
msg["language"] = self._settings.language msg["language"] = self._settings.language
if self._settings.speed:
msg["speed"] = self._settings.speed
if self._settings.generation_config: if self._settings.generation_config:
msg["generation_config"] = self._settings.generation_config.model_dump( msg["generation_config"] = self._settings.generation_config.model_dump(
exclude_none=True exclude_none=True
@@ -533,7 +492,7 @@ class CartesiaTTSService(AudioContextTTSService):
frame: The start frame containing initialization parameters. frame: The start frame containing initialization parameters.
""" """
await super().start(frame) await super().start(frame)
self._settings.output_sample_rate = self.sample_rate self._output_sample_rate = self.sample_rate
await self._connect() await self._connect()
async def stop(self, frame: EndFrame): async def stop(self, frame: EndFrame):
@@ -728,20 +687,12 @@ class CartesiaHttpTTSService(TTSService):
Parameters: Parameters:
language: Language to use for synthesis. language: Language to use for synthesis.
speed: Voice speed control for non-Sonic-3 models (literal values).
emotion: List of emotion controls for non-Sonic-3 models.
.. deprecated:: 0.0.68
The `emotion` parameter is deprecated and will be removed in a future version.
generation_config: Generation configuration for Sonic-3 models. Includes volume, generation_config: Generation configuration for Sonic-3 models. Includes volume,
speed (numeric), and emotion (string) parameters. speed (numeric), and emotion (string) parameters.
pronunciation_dict_id: The ID of the pronunciation dictionary to use for custom pronunciations. pronunciation_dict_id: The ID of the pronunciation dictionary to use for custom pronunciations.
""" """
language: Optional[Language] = Language.EN language: Optional[Language] = Language.EN
speed: Optional[Literal["slow", "normal", "fast"]] = None
emotion: Optional[List[str]] = Field(default_factory=list)
generation_config: Optional[GenerationConfig] = None generation_config: Optional[GenerationConfig] = None
pronunciation_dict_id: Optional[str] = None pronunciation_dict_id: Optional[str] = None
@@ -767,12 +718,12 @@ class CartesiaHttpTTSService(TTSService):
api_key: Cartesia API key for authentication. api_key: Cartesia API key for authentication.
voice_id: ID of the voice to use for synthesis. voice_id: ID of the voice to use for synthesis.
.. deprecated:: 1.0.0 .. deprecated:: 0.0.105
Use ``settings=CartesiaTTSSettings(voice=...)`` instead. Use ``settings=CartesiaTTSSettings(voice=...)`` instead.
model: TTS model to use (e.g., "sonic-3"). model: TTS model to use (e.g., "sonic-3").
.. deprecated:: 1.0.0 .. deprecated:: 0.0.105
Use ``settings=CartesiaTTSSettings(model=...)`` instead. Use ``settings=CartesiaTTSSettings(model=...)`` instead.
base_url: Base URL for Cartesia HTTP API. base_url: Base URL for Cartesia HTTP API.
@@ -784,7 +735,7 @@ class CartesiaHttpTTSService(TTSService):
container: Audio container format. container: Audio container format.
params: Additional input parameters for voice customization. params: Additional input parameters for voice customization.
.. deprecated:: 1.0.0 .. deprecated:: 0.0.105
Use ``settings=CartesiaTTSSettings(...)`` instead. Use ``settings=CartesiaTTSSettings(...)`` instead.
settings: Runtime-updatable settings. When provided alongside deprecated settings: Runtime-updatable settings. When provided alongside deprecated
@@ -794,9 +745,9 @@ class CartesiaHttpTTSService(TTSService):
# 1. Initialize default_settings with hardcoded defaults # 1. Initialize default_settings with hardcoded defaults
default_settings = CartesiaTTSSettings( default_settings = CartesiaTTSSettings(
model="sonic-3", model="sonic-3",
output_container=container, language=language_to_cartesia_language(Language.EN),
output_encoding=encoding, generation_config=None,
output_sample_rate=0, pronunciation_dict_id=None,
) )
# 2. Apply direct init arg overrides (deprecated) # 2. Apply direct init arg overrides (deprecated)
@@ -813,10 +764,6 @@ class CartesiaHttpTTSService(TTSService):
if not settings: if not settings:
if params.language is not None: if params.language is not None:
default_settings.language = self.language_to_service_language(params.language) default_settings.language = self.language_to_service_language(params.language)
if params.speed is not None:
default_settings.speed = params.speed
if params.emotion is not None:
default_settings.emotion = params.emotion
if params.generation_config is not None: if params.generation_config is not None:
default_settings.generation_config = params.generation_config default_settings.generation_config = params.generation_config
if params.pronunciation_dict_id is not None: if params.pronunciation_dict_id is not None:
@@ -836,6 +783,11 @@ class CartesiaHttpTTSService(TTSService):
self._base_url = base_url self._base_url = base_url
self._cartesia_version = cartesia_version self._cartesia_version = cartesia_version
# Audio output format — init-only, not runtime-updatable
self._output_container = container
self._output_encoding = encoding
self._output_sample_rate = 0 # Set in start() from self.sample_rate
self._session: aiohttp.ClientSession | None = aiohttp_session self._session: aiohttp.ClientSession | None = aiohttp_session
self._owns_session = aiohttp_session is None self._owns_session = aiohttp_session is None
@@ -865,7 +817,7 @@ class CartesiaHttpTTSService(TTSService):
frame: The start frame containing initialization parameters. frame: The start frame containing initialization parameters.
""" """
await super().start(frame) await super().start(frame)
self._settings.output_sample_rate = self.sample_rate self._output_sample_rate = self.sample_rate
if self._owns_session: if self._owns_session:
self._session = aiohttp.ClientSession() self._session = aiohttp.ClientSession()
@@ -909,22 +861,12 @@ class CartesiaHttpTTSService(TTSService):
try: try:
voice_config = {"mode": "id", "id": self._settings.voice} voice_config = {"mode": "id", "id": self._settings.voice}
if self._settings.emotion:
with warnings.catch_warnings():
warnings.simplefilter("always")
warnings.warn(
"The 'emotion' parameter in voice.__experimental_controls is deprecated and will be removed in a future version.",
DeprecationWarning,
stacklevel=2,
)
voice_config["__experimental_controls"] = {"emotion": self._settings.emotion}
await self.start_ttfb_metrics() await self.start_ttfb_metrics()
output_format = { output_format = {
"container": self._settings.output_container, "container": self._output_container,
"encoding": self._settings.output_encoding, "encoding": self._output_encoding,
"sample_rate": self._settings.output_sample_rate, "sample_rate": self._output_sample_rate,
} }
payload = { payload = {
@@ -937,9 +879,6 @@ class CartesiaHttpTTSService(TTSService):
if self._settings.language: if self._settings.language:
payload["language"] = self._settings.language payload["language"] = self._settings.language
if self._settings.speed:
payload["speed"] = self._settings.speed
if self._settings.generation_config: if self._settings.generation_config:
payload["generation_config"] = self._settings.generation_config.model_dump( payload["generation_config"] = self._settings.generation_config.model_dump(
exclude_none=True exclude_none=True