diff --git a/COMMUNITY_INTEGRATIONS.md b/COMMUNITY_INTEGRATIONS.md index e11c79f31..ff8d08ea5 100644 --- a/COMMUNITY_INTEGRATIONS.md +++ b/COMMUNITY_INTEGRATIONS.md @@ -257,15 +257,16 @@ The service stores its current settings in `self._settings` and declares the typ ```python class MySTTService(STTService): - _settings: MySTTSettings def __init__(self, *, model: str, language: str, region: str, **kwargs): - super().__init__(**kwargs) - # Initial value must be provided for every field in self._settings - # before service is started - self._settings = MySTTSettings(model=model, language=language, region=region) - self._sync_model_name_to_metrics() + # An initial value should be provided for every settings field. + # This will be validated at service start. + # (If you track sample_rate, it can be a placeholder value like 0; see + # "Sample Rate Handling"). + super().__init__( + settings=MySTTSettings(model=model, language=language, region=region), **kwargs + ) ``` To react to runtime setting changes, override `_update_settings`. The base implementation applies the delta to `self._settings` and returns a `dict` mapping each changed field name to its **pre-update** value. Your override should call `super()` first, then act on the changed fields. A common implementation might look like: diff --git a/examples/foundational/55zg-update-settings-hathora-tts.py b/examples/foundational/55zg-update-settings-hathora-tts.py index 9f6b6bd0a..80b9bfcce 100644 --- a/examples/foundational/55zg-update-settings-hathora-tts.py +++ b/examples/foundational/55zg-update-settings-hathora-tts.py @@ -54,7 +54,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): tts = HathoraTTSService( api_key=os.getenv("HATHORA_API_KEY"), - model="hathora-ai/polar", + model="hexgrad-kokoro-82m", ) llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY")) @@ -100,8 +100,8 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): await task.queue_frames([LLMRunFrame()]) await asyncio.sleep(10) - logger.info("Updating Hathora TTS settings: speed=1.3") - await task.queue_frame(TTSUpdateSettingsFrame(delta=HathoraTTSSettings(speed=1.3))) + logger.info("Updating Hathora TTS settings: speed=1.5") + await task.queue_frame(TTSUpdateSettingsFrame(delta=HathoraTTSSettings(speed=1.5))) @transport.event_handler("on_client_disconnected") async def on_client_disconnected(transport, client): diff --git a/examples/foundational/55zs-update-settings-hathora-stt.py b/examples/foundational/55zs-update-settings-hathora-stt.py index db5ed4d2a..7a033490a 100644 --- a/examples/foundational/55zs-update-settings-hathora-stt.py +++ b/examples/foundational/55zs-update-settings-hathora-stt.py @@ -52,7 +52,9 @@ transport_params = { async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): logger.info(f"Starting bot") - stt = HathoraSTTService(api_key=os.getenv("HATHORA_API_KEY"), model="deepgram-nova3") + stt = HathoraSTTService( + api_key=os.getenv("HATHORA_API_KEY"), model="nvidia-parakeet-tdt-0.6b-v3" + ) tts = CartesiaTTSService( api_key=os.getenv("CARTESIA_API_KEY"), diff --git a/src/pipecat/services/ai_service.py b/src/pipecat/services/ai_service.py index f092c2b49..c4e45a417 100644 --- a/src/pipecat/services/ai_service.py +++ b/src/pipecat/services/ai_service.py @@ -35,14 +35,21 @@ class AIService(FrameProcessor): this base infrastructure. """ - def __init__(self, **kwargs): + def __init__(self, settings: ServiceSettings | None = None, **kwargs): """Initialize the AI service. Args: + settings: The runtime-updatable settings for the AI service. **kwargs: Additional arguments passed to the parent FrameProcessor. """ super().__init__(**kwargs) - self._settings: ServiceSettings = ServiceSettings() # Here in case subclass doesn't implement more specific settings (hopefully shouldn't happen) + self._settings: ServiceSettings = ( + settings + # Here in case subclass doesn't implement more specific settings + # (which hopefully should be rare) + or ServiceSettings() + ) + self._sync_model_name_to_metrics() self._session_properties: Dict[str, Any] = {} self._tracing_enabled: bool = False self._tracing_context = None @@ -54,15 +61,12 @@ class AIService(FrameProcessor): of truth for it in `self._settings.model`. This method is just for syncing the model name to the metrics data. - TODO: as a next step we should make it so that service classes pass - model into `super().__init__` and `AIService` can be responsible for - syncing its initial value to metrics, just as it's responsible for - syncing any updates to its value to metrics via `_update_settings`. - Args: model: The name of the AI model to use. """ - self.set_core_metrics_data(MetricsData(processor=self.name, model=self._settings.model)) + self.set_core_metrics_data( + MetricsData(processor=self.name, model=self._settings.model or "") + ) async def start(self, frame: StartFrame): """Start the AI service. diff --git a/src/pipecat/services/anthropic/llm.py b/src/pipecat/services/anthropic/llm.py index 047159515..03190ef99 100644 --- a/src/pipecat/services/anthropic/llm.py +++ b/src/pipecat/services/anthropic/llm.py @@ -232,37 +232,39 @@ class AnthropicLLMService(LLMService): retry_on_timeout: Whether to retry the request once if it times out. **kwargs: Additional arguments passed to parent LLMService. """ - super().__init__(**kwargs) params = params or AnthropicLLMService.InputParams() + + super().__init__( + settings=AnthropicLLMSettings( + model=model, + max_tokens=params.max_tokens, + enable_prompt_caching=( + params.enable_prompt_caching + if params.enable_prompt_caching is not None + else ( + params.enable_prompt_caching_beta + if params.enable_prompt_caching_beta is not None + else False + ) + ), + temperature=params.temperature, + top_k=params.top_k, + top_p=params.top_p, + frequency_penalty=None, + presence_penalty=None, + seed=None, + filter_incomplete_user_turns=False, + user_turn_completion_config=None, + thinking=params.thinking, + extra=params.extra if isinstance(params.extra, dict) else {}, + ), + **kwargs, + ) self._client = client or AsyncAnthropic( api_key=api_key ) # if the client is provided, use it and remove it, otherwise create a new one self._retry_timeout_secs = retry_timeout_secs self._retry_on_timeout = retry_on_timeout - self._settings = AnthropicLLMSettings( - model=model, - max_tokens=params.max_tokens, - enable_prompt_caching=( - params.enable_prompt_caching - if params.enable_prompt_caching is not None - else ( - params.enable_prompt_caching_beta - if params.enable_prompt_caching_beta is not None - else False - ) - ), - temperature=params.temperature, - top_k=params.top_k, - top_p=params.top_p, - frequency_penalty=None, - presence_penalty=None, - seed=None, - filter_incomplete_user_turns=False, - user_turn_completion_config=None, - thinking=params.thinking, - extra=params.extra if isinstance(params.extra, dict) else {}, - ) - self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate usage metrics. diff --git a/src/pipecat/services/assemblyai/stt.py b/src/pipecat/services/assemblyai/stt.py index 44ae123b7..a89f5fe52 100644 --- a/src/pipecat/services/assemblyai/stt.py +++ b/src/pipecat/services/assemblyai/stt.py @@ -111,15 +111,17 @@ class AssemblyAISTTService(WebsocketSTTService): connection_params = self._configure_manual_turn_mode(connection_params) super().__init__( - sample_rate=connection_params.sample_rate, ttfs_p99_latency=ttfs_p99_latency, **kwargs + sample_rate=connection_params.sample_rate, + ttfs_p99_latency=ttfs_p99_latency, + settings=AssemblyAISTTSettings( + model=None, + language=language, + connection_params=connection_params, + ), + **kwargs, ) self._api_key = api_key - self._settings = AssemblyAISTTSettings( - model=None, - language=language, - connection_params=connection_params, - ) self._api_endpoint_base_url = api_endpoint_base_url self._vad_force_turn_endpoint = vad_force_turn_endpoint diff --git a/src/pipecat/services/asyncai/tts.py b/src/pipecat/services/asyncai/tts.py index f1f73b7ff..334f80d80 100644 --- a/src/pipecat/services/asyncai/tts.py +++ b/src/pipecat/services/asyncai/tts.py @@ -147,30 +147,29 @@ class AsyncAITTSService(AudioContextTTSService): aggregate_sentences: Whether to aggregate sentences within the TTSService. **kwargs: Additional arguments passed to the parent service. """ + params = params or AsyncAITTSService.InputParams() + super().__init__( aggregate_sentences=aggregate_sentences, pause_frame_processing=True, push_stop_frames=True, sample_rate=sample_rate, + settings=AsyncAITTSSettings( + model=model, + voice=voice_id, + output_container=container, + output_encoding=encoding, + output_sample_rate=0, + language=self.language_to_service_language(params.language) + if params.language + else None, + ), **kwargs, ) - params = params or AsyncAITTSService.InputParams() - self._api_key = api_key self._api_version = version self._url = url - self._settings = AsyncAITTSSettings( - model=model, - voice=voice_id, - output_container=container, - output_encoding=encoding, - output_sample_rate=0, - language=self.language_to_service_language(params.language) - if params.language - else None, - ) - self._sync_model_name_to_metrics() self._receive_task = None self._keepalive_task = None @@ -501,24 +500,26 @@ class AsyncAIHttpTTSService(TTSService): params: Additional input parameters for voice customization. **kwargs: Additional arguments passed to the parent TTSService. """ - super().__init__(sample_rate=sample_rate, **kwargs) - params = params or AsyncAIHttpTTSService.InputParams() + super().__init__( + sample_rate=sample_rate, + settings=AsyncAITTSSettings( + model=model, + voice=voice_id, + output_container=container, + output_encoding=encoding, + output_sample_rate=0, + language=self.language_to_service_language(params.language) + if params.language + else None, + ), + **kwargs, + ) + self._api_key = api_key self._base_url = url self._api_version = version - self._settings = AsyncAITTSSettings( - model=model, - voice=voice_id, - output_container=container, - output_encoding=encoding, - output_sample_rate=0, - language=self.language_to_service_language(params.language) - if params.language - else None, - ) - self._sync_model_name_to_metrics() self._session = aiohttp_session diff --git a/src/pipecat/services/aws/llm.py b/src/pipecat/services/aws/llm.py index 34e869c69..540ac4a8e 100644 --- a/src/pipecat/services/aws/llm.py +++ b/src/pipecat/services/aws/llm.py @@ -797,10 +797,28 @@ class AWSBedrockLLMService(LLMService): retry_on_timeout: Whether to retry the request once if it times out. **kwargs: Additional arguments passed to parent LLMService. """ - super().__init__(**kwargs) - params = params or AWSBedrockLLMService.InputParams() + super().__init__( + settings=AWSBedrockLLMSettings( + model=model, + max_tokens=params.max_tokens, + temperature=params.temperature, + top_p=params.top_p, + top_k=None, + frequency_penalty=None, + presence_penalty=None, + seed=None, + filter_incomplete_user_turns=False, + user_turn_completion_config=None, + latency=params.latency, + additional_model_request_fields=params.additional_model_request_fields + if isinstance(params.additional_model_request_fields, dict) + else {}, + ), + **kwargs, + ) + # Initialize the AWS Bedrock client if not client_config: client_config = Config( @@ -822,23 +840,6 @@ class AWSBedrockLLMService(LLMService): self._retry_timeout_secs = retry_timeout_secs self._retry_on_timeout = retry_on_timeout - self._settings = AWSBedrockLLMSettings( - model=model, - max_tokens=params.max_tokens, - temperature=params.temperature, - top_p=params.top_p, - top_k=None, - frequency_penalty=None, - presence_penalty=None, - seed=None, - filter_incomplete_user_turns=False, - user_turn_completion_config=None, - latency=params.latency, - additional_model_request_fields=params.additional_model_request_fields - if isinstance(params.additional_model_request_fields, dict) - else {}, - ) - self._sync_model_name_to_metrics() logger.info(f"Using AWS Bedrock model: {model}") diff --git a/src/pipecat/services/aws/nova_sonic/llm.py b/src/pipecat/services/aws/nova_sonic/llm.py index e51a1842c..29612e593 100644 --- a/src/pipecat/services/aws/nova_sonic/llm.py +++ b/src/pipecat/services/aws/nova_sonic/llm.py @@ -254,28 +254,30 @@ class AWSNovaSonicLLMService(LLMService): **kwargs: Additional arguments passed to the parent LLMService. """ - super().__init__(**kwargs) + params = params or Params() + + super().__init__( + settings=AWSNovaSonicLLMSettings( + model=model, + voice_id=voice_id, + temperature=params.temperature, + max_tokens=params.max_tokens, + top_p=params.top_p, + top_k=None, + frequency_penalty=None, + presence_penalty=None, + seed=None, + filter_incomplete_user_turns=False, + user_turn_completion_config=None, + endpointing_sensitivity=params.endpointing_sensitivity, + ), + **kwargs, + ) self._secret_access_key = secret_access_key self._access_key_id = access_key_id self._session_token = session_token self._region = region self._client: Optional[BedrockRuntimeClient] = None - params = params or Params() - self._settings = AWSNovaSonicLLMSettings( - model=model, - voice_id=voice_id, - temperature=params.temperature, - max_tokens=params.max_tokens, - top_p=params.top_p, - top_k=None, - frequency_penalty=None, - presence_penalty=None, - seed=None, - filter_incomplete_user_turns=False, - user_turn_completion_config=None, - endpointing_sensitivity=params.endpointing_sensitivity, - ) - self._sync_model_name_to_metrics() # Audio I/O config (hardware settings, not runtime-tunable) self._input_sample_rate = params.input_sample_rate diff --git a/src/pipecat/services/aws/stt.py b/src/pipecat/services/aws/stt.py index 1d8ae84f5..7c3fb398e 100644 --- a/src/pipecat/services/aws/stt.py +++ b/src/pipecat/services/aws/stt.py @@ -99,15 +99,17 @@ class AWSTranscribeSTTService(WebsocketSTTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to parent STTService class. """ - super().__init__(ttfs_p99_latency=ttfs_p99_latency, **kwargs) - - self._settings = AWSTranscribeSTTSettings( - language=self.language_to_service_language(language) or "en-US", - sample_rate=sample_rate, - media_encoding="linear16", - number_of_channels=1, - show_speaker_label=False, - enable_channel_identification=False, + super().__init__( + ttfs_p99_latency=ttfs_p99_latency, + settings=AWSTranscribeSTTSettings( + language=self.language_to_service_language(language) or "en-US", + sample_rate=sample_rate, + media_encoding="linear16", + number_of_channels=1, + show_speaker_label=False, + enable_channel_identification=False, + ), + **kwargs, ) # Validate sample rate - AWS Transcribe only supports 8000 Hz or 16000 Hz diff --git a/src/pipecat/services/aws/tts.py b/src/pipecat/services/aws/tts.py index 8b06ad7e9..017477a7a 100644 --- a/src/pipecat/services/aws/tts.py +++ b/src/pipecat/services/aws/tts.py @@ -195,10 +195,25 @@ class AWSPollyTTSService(TTSService): params: Additional input parameters for voice customization. **kwargs: Additional arguments passed to parent TTSService class. """ - super().__init__(sample_rate=sample_rate, **kwargs) - params = params or AWSPollyTTSService.InputParams() + super().__init__( + sample_rate=sample_rate, + settings=AWSPollyTTSSettings( + model=None, + voice=voice_id, + engine=params.engine, + language=self.language_to_service_language(params.language) + if params.language + else "en-US", + pitch=params.pitch, + rate=params.rate, + volume=params.volume, + lexicon_names=params.lexicon_names, + ), + **kwargs, + ) + # Get credentials from environment variables if not provided self._aws_params = { "aws_access_key_id": aws_access_key_id or os.getenv("AWS_ACCESS_KEY_ID"), @@ -208,18 +223,6 @@ class AWSPollyTTSService(TTSService): } self._aws_session = aioboto3.Session() - self._settings = AWSPollyTTSSettings( - model=None, - voice=voice_id, - engine=params.engine, - language=self.language_to_service_language(params.language) - if params.language - else "en-US", - pitch=params.pitch, - rate=params.rate, - volume=params.volume, - lexicon_names=params.lexicon_names, - ) self._resampler = create_stream_resampler() diff --git a/src/pipecat/services/azure/image.py b/src/pipecat/services/azure/image.py index f5ce4a9f1..66cc28504 100644 --- a/src/pipecat/services/azure/image.py +++ b/src/pipecat/services/azure/image.py @@ -12,6 +12,7 @@ using REST endpoints for creating images from text prompts. import asyncio import io +from dataclasses import dataclass from typing import AsyncGenerator import aiohttp @@ -19,6 +20,16 @@ from PIL import Image from pipecat.frames.frames import ErrorFrame, Frame, URLImageRawFrame from pipecat.services.image_service import ImageGenService +from pipecat.services.settings import ImageGenSettings + + +@dataclass +class AzureImageGenSettings(ImageGenSettings): + """Settings for the Azure image generation service. + + Parameters: + model: Azure image generation model identifier. + """ class AzureImageGenServiceREST(ImageGenService): @@ -49,13 +60,11 @@ class AzureImageGenServiceREST(ImageGenService): aiohttp_session: Shared aiohttp session for HTTP requests. api_version: Azure API version string. Defaults to "2023-06-01-preview". """ - super().__init__() + super().__init__(settings=AzureImageGenSettings(model=model)) self._api_key = api_key self._azure_endpoint = endpoint self._api_version = api_version - self._settings.model = model - self._sync_model_name_to_metrics() self._image_size = image_size self._aiohttp_session = aiohttp_session diff --git a/src/pipecat/services/azure/stt.py b/src/pipecat/services/azure/stt.py index 096f236a6..c1a38ec0c 100644 --- a/src/pipecat/services/azure/stt.py +++ b/src/pipecat/services/azure/stt.py @@ -96,7 +96,17 @@ class AzureSTTService(STTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to parent STTService. """ - super().__init__(sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, **kwargs) + super().__init__( + sample_rate=sample_rate, + ttfs_p99_latency=ttfs_p99_latency, + settings=AzureSTTSettings( + model=None, + region=region, + language=language_to_azure_language(language), + sample_rate=sample_rate, + ), + **kwargs, + ) self._speech_config = SpeechConfig( subscription=api_key, @@ -109,12 +119,6 @@ class AzureSTTService(STTService): self._audio_stream = None self._speech_recognizer = None - self._settings = AzureSTTSettings( - model=None, - region=region, - language=language_to_azure_language(language), - sample_rate=sample_rate, - ) def can_generate_metrics(self) -> bool: """Check if this service can generate performance metrics. diff --git a/src/pipecat/services/azure/tts.py b/src/pipecat/services/azure/tts.py index 3a176055d..b3534b28e 100644 --- a/src/pipecat/services/azure/tts.py +++ b/src/pipecat/services/azure/tts.py @@ -141,7 +141,6 @@ class AzureBaseTTSService: api_key: str, region: str, voice: str = "en-US-SaraNeural", - params: Optional[InputParams] = None, ): """Initialize Azure-specific configuration. @@ -151,25 +150,7 @@ class AzureBaseTTSService: api_key: Azure Cognitive Services subscription key. region: Azure region identifier (e.g., "eastus", "westus2"). voice: Voice name to use for synthesis. Defaults to "en-US-SaraNeural". - params: Voice and synthesis parameters configuration. """ - params = params or AzureBaseTTSService.InputParams() - - self._settings = AzureTTSSettings( - model=None, - emphasis=params.emphasis, - language=self.language_to_service_language(params.language) - if params.language - else "en-US", - pitch=params.pitch, - rate=params.rate, - role=params.role, - style=params.style, - style_degree=params.style_degree, - voice=voice, - volume=params.volume, - ) - self._api_key = api_key self._region = region self._speech_synthesizer = None @@ -289,6 +270,8 @@ class AzureTTSService(TTSService, AzureBaseTTSService): aggregate_sentences: Whether to aggregate sentences before synthesis. **kwargs: Additional arguments passed to the parent TTSService. """ + params = params or AzureBaseTTSService.InputParams() + super().__init__( aggregate_sentences=aggregate_sentences, push_text_frames=False, # We'll push text frames based on word timestamps @@ -296,11 +279,25 @@ class AzureTTSService(TTSService, AzureBaseTTSService): pause_frame_processing=True, supports_word_timestamps=True, sample_rate=sample_rate, + settings=AzureTTSSettings( + model=None, + emphasis=params.emphasis, + language=self.language_to_service_language(params.language) + if params.language + else "en-US", + pitch=params.pitch, + rate=params.rate, + role=params.role, + style=params.style, + style_degree=params.style_degree, + voice=voice, + volume=params.volume, + ), **kwargs, ) # Initialize Azure-specific functionality from mixin - self._init_azure_base(api_key=api_key, region=region, voice=voice, params=params) + self._init_azure_base(api_key=api_key, region=region, voice=voice) self._speech_config = None self._speech_synthesizer = None @@ -734,10 +731,29 @@ class AzureHttpTTSService(TTSService, AzureBaseTTSService): params: Voice and synthesis parameters configuration. **kwargs: Additional arguments passed to parent TTSService. """ - super().__init__(sample_rate=sample_rate, **kwargs) + params = params or AzureBaseTTSService.InputParams() + + super().__init__( + sample_rate=sample_rate, + settings=AzureTTSSettings( + model=None, + emphasis=params.emphasis, + language=self.language_to_service_language(params.language) + if params.language + else "en-US", + pitch=params.pitch, + rate=params.rate, + role=params.role, + style=params.style, + style_degree=params.style_degree, + voice=voice, + volume=params.volume, + ), + **kwargs, + ) # Initialize Azure-specific functionality from mixin - self._init_azure_base(api_key=api_key, region=region, voice=voice, params=params) + self._init_azure_base(api_key=api_key, region=region, voice=voice) self._speech_config = None self._speech_synthesizer = None diff --git a/src/pipecat/services/camb/tts.py b/src/pipecat/services/camb/tts.py index a2887df28..75b299569 100644 --- a/src/pipecat/services/camb/tts.py +++ b/src/pipecat/services/camb/tts.py @@ -213,11 +213,6 @@ class CambTTSService(TTSService): params: Additional voice parameters. If None, uses defaults. **kwargs: Additional arguments passed to parent TTSService. """ - super().__init__(sample_rate=sample_rate, **kwargs) - - self._api_key = api_key - self._timeout = timeout - params = params or CambTTSService.InputParams() # Warn if sample rate doesn't match model's supported rate @@ -227,16 +222,23 @@ class CambTTSService(TTSService): f"sample rate. Current rate of {sample_rate}Hz may cause issues." ) - # Build settings - self._settings = CambTTSSettings( - model=model, - voice=voice_id, - language=( - self.language_to_service_language(params.language) if params.language else "en-us" + super().__init__( + sample_rate=sample_rate, + settings=CambTTSSettings( + model=model, + voice=voice_id, + language=( + self.language_to_service_language(params.language) + if params.language + else "en-us" + ), + user_instructions=params.user_instructions, ), - user_instructions=params.user_instructions, + **kwargs, ) - self._sync_model_name_to_metrics() + + self._api_key = api_key + self._timeout = timeout self._client = None diff --git a/src/pipecat/services/cartesia/stt.py b/src/pipecat/services/cartesia/stt.py index 20ff04963..526fc9116 100644 --- a/src/pipecat/services/cartesia/stt.py +++ b/src/pipecat/services/cartesia/stt.py @@ -173,13 +173,6 @@ class CartesiaSTTService(WebsocketSTTService): **kwargs: Additional arguments passed to parent STTService. """ sample_rate = sample_rate or (live_options.sample_rate if live_options else None) - super().__init__( - sample_rate=sample_rate, - ttfs_p99_latency=ttfs_p99_latency, - keepalive_timeout=120, - keepalive_interval=30, - **kwargs, - ) default_options = CartesiaLiveOptions( model="ink-whisper", @@ -196,12 +189,19 @@ class CartesiaSTTService(WebsocketSTTService): k: v for k, v in merged_options.items() if not isinstance(v, str) or v != "None" } - self._settings = CartesiaSTTSettings( - model=merged_options["model"], - language=merged_options.get("language"), - encoding=merged_options.get("encoding", "pcm_s16le"), + super().__init__( + sample_rate=sample_rate, + ttfs_p99_latency=ttfs_p99_latency, + keepalive_timeout=120, + keepalive_interval=30, + settings=CartesiaSTTSettings( + model=merged_options["model"], + language=merged_options.get("language"), + encoding=merged_options.get("encoding", "pcm_s16le"), + ), + **kwargs, ) - self._sync_model_name_to_metrics() + self._api_key = api_key self._base_url = base_url or "api.cartesia.ai" self._receive_task = None diff --git a/src/pipecat/services/cartesia/tts.py b/src/pipecat/services/cartesia/tts.py index f45e7c54f..edf838e59 100644 --- a/src/pipecat/services/cartesia/tts.py +++ b/src/pipecat/services/cartesia/tts.py @@ -305,6 +305,8 @@ class CartesiaTTSService(AudioContextTTSService): # if we're interrupted. Cartesia gives us word-by-word timestamps. We # can use those to generate text frames ourselves aligned with the # playout timing of the audio! + params = params or CartesiaTTSService.InputParams() + super().__init__( aggregate_sentences=aggregate_sentences, push_text_frames=False, @@ -312,6 +314,20 @@ class CartesiaTTSService(AudioContextTTSService): supports_word_timestamps=True, sample_rate=sample_rate, text_aggregator=text_aggregator, + settings=CartesiaTTSSettings( + model=model, + output_container=container, + output_encoding=encoding, + output_sample_rate=0, + language=self.language_to_service_language(params.language) + if params.language + else None, + speed=params.speed, + emotion=params.emotion, + generation_config=params.generation_config, + pronunciation_dict_id=params.pronunciation_dict_id, + voice=voice_id, + ), **kwargs, ) @@ -323,26 +339,9 @@ class CartesiaTTSService(AudioContextTTSService): # and insert these tags for the purpose of the TTS service alone. self._text_aggregator = SkipTagsAggregator([("", "")]) - params = params or CartesiaTTSService.InputParams() - self._api_key = api_key self._cartesia_version = cartesia_version self._url = url - self._settings = CartesiaTTSSettings( - model=model, - output_container=container, - output_encoding=encoding, - output_sample_rate=0, - language=self.language_to_service_language(params.language) - if params.language - else None, - speed=params.speed, - emotion=params.emotion, - generation_config=params.generation_config, - pronunciation_dict_id=params.pronunciation_dict_id, - voice=voice_id, - ) - self._sync_model_name_to_metrics() self._receive_task = None @@ -727,28 +726,30 @@ class CartesiaHttpTTSService(TTSService): params: Additional input parameters for voice customization. **kwargs: Additional arguments passed to the parent TTSService. """ - super().__init__(sample_rate=sample_rate, **kwargs) - params = params or CartesiaHttpTTSService.InputParams() + super().__init__( + sample_rate=sample_rate, + settings=CartesiaTTSSettings( + model=model, + voice=voice_id, + output_container=container, + output_encoding=encoding, + output_sample_rate=0, + language=self.language_to_service_language(params.language) + if params.language + else None, + speed=params.speed, + emotion=params.emotion, + generation_config=params.generation_config, + pronunciation_dict_id=params.pronunciation_dict_id, + ), + **kwargs, + ) + self._api_key = api_key self._base_url = base_url self._cartesia_version = cartesia_version - self._settings = CartesiaTTSSettings( - model=model, - voice=voice_id, - output_container=container, - output_encoding=encoding, - output_sample_rate=0, - language=self.language_to_service_language(params.language) - if params.language - else None, - speed=params.speed, - emotion=params.emotion, - generation_config=params.generation_config, - pronunciation_dict_id=params.pronunciation_dict_id, - ) - self._sync_model_name_to_metrics() self._client = AsyncCartesia( api_key=api_key, diff --git a/src/pipecat/services/deepgram/flux/stt.py b/src/pipecat/services/deepgram/flux/stt.py index f0018d5c8..d509b267e 100644 --- a/src/pipecat/services/deepgram/flux/stt.py +++ b/src/pipecat/services/deepgram/flux/stt.py @@ -207,26 +207,24 @@ class DeepgramFluxSTTService(WebsocketSTTService): # was never destroyed. # So we can keep it here as false, because inside the method send_with_retry, it will # already try to reconnect if needed. + params = params or DeepgramFluxSTTService.InputParams() super().__init__( sample_rate=sample_rate, reconnect_on_error=False, + settings=DeepgramFluxSTTSettings( + model=model, + language=Language.EN, + encoding=flux_encoding, + eager_eot_threshold=params.eager_eot_threshold, + eot_threshold=params.eot_threshold, + eot_timeout_ms=params.eot_timeout_ms, + keyterm=params.keyterm or [], + mip_opt_out=params.mip_opt_out, + tag=params.tag or [], + min_confidence=params.min_confidence, + ), **kwargs, ) - - params = params or DeepgramFluxSTTService.InputParams() - self._settings = DeepgramFluxSTTSettings( - model=model, - language=Language.EN, - encoding=flux_encoding, - eager_eot_threshold=params.eager_eot_threshold, - eot_threshold=params.eot_threshold, - eot_timeout_ms=params.eot_timeout_ms, - keyterm=params.keyterm or [], - mip_opt_out=params.mip_opt_out, - tag=params.tag or [], - min_confidence=params.min_confidence, - ) - self._sync_model_name_to_metrics() self._api_key = api_key self._url = url self._should_interrupt = should_interrupt diff --git a/src/pipecat/services/deepgram/stt.py b/src/pipecat/services/deepgram/stt.py index 0792ea3c9..aa5c2ce8d 100644 --- a/src/pipecat/services/deepgram/stt.py +++ b/src/pipecat/services/deepgram/stt.py @@ -117,7 +117,6 @@ class DeepgramSTTService(STTService): The `vad_events` option in LiveOptions is deprecated as of version 0.0.99 and will be removed in a future version. Please use the Silero VAD instead. """ sample_rate = sample_rate or (live_options.sample_rate if live_options else None) - super().__init__(sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, **kwargs) if url: import warnings @@ -155,12 +154,16 @@ class DeepgramSTTService(STTService): merged_options["language"] = merged_options["language"].value merged_live_options = LiveOptions(**merged_options) - self._settings = DeepgramSTTSettings( - model=merged_options.get("model"), - language=merged_options.get("language"), - live_options=merged_live_options, + super().__init__( + sample_rate=sample_rate, + ttfs_p99_latency=ttfs_p99_latency, + settings=DeepgramSTTSettings( + model=merged_options.get("model"), + language=merged_options.get("language"), + live_options=merged_live_options, + ), + **kwargs, ) - self._sync_model_name_to_metrics() self._addons = addons self._should_interrupt = should_interrupt diff --git a/src/pipecat/services/deepgram/stt_sagemaker.py b/src/pipecat/services/deepgram/stt_sagemaker.py index 3820f8d84..bc5eebe37 100644 --- a/src/pipecat/services/deepgram/stt_sagemaker.py +++ b/src/pipecat/services/deepgram/stt_sagemaker.py @@ -115,10 +115,6 @@ class DeepgramSageMakerSTTService(STTService): **kwargs: Additional arguments passed to the parent STTService. """ sample_rate = sample_rate or (live_options.sample_rate if live_options else None) - super().__init__(sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, **kwargs) - - self._endpoint_name = endpoint_name - self._region = region # Create default options similar to DeepgramSTTService default_options = LiveOptions( @@ -144,12 +140,19 @@ class DeepgramSageMakerSTTService(STTService): merged_options["language"] = merged_options["language"].value merged_live_options = LiveOptions(**merged_options) - self._settings = DeepgramSageMakerSTTSettings( - model=merged_options.get("model"), - language=merged_options.get("language"), - live_options=merged_live_options, + super().__init__( + sample_rate=sample_rate, + ttfs_p99_latency=ttfs_p99_latency, + settings=DeepgramSageMakerSTTSettings( + model=merged_options.get("model"), + language=merged_options.get("language"), + live_options=merged_live_options, + ), + **kwargs, ) - self._sync_model_name_to_metrics() + + self._endpoint_name = endpoint_name + self._region = region self._client: Optional[SageMakerBidiClient] = None self._response_task: Optional[asyncio.Task] = None diff --git a/src/pipecat/services/deepgram/tts.py b/src/pipecat/services/deepgram/tts.py index b3973bba2..c05b90868 100644 --- a/src/pipecat/services/deepgram/tts.py +++ b/src/pipecat/services/deepgram/tts.py @@ -101,18 +101,17 @@ class DeepgramTTSService(WebsocketTTSService): pause_frame_processing=True, push_stop_frames=True, append_trailing_space=True, + settings=DeepgramTTSSettings( + model=voice, + voice=voice, + language=None, + encoding=encoding, + ), **kwargs, ) self._api_key = api_key self._base_url = base_url - self._settings = DeepgramTTSSettings( - model=voice, - voice=voice, - language=None, - encoding=encoding, - ) - self._sync_model_name_to_metrics() self._receive_task = None self._context_id: Optional[str] = None @@ -394,18 +393,20 @@ class DeepgramHttpTTSService(TTSService): encoding: Audio encoding format. Defaults to "linear16". **kwargs: Additional arguments passed to parent TTSService class. """ - super().__init__(sample_rate=sample_rate, **kwargs) + super().__init__( + sample_rate=sample_rate, + settings=DeepgramTTSSettings( + model=voice, + voice=voice, + language=None, + encoding=encoding, + ), + **kwargs, + ) self._api_key = api_key self._session = aiohttp_session self._base_url = base_url - self._settings = DeepgramTTSSettings( - model=voice, - voice=voice, - language=None, - encoding=encoding, - ) - self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if the service can generate metrics. diff --git a/src/pipecat/services/deepgram/tts_sagemaker.py b/src/pipecat/services/deepgram/tts_sagemaker.py index 798a62bf8..b583ce76c 100644 --- a/src/pipecat/services/deepgram/tts_sagemaker.py +++ b/src/pipecat/services/deepgram/tts_sagemaker.py @@ -99,18 +99,17 @@ class DeepgramSageMakerTTSService(TTSService): push_stop_frames=True, pause_frame_processing=True, append_trailing_space=True, + settings=DeepgramSageMakerTTSSettings( + model=voice, + voice=voice, + language=None, + encoding=encoding, + ), **kwargs, ) self._endpoint_name = endpoint_name self._region = region - self._settings = DeepgramSageMakerTTSSettings( - model=voice, - voice=voice, - language=None, - encoding=encoding, - ) - self._sync_model_name_to_metrics() self._client: Optional[SageMakerBidiClient] = None self._response_task: Optional[asyncio.Task] = None diff --git a/src/pipecat/services/elevenlabs/stt.py b/src/pipecat/services/elevenlabs/stt.py index 7a821304d..c3f4300f4 100644 --- a/src/pipecat/services/elevenlabs/stt.py +++ b/src/pipecat/services/elevenlabs/stt.py @@ -261,28 +261,26 @@ class ElevenLabsSTTService(SegmentedSTTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to SegmentedSTTService. """ + params = params or ElevenLabsSTTService.InputParams() + super().__init__( sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, + settings=ElevenLabsSTTSettings( + model=model, + language=self.language_to_service_language(params.language) + if params.language + else "eng", + tag_audio_events=params.tag_audio_events, + ), **kwargs, ) - params = params or ElevenLabsSTTService.InputParams() - self._api_key = api_key self._base_url = base_url self._session = aiohttp_session self._model_id = model - self._settings = ElevenLabsSTTSettings( - model=model, - language=self.language_to_service_language(params.language) - if params.language - else "eng", - tag_audio_events=params.tag_audio_events, - ) - self._sync_model_name_to_metrics() - def can_generate_metrics(self) -> bool: """Check if the service can generate processing metrics. @@ -500,16 +498,28 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to WebsocketSTTService. """ + params = params or ElevenLabsRealtimeSTTService.InputParams() + super().__init__( sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, keepalive_timeout=10, keepalive_interval=5, + settings=ElevenLabsRealtimeSTTSettings( + model=model, + language=params.language_code, + commit_strategy=params.commit_strategy, + vad_silence_threshold_secs=params.vad_silence_threshold_secs, + vad_threshold=params.vad_threshold, + min_speech_duration_ms=params.min_speech_duration_ms, + min_silence_duration_ms=params.min_silence_duration_ms, + include_timestamps=params.include_timestamps, + enable_logging=params.enable_logging, + include_language_detection=params.include_language_detection, + ), **kwargs, ) - params = params or ElevenLabsRealtimeSTTService.InputParams() - self._api_key = api_key self._base_url = base_url self._model_id = model @@ -519,20 +529,6 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService): self._connected_event = asyncio.Event() self._connected_event.set() - self._settings = ElevenLabsRealtimeSTTSettings( - model=model, - language=params.language_code, - commit_strategy=params.commit_strategy, - vad_silence_threshold_secs=params.vad_silence_threshold_secs, - vad_threshold=params.vad_threshold, - min_speech_duration_ms=params.min_speech_duration_ms, - min_silence_duration_ms=params.min_silence_duration_ms, - include_timestamps=params.include_timestamps, - enable_logging=params.enable_logging, - include_language_detection=params.include_language_detection, - ) - self._sync_model_name_to_metrics() - def can_generate_metrics(self) -> bool: """Check if the service can generate processing metrics. diff --git a/src/pipecat/services/elevenlabs/tts.py b/src/pipecat/services/elevenlabs/tts.py index 25e1aa5dd..c68d005f1 100644 --- a/src/pipecat/services/elevenlabs/tts.py +++ b/src/pipecat/services/elevenlabs/tts.py @@ -394,6 +394,8 @@ class ElevenLabsTTSService(AudioContextTTSService): # Finally, ElevenLabs doesn't provide information on when the bot stops # speaking for a while, so we want the parent class to send TTSStopFrame # after a short period not receiving any audio. + params = params or ElevenLabsTTSService.InputParams() + super().__init__( aggregate_sentences=aggregate_sentences, push_text_frames=False, @@ -401,30 +403,27 @@ class ElevenLabsTTSService(AudioContextTTSService): pause_frame_processing=True, supports_word_timestamps=True, sample_rate=sample_rate, + settings=ElevenLabsTTSSettings( + model=model, + voice=voice_id, + language=( + self.language_to_service_language(params.language) if params.language else None + ), + stability=params.stability, + similarity_boost=params.similarity_boost, + style=params.style, + use_speaker_boost=params.use_speaker_boost, + speed=params.speed, + auto_mode=str(params.auto_mode).lower(), + enable_ssml_parsing=params.enable_ssml_parsing, + enable_logging=params.enable_logging, + apply_text_normalization=params.apply_text_normalization, + ), **kwargs, ) - params = params or ElevenLabsTTSService.InputParams() - self._api_key = api_key self._url = url - self._settings = ElevenLabsTTSSettings( - model=model, - voice=voice_id, - language=( - self.language_to_service_language(params.language) if params.language else None - ), - stability=params.stability, - similarity_boost=params.similarity_boost, - style=params.style, - use_speaker_boost=params.use_speaker_boost, - speed=params.speed, - auto_mode=str(params.auto_mode).lower(), - enable_ssml_parsing=params.enable_ssml_parsing, - enable_logging=params.enable_logging, - apply_text_normalization=params.apply_text_normalization, - ) - self._sync_model_name_to_metrics() self._output_format = "" # initialized in start() self._voice_settings = self._set_voice_settings() @@ -910,37 +909,35 @@ class ElevenLabsHttpTTSService(TTSService): aggregate_sentences: Whether to aggregate sentences within the TTSService. **kwargs: Additional arguments passed to the parent service. """ + params = params or ElevenLabsHttpTTSService.InputParams() + super().__init__( aggregate_sentences=aggregate_sentences, push_text_frames=False, push_stop_frames=True, supports_word_timestamps=True, sample_rate=sample_rate, + settings=ElevenLabsHttpTTSSettings( + model=model, + voice=voice_id, + language=self.language_to_service_language(params.language) + if params.language + else None, + optimize_streaming_latency=params.optimize_streaming_latency, + stability=params.stability, + similarity_boost=params.similarity_boost, + style=params.style, + use_speaker_boost=params.use_speaker_boost, + speed=params.speed, + apply_text_normalization=params.apply_text_normalization, + ), **kwargs, ) - params = params or ElevenLabsHttpTTSService.InputParams() - self._api_key = api_key self._base_url = base_url - self._params = params self._session = aiohttp_session - self._settings = ElevenLabsHttpTTSSettings( - model=model, - voice=voice_id, - language=self.language_to_service_language(params.language) - if params.language - else None, - optimize_streaming_latency=params.optimize_streaming_latency, - stability=params.stability, - similarity_boost=params.similarity_boost, - style=params.style, - use_speaker_boost=params.use_speaker_boost, - speed=params.speed, - apply_text_normalization=params.apply_text_normalization, - ) - self._sync_model_name_to_metrics() self._output_format = "" # initialized in start() self._voice_settings = self._set_voice_settings() self._pronunciation_dictionary_locators = params.pronunciation_dictionary_locators diff --git a/src/pipecat/services/fal/image.py b/src/pipecat/services/fal/image.py index fd9d9a22d..c16d31b43 100644 --- a/src/pipecat/services/fal/image.py +++ b/src/pipecat/services/fal/image.py @@ -13,6 +13,7 @@ for creating images from text prompts using various AI models. import asyncio import io import os +from dataclasses import dataclass from typing import AsyncGenerator, Dict, Optional, Union import aiohttp @@ -22,6 +23,7 @@ from pydantic import BaseModel from pipecat.frames.frames import ErrorFrame, Frame, URLImageRawFrame from pipecat.services.image_service import ImageGenService +from pipecat.services.settings import ImageGenSettings try: import fal_client @@ -31,6 +33,15 @@ except ModuleNotFoundError as e: raise Exception(f"Missing module: {e}") +@dataclass +class FalImageGenSettings(ImageGenSettings): + """Settings for the Fal image generation service. + + Parameters: + model: Fal.ai model identifier. + """ + + class FalImageGenService(ImageGenService): """Fal's image generation service. @@ -77,9 +88,7 @@ class FalImageGenService(ImageGenService): key: Optional API key for Fal.ai. If provided, sets FAL_KEY environment variable. **kwargs: Additional arguments passed to parent ImageGenService. """ - super().__init__(**kwargs) - self._settings.model = model - self._sync_model_name_to_metrics() + super().__init__(settings=FalImageGenSettings(model=model), **kwargs) self._params = params self._aiohttp_session = aiohttp_session if key: diff --git a/src/pipecat/services/fal/stt.py b/src/pipecat/services/fal/stt.py index 923c3c2ea..bf70c1c2a 100644 --- a/src/pipecat/services/fal/stt.py +++ b/src/pipecat/services/fal/stt.py @@ -207,14 +207,23 @@ class FalSTTService(SegmentedSTTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to SegmentedSTTService. """ + params = params or FalSTTService.InputParams() + super().__init__( sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, + settings=FalSTTSettings( + model=None, + language=self.language_to_service_language(params.language) + if params.language + else "en", + task=params.task, + chunk_level=params.chunk_level, + version=params.version, + ), **kwargs, ) - params = params or FalSTTService.InputParams() - if api_key: os.environ["FAL_KEY"] = api_key elif "FAL_KEY" not in os.environ: @@ -223,15 +232,6 @@ class FalSTTService(SegmentedSTTService): ) self._fal_client = fal_client.AsyncClient(key=api_key or os.getenv("FAL_KEY")) - self._settings = FalSTTSettings( - model=None, - language=self.language_to_service_language(params.language) - if params.language - else "en", - task=params.task, - chunk_level=params.chunk_level, - version=params.version, - ) def can_generate_metrics(self) -> bool: """Check if the service can generate processing metrics. diff --git a/src/pipecat/services/fish/tts.py b/src/pipecat/services/fish/tts.py index 1927b6cac..9f9d753de 100644 --- a/src/pipecat/services/fish/tts.py +++ b/src/pipecat/services/fish/tts.py @@ -138,13 +138,6 @@ class FishAudioTTSService(InterruptibleTTSService): params: Additional input parameters for voice customization. **kwargs: Additional arguments passed to the parent service. """ - super().__init__( - push_stop_frames=True, - pause_frame_processing=True, - sample_rate=sample_rate, - **kwargs, - ) - params = params or FishAudioTTSService.InputParams() # Validation for model and reference_id parameters @@ -169,25 +162,30 @@ class FishAudioTTSService(InterruptibleTTSService): ) reference_id = model + super().__init__( + push_stop_frames=True, + pause_frame_processing=True, + sample_rate=sample_rate, + settings=FishAudioTTSSettings( + model=model_id, + voice=reference_id, + fish_sample_rate=0, + latency=params.latency, + format=output_format, + normalize=params.normalize, + prosody_speed=params.prosody_speed, + prosody_volume=params.prosody_volume, + reference_id=reference_id, + ), + **kwargs, + ) + self._api_key = api_key self._base_url = "wss://api.fish.audio/v1/tts/live" self._websocket = None self._receive_task = None self._request_id = None - self._settings = FishAudioTTSSettings( - model=model_id, - voice=reference_id, - fish_sample_rate=0, - latency=params.latency, - format=output_format, - normalize=params.normalize, - prosody_speed=params.prosody_speed, - prosody_volume=params.prosody_volume, - reference_id=reference_id, - ) - self._sync_model_name_to_metrics() - def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/gladia/stt.py b/src/pipecat/services/gladia/stt.py index c1ce02d87..045a56613 100644 --- a/src/pipecat/services/gladia/stt.py +++ b/src/pipecat/services/gladia/stt.py @@ -278,14 +278,6 @@ class GladiaSTTService(WebsocketSTTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to the STTService parent class. """ - super().__init__( - sample_rate=sample_rate, - ttfs_p99_latency=ttfs_p99_latency, - keepalive_timeout=20, - keepalive_interval=5, - **kwargs, - ) - params = params or GladiaInputParams() if params.language is not None: @@ -308,11 +300,6 @@ class GladiaSTTService(WebsocketSTTService): stacklevel=2, ) - self._api_key = api_key - self._region = region - self._url = url - self._receive_task = None - # Resolve deprecated language → language_config at init time language_config = params.language_config if not language_config and params.language: @@ -320,22 +307,33 @@ class GladiaSTTService(WebsocketSTTService): if language_code: language_config = LanguageConfig(languages=[language_code], code_switching=False) - self._settings = GladiaSTTSettings( - model=model, - language=None, - encoding=params.encoding, - bit_depth=params.bit_depth, - channels=params.channels, - custom_metadata=params.custom_metadata, - endpointing=params.endpointing, - maximum_duration_without_endpointing=params.maximum_duration_without_endpointing, - language_config=language_config, - pre_processing=params.pre_processing, - realtime_processing=params.realtime_processing, - messages_config=params.messages_config, - enable_vad=params.enable_vad, + super().__init__( + sample_rate=sample_rate, + ttfs_p99_latency=ttfs_p99_latency, + keepalive_timeout=20, + keepalive_interval=5, + settings=GladiaSTTSettings( + model=model, + language=None, + encoding=params.encoding, + bit_depth=params.bit_depth, + channels=params.channels, + custom_metadata=params.custom_metadata, + endpointing=params.endpointing, + maximum_duration_without_endpointing=params.maximum_duration_without_endpointing, + language_config=language_config, + pre_processing=params.pre_processing, + realtime_processing=params.realtime_processing, + messages_config=params.messages_config, + enable_vad=params.enable_vad, + ), + **kwargs, ) - self._sync_model_name_to_metrics() + + self._api_key = api_key + self._region = region + self._url = url + self._receive_task = None # Session management self._session_url = None diff --git a/src/pipecat/services/google/gemini_live/llm.py b/src/pipecat/services/google/gemini_live/llm.py index 037b23bb3..d06f941c7 100644 --- a/src/pipecat/services/google/gemini_live/llm.py +++ b/src/pipecat/services/google/gemini_live/llm.py @@ -695,10 +695,38 @@ class GeminiLiveLLMService(LLMService): stacklevel=2, ) - super().__init__(base_url=base_url, **kwargs) - params = params or InputParams() + super().__init__( + base_url=base_url, + settings=GeminiLiveLLMSettings( + model=model, + frequency_penalty=params.frequency_penalty, + max_tokens=params.max_tokens, + presence_penalty=params.presence_penalty, + temperature=params.temperature, + top_k=params.top_k, + top_p=params.top_p, + seed=None, + filter_incomplete_user_turns=False, + user_turn_completion_config=None, + modalities=params.modalities, + language=language_to_gemini_language(params.language) + if params.language + else "en-US", + media_resolution=params.media_resolution, + vad=params.vad, + context_window_compression=params.context_window_compression.model_dump() + if params.context_window_compression + else {}, + thinking=params.thinking or {}, + enable_affective_dialog=params.enable_affective_dialog or False, + proactivity=params.proactivity or {}, + extra=params.extra if isinstance(params.extra, dict) else {}, + ), + **kwargs, + ) + self._last_sent_time = 0 self._base_url = base_url self._voice_id = voice_id @@ -742,31 +770,6 @@ class GeminiLiveLLMService(LLMService): self._consecutive_failures = 0 self._connection_start_time = None - self._settings = GeminiLiveLLMSettings( - model=model, - frequency_penalty=params.frequency_penalty, - max_tokens=params.max_tokens, - presence_penalty=params.presence_penalty, - temperature=params.temperature, - top_k=params.top_k, - top_p=params.top_p, - seed=None, - filter_incomplete_user_turns=False, - user_turn_completion_config=None, - modalities=params.modalities, - language=self._language_code, - media_resolution=params.media_resolution, - vad=params.vad, - context_window_compression=params.context_window_compression.model_dump() - if params.context_window_compression - else {}, - thinking=params.thinking or {}, - enable_affective_dialog=params.enable_affective_dialog or False, - proactivity=params.proactivity or {}, - extra=params.extra if isinstance(params.extra, dict) else {}, - ) - self._sync_model_name_to_metrics() - self._file_api_base_url = file_api_base_url self._file_api: Optional[GeminiFileAPI] = None diff --git a/src/pipecat/services/google/image.py b/src/pipecat/services/google/image.py index f03b1da63..e69faf65e 100644 --- a/src/pipecat/services/google/image.py +++ b/src/pipecat/services/google/image.py @@ -16,6 +16,7 @@ import os # Suppress gRPC fork warnings os.environ["GRPC_ENABLE_FORK_SUPPORT"] = "false" +from dataclasses import dataclass from typing import Any, AsyncGenerator, Optional from loguru import logger @@ -25,6 +26,7 @@ from pydantic import BaseModel, Field from pipecat.frames.frames import ErrorFrame, Frame, URLImageRawFrame from pipecat.services.google.utils import update_google_client_http_options from pipecat.services.image_service import ImageGenService +from pipecat.services.settings import ImageGenSettings try: from google import genai @@ -35,6 +37,15 @@ except ModuleNotFoundError as e: raise Exception(f"Missing module: {e}") +@dataclass +class GoogleImageGenSettings(ImageGenSettings): + """Settings for the Google image generation service. + + Parameters: + model: Google Imagen model identifier. + """ + + class GoogleImageGenService(ImageGenService): """Google AI image generation service using Imagen models. @@ -72,17 +83,15 @@ class GoogleImageGenService(ImageGenService): http_options: HTTP options for the client. **kwargs: Additional arguments passed to the parent ImageGenService. """ - super().__init__(**kwargs) - self._params = params or GoogleImageGenService.InputParams() + params = params or GoogleImageGenService.InputParams() + super().__init__(settings=GoogleImageGenSettings(model=params.model), **kwargs) + self._params = params # Add client header http_options = update_google_client_http_options(http_options) self._client = genai.Client(api_key=api_key, http_options=http_options) - self._settings.model = self._params.model - self._sync_model_name_to_metrics() - def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/google/llm.py b/src/pipecat/services/google/llm.py index 1c6f56669..37ccfae9a 100644 --- a/src/pipecat/services/google/llm.py +++ b/src/pipecat/services/google/llm.py @@ -793,29 +793,29 @@ class GoogleLLMService(LLMService): http_options: HTTP options for the client. **kwargs: Additional arguments passed to parent class. """ - super().__init__(**kwargs) - params = params or GoogleLLMService.InputParams() + super().__init__( + settings=GoogleLLMSettings( + model=model, + max_tokens=params.max_tokens, + temperature=params.temperature, + top_k=params.top_k, + top_p=params.top_p, + frequency_penalty=None, + presence_penalty=None, + seed=None, + filter_incomplete_user_turns=False, + user_turn_completion_config=None, + thinking=params.thinking, + extra=params.extra if isinstance(params.extra, dict) else {}, + ), + **kwargs, + ) + self._api_key = api_key self._system_instruction = system_instruction self._http_options = update_google_client_http_options(http_options) - - self._settings = GoogleLLMSettings( - model=model, - max_tokens=params.max_tokens, - temperature=params.temperature, - top_k=params.top_k, - top_p=params.top_p, - frequency_penalty=None, - presence_penalty=None, - seed=None, - filter_incomplete_user_turns=False, - user_turn_completion_config=None, - thinking=params.thinking, - extra=params.extra if isinstance(params.extra, dict) else {}, - ) - self._sync_model_name_to_metrics() self._tools = tools self._tool_config = tool_config diff --git a/src/pipecat/services/google/stt.py b/src/pipecat/services/google/stt.py index ac3afa7a3..95d91d462 100644 --- a/src/pipecat/services/google/stt.py +++ b/src/pipecat/services/google/stt.py @@ -499,10 +499,29 @@ class GoogleSTTService(STTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to STTService. """ - super().__init__(sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, **kwargs) - params = params or GoogleSTTService.InputParams() + super().__init__( + sample_rate=sample_rate, + ttfs_p99_latency=ttfs_p99_latency, + settings=GoogleSTTSettings( + language=None, + languages=list(params.language_list), + language_codes=None, + model=params.model, + use_separate_recognition_per_channel=params.use_separate_recognition_per_channel, + enable_automatic_punctuation=params.enable_automatic_punctuation, + enable_spoken_punctuation=params.enable_spoken_punctuation, + enable_spoken_emojis=params.enable_spoken_emojis, + profanity_filter=params.profanity_filter, + enable_word_time_offsets=params.enable_word_time_offsets, + enable_word_confidence=params.enable_word_confidence, + enable_interim_results=params.enable_interim_results, + enable_voice_activity_events=params.enable_voice_activity_events, + ), + **kwargs, + ) + self._location = location self._stream = None self._config = None @@ -553,22 +572,6 @@ class GoogleSTTService(STTService): self._client = speech_v2.SpeechAsyncClient(credentials=creds, client_options=client_options) - self._settings = GoogleSTTSettings( - language=None, - languages=list(params.language_list), - language_codes=None, - model=params.model, - use_separate_recognition_per_channel=params.use_separate_recognition_per_channel, - enable_automatic_punctuation=params.enable_automatic_punctuation, - enable_spoken_punctuation=params.enable_spoken_punctuation, - enable_spoken_emojis=params.enable_spoken_emojis, - profanity_filter=params.profanity_filter, - enable_word_time_offsets=params.enable_word_time_offsets, - enable_word_confidence=params.enable_word_confidence, - enable_interim_results=params.enable_interim_results, - enable_voice_activity_events=params.enable_voice_activity_events, - ) - def can_generate_metrics(self) -> bool: """Check if the service can generate metrics. diff --git a/src/pipecat/services/google/tts.py b/src/pipecat/services/google/tts.py index 3416ee6d4..80c71b10f 100644 --- a/src/pipecat/services/google/tts.py +++ b/src/pipecat/services/google/tts.py @@ -602,25 +602,28 @@ class GoogleHttpTTSService(TTSService): params: Voice customization parameters including pitch, rate, volume, etc. **kwargs: Additional arguments passed to parent TTSService. """ - super().__init__(sample_rate=sample_rate, **kwargs) - params = params or GoogleHttpTTSService.InputParams() - self._location = location - self._settings = GoogleHttpTTSSettings( - model=None, - pitch=params.pitch, - rate=params.rate, - speaking_rate=params.speaking_rate, - volume=params.volume, - emphasis=params.emphasis, - language=self.language_to_service_language(params.language) - if params.language - else "en-US", - gender=params.gender, - google_style=params.google_style, - voice=voice_id, + super().__init__( + sample_rate=sample_rate, + settings=GoogleHttpTTSSettings( + model=None, + pitch=params.pitch, + rate=params.rate, + speaking_rate=params.speaking_rate, + volume=params.volume, + emphasis=params.emphasis, + language=self.language_to_service_language(params.language) + if params.language + else "en-US", + gender=params.gender, + google_style=params.google_style, + voice=voice_id, + ), + **kwargs, ) + + self._location = location self._client: texttospeech_v1.TextToSpeechAsyncClient = self._create_client( credentials, credentials_path ) @@ -1016,19 +1019,22 @@ class GoogleTTSService(GoogleBaseTTSService): params: Language configuration parameters. **kwargs: Additional arguments passed to parent TTSService. """ - super().__init__(sample_rate=sample_rate, **kwargs) - params = params or GoogleTTSService.InputParams() - self._location = location - self._settings = GoogleStreamTTSSettings( - model=None, - language=self.language_to_service_language(params.language) - if params.language - else "en-US", - speaking_rate=params.speaking_rate, - voice=voice_id, + super().__init__( + sample_rate=sample_rate, + settings=GoogleStreamTTSSettings( + model=None, + language=self.language_to_service_language(params.language) + if params.language + else "en-US", + speaking_rate=params.speaking_rate, + voice=voice_id, + ), + **kwargs, ) + + self._location = location self._voice_cloning_key = voice_cloning_key self._client: texttospeech_v1.TextToSpeechAsyncClient = self._create_client( credentials, credentials_path @@ -1222,26 +1228,28 @@ class GeminiTTSService(GoogleBaseTTSService): f"Google TTS only supports {self.GOOGLE_SAMPLE_RATE}Hz sample rate. " f"Current rate of {sample_rate}Hz may cause issues." ) - super().__init__(sample_rate=sample_rate, **kwargs) - params = params or GeminiTTSService.InputParams() if voice_id not in self.AVAILABLE_VOICES: logger.warning(f"Voice '{voice_id}' not in known voices list. Using anyway.") - self._location = location - self._model = model - self._settings = GeminiTTSSettings( - model=None, - language=self.language_to_service_language(params.language) - if params.language - else "en-US", - prompt=params.prompt, - multi_speaker=params.multi_speaker, - speaker_configs=params.speaker_configs, - voice=voice_id, + super().__init__( + sample_rate=sample_rate, + settings=GeminiTTSSettings( + model=None, + language=self.language_to_service_language(params.language) + if params.language + else "en-US", + prompt=params.prompt, + multi_speaker=params.multi_speaker, + speaker_configs=params.speaker_configs, + voice=voice_id, + ), + **kwargs, ) + self._location = location + self._model = model self._client: texttospeech_v1.TextToSpeechAsyncClient = self._create_client( credentials, credentials_path ) diff --git a/src/pipecat/services/gradium/stt.py b/src/pipecat/services/gradium/stt.py index 4ec7bf6ff..ac35c6e52 100644 --- a/src/pipecat/services/gradium/stt.py +++ b/src/pipecat/services/gradium/stt.py @@ -129,8 +129,6 @@ class GradiumSTTService(WebsocketSTTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to parent STTService class. """ - super().__init__(sample_rate=SAMPLE_RATE, ttfs_p99_latency=ttfs_p99_latency, **kwargs) - if json_config is not None: import warnings @@ -140,19 +138,24 @@ class GradiumSTTService(WebsocketSTTService): stacklevel=2, ) + params = params or GradiumSTTService.InputParams() + + super().__init__( + sample_rate=SAMPLE_RATE, + ttfs_p99_latency=ttfs_p99_latency, + settings=GradiumSTTSettings( + model=None, + language=params.language, + delay_in_frames=params.delay_in_frames or None, + ), + **kwargs, + ) + self._api_key = api_key self._api_endpoint_base_url = api_endpoint_base_url self._websocket = None self._json_config = json_config - params = params or GradiumSTTService.InputParams() - - self._settings = GradiumSTTSettings( - model=None, - language=params.language, - delay_in_frames=params.delay_in_frames or None, - ) - self._receive_task = None self._audio_buffer = bytearray() diff --git a/src/pipecat/services/gradium/tts.py b/src/pipecat/services/gradium/tts.py index ee6e6821e..c8a83a7f2 100644 --- a/src/pipecat/services/gradium/tts.py +++ b/src/pipecat/services/gradium/tts.py @@ -85,27 +85,27 @@ class GradiumTTSService(AudioContextTTSService): params: Additional configuration parameters. **kwargs: Additional arguments passed to parent class. """ + params = params or GradiumTTSService.InputParams() + super().__init__( push_stop_frames=True, push_text_frames=False, pause_frame_processing=True, supports_word_timestamps=True, sample_rate=SAMPLE_RATE, + settings=GradiumTTSSettings( + model=model, + voice=voice_id, + language=None, + output_format="pcm", + ), **kwargs, ) - params = params or GradiumTTSService.InputParams() - # Store service configuration self._api_key = api_key self._url = url self._json_config = json_config - self._settings = GradiumTTSSettings( - model=model, - voice=voice_id, - language=None, - output_format="pcm", - ) # State tracking self._receive_task = None diff --git a/src/pipecat/services/grok/realtime/llm.py b/src/pipecat/services/grok/realtime/llm.py index 4f6a62e24..c522c6c6f 100644 --- a/src/pipecat/services/grok/realtime/llm.py +++ b/src/pipecat/services/grok/realtime/llm.py @@ -145,25 +145,27 @@ class GrokRealtimeLLMService(LLMService): start_audio_paused: Whether to start with audio input paused. Defaults to False. **kwargs: Additional arguments passed to parent LLMService. """ - super().__init__(base_url=base_url, **kwargs) + super().__init__( + base_url=base_url, + settings=GrokRealtimeLLMSettings( + model=None, + temperature=None, + max_tokens=None, + top_p=None, + top_k=None, + frequency_penalty=None, + presence_penalty=None, + seed=None, + filter_incomplete_user_turns=False, + user_turn_completion_config=None, + session_properties=session_properties or events.SessionProperties(), + ), + **kwargs, + ) self.api_key = api_key self.base_url = base_url - self._settings = GrokRealtimeLLMSettings( - model=None, - temperature=None, - max_tokens=None, - top_p=None, - top_k=None, - frequency_penalty=None, - presence_penalty=None, - seed=None, - filter_incomplete_user_turns=False, - user_turn_completion_config=None, - session_properties=session_properties or events.SessionProperties(), - ) - self._audio_input_paused = start_audio_paused self._websocket = None self._receive_task = None diff --git a/src/pipecat/services/groq/tts.py b/src/pipecat/services/groq/tts.py index cc073f8c7..7e4d40dba 100644 --- a/src/pipecat/services/groq/tts.py +++ b/src/pipecat/services/groq/tts.py @@ -99,27 +99,24 @@ class GroqTTSService(TTSService): if sample_rate != self.GROQ_SAMPLE_RATE: logger.warning(f"Groq TTS only supports {self.GROQ_SAMPLE_RATE}Hz sample rate. ") + params = params or GroqTTSService.InputParams() + super().__init__( pause_frame_processing=True, sample_rate=sample_rate, + settings=GroqTTSSettings( + model=model_name, + voice=voice_id, + language=str(params.language) if params.language else "en", + output_format=output_format, + speed=params.speed, + groq_sample_rate=sample_rate, + ), **kwargs, ) - params = params or GroqTTSService.InputParams() - self._api_key = api_key self._output_format = output_format - self._params = params - - self._settings = GroqTTSSettings( - model=model_name, - voice=voice_id, - language=str(params.language) if params.language else "en", - output_format=output_format, - speed=params.speed, - groq_sample_rate=sample_rate, - ) - self._sync_model_name_to_metrics() self._client = AsyncGroq(api_key=self._api_key) diff --git a/src/pipecat/services/hathora/stt.py b/src/pipecat/services/hathora/stt.py index a08a80aa2..84f4116f3 100644 --- a/src/pipecat/services/hathora/stt.py +++ b/src/pipecat/services/hathora/stt.py @@ -89,24 +89,23 @@ class HathoraSTTService(SegmentedSTTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to the parent class. """ + params = params or HathoraSTTService.InputParams() + super().__init__( sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, + settings=HathoraSTTSettings( + model=model, + language=params.language, + config=params.config, + ), **kwargs, ) + self._model = model self._api_key = api_key or os.getenv("HATHORA_API_KEY") self._base_url = base_url - params = params or HathoraSTTService.InputParams() - - self._settings = HathoraSTTSettings( - model=model, - language=params.language, - config=params.config, - ) - self._sync_model_name_to_metrics() - def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/hathora/tts.py b/src/pipecat/services/hathora/tts.py index 1e7662aab..8d6fb32cb 100644 --- a/src/pipecat/services/hathora/tts.py +++ b/src/pipecat/services/hathora/tts.py @@ -107,24 +107,23 @@ class HathoraTTSService(TTSService): params: Configuration parameters. **kwargs: Additional arguments passed to the parent class. """ + params = params or HathoraTTSService.InputParams() + super().__init__( sample_rate=sample_rate, + settings=HathoraTTSSettings( + model=model, + voice=voice_id, + language=None, # Not applicable here + speed=params.speed, + config=params.config, + ), **kwargs, ) self._model = model self._api_key = api_key or os.getenv("HATHORA_API_KEY") self._base_url = base_url - params = params or HathoraTTSService.InputParams() - - self._settings = HathoraTTSSettings( - model=model, - voice=voice_id, - speed=params.speed, - config=params.config, - ) - self._sync_model_name_to_metrics() - def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/hume/tts.py b/src/pipecat/services/hume/tts.py index 3fb43ff88..2a075ab36 100644 --- a/src/pipecat/services/hume/tts.py +++ b/src/pipecat/services/hume/tts.py @@ -121,11 +121,21 @@ class HumeTTSService(TTSService): f"Hume TTS streams at {HUME_SAMPLE_RATE} Hz; configured sample_rate={sample_rate}" ) + params = params or HumeTTSService.InputParams() + super().__init__( sample_rate=sample_rate, push_text_frames=False, push_stop_frames=True, supports_word_timestamps=True, + settings=HumeTTSSettings( + model=None, + voice=voice_id, + language=None, # Not applicable here + description=params.description, + speed=params.speed, + trailing_silence=params.trailing_silence, + ), **kwargs, ) @@ -135,15 +145,6 @@ class HumeTTSService(TTSService): self._client = AsyncHumeClient(api_key=api_key, httpx_client=self._http_client) - params = params or HumeTTSService.InputParams() - self._settings = HumeTTSSettings( - model=None, - voice=voice_id, - description=params.description, - speed=params.speed, - trailing_silence=params.trailing_silence, - ) - self._audio_bytes = b"" # Track cumulative time for word timestamps across utterances diff --git a/src/pipecat/services/image_service.py b/src/pipecat/services/image_service.py index 58ab58fa4..f99909444 100644 --- a/src/pipecat/services/image_service.py +++ b/src/pipecat/services/image_service.py @@ -11,11 +11,12 @@ text prompts into images. """ from abc import abstractmethod -from typing import AsyncGenerator +from typing import AsyncGenerator, Optional from pipecat.frames.frames import Frame, TextFrame from pipecat.processors.frame_processor import FrameDirection from pipecat.services.ai_service import AIService +from pipecat.services.settings import ImageGenSettings class ImageGenService(AIService): @@ -26,13 +27,20 @@ class ImageGenService(AIService): generation functionality using their specific AI service. """ - def __init__(self, **kwargs): + def __init__(self, *, settings: Optional[ImageGenSettings] = None, **kwargs): """Initialize the image generation service. Args: + settings: The runtime-updatable settings for the image generation service. **kwargs: Additional arguments passed to the parent AIService. """ - super().__init__(**kwargs) + super().__init__( + settings=settings + # Here in case subclass doesn't implement more specific settings + # (which hopefully should be rare) + or ImageGenSettings(), + **kwargs, + ) # Renders the image. Returns an Image object. @abstractmethod diff --git a/src/pipecat/services/inworld/tts.py b/src/pipecat/services/inworld/tts.py index 22bdf22ff..2fb86b4a6 100644 --- a/src/pipecat/services/inworld/tts.py +++ b/src/pipecat/services/inworld/tts.py @@ -150,16 +150,28 @@ class InworldHttpTTSService(TTSService): params: Input parameters for Inworld TTS configuration. **kwargs: Additional arguments passed to the parent class. """ + params = params or InworldHttpTTSService.InputParams() + super().__init__( push_text_frames=False, push_stop_frames=True, supports_word_timestamps=True, sample_rate=sample_rate, + settings=InworldTTSSettings( + model=model, + voice=voice_id, + language=None, + audio_encoding=encoding, + audio_sample_rate=0, + speaking_rate=params.speaking_rate, + temperature=params.temperature, + timestamp_transport_strategy=params.timestamp_transport_strategy, + auto_mode=None, # Not applicable for HTTP TTS + apply_text_normalization=None, # Not applicable for HTTP TTS + ), **kwargs, ) - params = params or InworldHttpTTSService.InputParams() - self._api_key = api_key self._session = aiohttp_session self._streaming = streaming @@ -170,23 +182,8 @@ class InworldHttpTTSService(TTSService): else: self._base_url = "https://api.inworld.ai/tts/v1/voice" - self._settings = InworldTTSSettings( - model=model, - voice=voice_id, - language=None, - audio_encoding=encoding, - audio_sample_rate=0, - speaking_rate=params.speaking_rate, - temperature=params.temperature, - timestamp_transport_strategy=params.timestamp_transport_strategy, - auto_mode=None, # Not applicable for HTTP TTS - apply_text_normalization=None, # Not applicable for HTTP TTS - ) - self._cumulative_time = 0.0 - self._sync_model_name_to_metrics() - def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. @@ -530,6 +527,8 @@ class InworldTTSService(AudioContextTTSService): append_trailing_space: Whether to append a trailing space to text before sending to TTS. **kwargs: Additional arguments passed to the parent class. """ + params = params or InworldTTSService.InputParams() + super().__init__( push_text_frames=False, push_stop_frames=True, @@ -538,25 +537,23 @@ class InworldTTSService(AudioContextTTSService): sample_rate=sample_rate, aggregate_sentences=aggregate_sentences, append_trailing_space=append_trailing_space, + settings=InworldTTSSettings( + model=model, + voice=voice_id, + language=None, + audio_encoding=encoding, + audio_sample_rate=0, + speaking_rate=params.speaking_rate, + temperature=params.temperature, + apply_text_normalization=params.apply_text_normalization, + timestamp_transport_strategy=params.timestamp_transport_strategy, + auto_mode=params.auto_mode if params.auto_mode is not None else aggregate_sentences, + ), **kwargs, ) - params = params or InworldTTSService.InputParams() - self._api_key = api_key self._url = url - self._settings = InworldTTSSettings( - model=model, - voice=voice_id, - language=None, - audio_encoding=encoding, - audio_sample_rate=0, - speaking_rate=params.speaking_rate, - temperature=params.temperature, - apply_text_normalization=params.apply_text_normalization, - timestamp_transport_strategy=params.timestamp_transport_strategy, - auto_mode=params.auto_mode if params.auto_mode is not None else aggregate_sentences, - ) self._timestamp_type = "WORD" self._buffer_settings = { @@ -575,8 +572,6 @@ class InworldTTSService(AudioContextTTSService): # Track the end time of the last word in the current generation self._generation_end_time = 0.0 - self._sync_model_name_to_metrics() - def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/kokoro/tts.py b/src/pipecat/services/kokoro/tts.py index 519c565ba..4b35fa46d 100644 --- a/src/pipecat/services/kokoro/tts.py +++ b/src/pipecat/services/kokoro/tts.py @@ -137,19 +137,20 @@ class KokoroTTSService(TTSService): **kwargs: Additional arguments passed to parent `TTSService`. """ - super().__init__(**kwargs) - params = params or KokoroTTSService.InputParams() - self._lang_code = language_to_kokoro_language(params.language) - - self._settings = KokoroTTSSettings( - model=None, - voice=voice_id, - language=language_to_kokoro_language(params.language), - lang_code=language_to_kokoro_language(params.language), + super().__init__( + settings=KokoroTTSSettings( + model=None, + voice=voice_id, + language=language_to_kokoro_language(params.language), + lang_code=language_to_kokoro_language(params.language), + ), + **kwargs, ) + self._lang_code = language_to_kokoro_language(params.language) + model = Path(model_path) if model_path else KOKORO_CACHE_DIR / "kokoro-v1.0.onnx" voices = Path(voices_path) if voices_path else KOKORO_CACHE_DIR / "voices-v1.0.bin" diff --git a/src/pipecat/services/llm_service.py b/src/pipecat/services/llm_service.py index 1102e85a1..a06423754 100644 --- a/src/pipecat/services/llm_service.py +++ b/src/pipecat/services/llm_service.py @@ -181,7 +181,11 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService): adapter_class: Type[BaseLLMAdapter] = OpenAILLMAdapter def __init__( - self, run_in_parallel: bool = True, function_call_timeout_secs: float = 10.0, **kwargs + self, + run_in_parallel: bool = True, + function_call_timeout_secs: float = 10.0, + settings: Optional[LLMSettings] = None, + **kwargs, ): """Initialize the LLM service. @@ -190,10 +194,17 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService): Defaults to True. function_call_timeout_secs: Timeout in seconds for deferred function calls. Defaults to 10.0 seconds. + settings: The runtime-updatable settings for the LLM service. **kwargs: Additional arguments passed to the parent AIService. """ - super().__init__(**kwargs) + super().__init__( + settings=settings + # Here in case subclass doesn't implement more specific settings + # (which hopefully should be rare) + or LLMSettings(), + **kwargs, + ) self._run_in_parallel = run_in_parallel self._function_call_timeout_secs = function_call_timeout_secs self._filter_incomplete_user_turns: bool = False @@ -204,7 +215,6 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService): self._sequential_runner_task: Optional[asyncio.Task] = None self._skip_tts: Optional[bool] = None self._summary_task: Optional[asyncio.Task] = None - self._settings = LLMSettings() # Here in case subclass doesn't implement more specific settings (hopefully shouldn't happen) self._register_event_handler("on_function_calls_started") self._register_event_handler("on_completion_timeout") diff --git a/src/pipecat/services/lmnt/tts.py b/src/pipecat/services/lmnt/tts.py index b7ebb19ea..a2c500ca2 100644 --- a/src/pipecat/services/lmnt/tts.py +++ b/src/pipecat/services/lmnt/tts.py @@ -118,17 +118,16 @@ class LmntTTSService(InterruptibleTTSService): push_stop_frames=True, pause_frame_processing=True, sample_rate=sample_rate, + settings=LmntTTSSettings( + model=model, + voice=voice_id, + language=self.language_to_service_language(language), + format="raw", + ), **kwargs, ) self._api_key = api_key - self._settings = LmntTTSSettings( - model=model, - voice=voice_id, - language=self.language_to_service_language(language), - format="raw", - ) - self._sync_model_name_to_metrics() self._receive_task = None self._context_id: Optional[str] = None diff --git a/src/pipecat/services/minimax/tts.py b/src/pipecat/services/minimax/tts.py index 388b7ee9e..116d24a34 100644 --- a/src/pipecat/services/minimax/tts.py +++ b/src/pipecat/services/minimax/tts.py @@ -227,35 +227,35 @@ class MiniMaxHttpTTSService(TTSService): params: Additional configuration parameters. **kwargs: Additional arguments passed to parent TTSService. """ - super().__init__(sample_rate=sample_rate, **kwargs) - params = params or MiniMaxHttpTTSService.InputParams() + super().__init__( + sample_rate=sample_rate, + settings=MiniMaxTTSSettings( + model=model, + voice=voice_id, + language=None, + stream=True, + speed=params.speed, + volume=params.volume, + pitch=params.pitch, + language_boost=None, + emotion=None, + text_normalization=None, + latex_read=None, + audio_bitrate=128000, + audio_format="pcm", + audio_channel=1, + audio_sample_rate=0, + ), + **kwargs, + ) + self._api_key = api_key self._group_id = group_id self._base_url = f"{base_url}?GroupId={group_id}" self._session = aiohttp_session - # Create voice settings - self._settings = MiniMaxTTSSettings( - model=model, - voice=voice_id, - language=None, - stream=True, - speed=params.speed, - volume=params.volume, - pitch=params.pitch, - language_boost=None, - emotion=None, - text_normalization=None, - latex_read=None, - audio_bitrate=128000, - audio_format="pcm", - audio_channel=1, - audio_sample_rate=0, - ) - self._sync_model_name_to_metrics() - # Add language boost if provided if params.language: service_lang = self.language_to_service_language(params.language) diff --git a/src/pipecat/services/moondream/vision.py b/src/pipecat/services/moondream/vision.py index 16be15ac5..53b98b77a 100644 --- a/src/pipecat/services/moondream/vision.py +++ b/src/pipecat/services/moondream/vision.py @@ -11,6 +11,7 @@ for image analysis and description generation. """ import asyncio +from dataclasses import dataclass from typing import AsyncGenerator, Optional from loguru import logger @@ -24,6 +25,7 @@ from pipecat.frames.frames import ( VisionFullResponseStartFrame, VisionTextFrame, ) +from pipecat.services.settings import VisionSettings from pipecat.services.vision_service import VisionService try: @@ -60,6 +62,15 @@ def detect_device(): return torch.device("cpu"), torch.float32 +@dataclass +class MoondreamSettings(VisionSettings): + """Settings for the Moondream vision service. + + Parameters: + model: Moondream model identifier. + """ + + class MoondreamService(VisionService): """Moondream vision-language model service. @@ -79,10 +90,7 @@ class MoondreamService(VisionService): use_cpu: Whether to force CPU usage instead of hardware acceleration. **kwargs: Additional arguments passed to the parent VisionService. """ - super().__init__(**kwargs) - - self._settings.model = model - self._sync_model_name_to_metrics() + super().__init__(settings=MoondreamSettings(model=model), **kwargs) if not use_cpu: device, dtype = detect_device() diff --git a/src/pipecat/services/neuphonic/tts.py b/src/pipecat/services/neuphonic/tts.py index e076958c4..81b366a8b 100644 --- a/src/pipecat/services/neuphonic/tts.py +++ b/src/pipecat/services/neuphonic/tts.py @@ -134,26 +134,26 @@ class NeuphonicTTSService(InterruptibleTTSService): aggregate_sentences: Whether to aggregate sentences within the TTSService. **kwargs: Additional arguments passed to parent InterruptibleTTSService. """ + params = params or NeuphonicTTSService.InputParams() + super().__init__( aggregate_sentences=aggregate_sentences, push_stop_frames=True, stop_frame_timeout_s=2.0, sample_rate=sample_rate, + settings=NeuphonicTTSSettings( + model=None, + language=self.language_to_service_language(params.language), + speed=params.speed, + encoding=encoding, + sampling_rate=sample_rate, + voice=voice_id, + ), **kwargs, ) - params = params or NeuphonicTTSService.InputParams() - self._api_key = api_key self._url = url - self._settings = NeuphonicTTSSettings( - model=None, - language=self.language_to_service_language(params.language), - speed=params.speed, - encoding=encoding, - sampling_rate=sample_rate, - voice=voice_id, - ) self._cumulative_time = 0 @@ -443,21 +443,24 @@ class NeuphonicHttpTTSService(TTSService): params: Additional input parameters for TTS configuration. **kwargs: Additional arguments passed to parent TTSService. """ - super().__init__(sample_rate=sample_rate, **kwargs) - params = params or NeuphonicHttpTTSService.InputParams() + super().__init__( + sample_rate=sample_rate, + settings=NeuphonicTTSSettings( + model=None, + voice=voice_id, + language=self.language_to_service_language(params.language) or "en", + speed=params.speed, + encoding=encoding, + sampling_rate=sample_rate, + ), + **kwargs, + ) + self._api_key = api_key self._session = aiohttp_session self._base_url = url.rstrip("/") - self._settings = NeuphonicTTSSettings( - model=None, - voice=voice_id, - language=self.language_to_service_language(params.language) or "en", - speed=params.speed, - encoding=encoding, - sampling_rate=sample_rate, - ) def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/nvidia/stt.py b/src/pipecat/services/nvidia/stt.py index 3bbe04f51..950515096 100644 --- a/src/pipecat/services/nvidia/stt.py +++ b/src/pipecat/services/nvidia/stt.py @@ -164,10 +164,18 @@ class NvidiaSTTService(STTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to STTService. """ - super().__init__(sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, **kwargs) - params = params or NvidiaSTTService.InputParams() + super().__init__( + sample_rate=sample_rate, + ttfs_p99_latency=ttfs_p99_latency, + settings=NvidiaSTTSettings( + model=model_function_map.get("model_name"), + language=params.language, + ), + **kwargs, + ) + self._server = server self._api_key = api_key self._use_ssl = use_ssl @@ -180,12 +188,6 @@ class NvidiaSTTService(STTService): self._custom_configuration = "" self._function_id = model_function_map.get("function_id") - self._settings = NvidiaSTTSettings( - model=model_function_map.get("model_name"), - language=params.language, - ) - self._sync_model_name_to_metrics() - self._asr_service = None self._queue = None self._config = None @@ -463,10 +465,24 @@ class NvidiaSegmentedSTTService(SegmentedSTTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to SegmentedSTTService """ - super().__init__(sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, **kwargs) - params = params or NvidiaSegmentedSTTService.InputParams() + super().__init__( + sample_rate=sample_rate, + ttfs_p99_latency=ttfs_p99_latency, + settings=NvidiaSegmentedSTTSettings( + model=model_function_map.get("model_name"), + language=self.language_to_service_language(params.language or Language.EN_US) + or "en-US", + profanity_filter=params.profanity_filter, + automatic_punctuation=params.automatic_punctuation, + verbatim_transcripts=params.verbatim_transcripts, + boosted_lm_words=params.boosted_lm_words, + boosted_lm_score=params.boosted_lm_score, + ), + **kwargs, + ) + # Initialize NVIDIA Riva settings self._api_key = api_key self._server = server @@ -484,17 +500,6 @@ class NvidiaSegmentedSTTService(SegmentedSTTService): self._config = None self._asr_service = None - self._settings = NvidiaSegmentedSTTSettings( - model=model_function_map.get("model_name"), - language=self.language_to_service_language(params.language or Language.EN_US) - or "en-US", - profanity_filter=params.profanity_filter, - automatic_punctuation=params.automatic_punctuation, - verbatim_transcripts=params.verbatim_transcripts, - boosted_lm_words=params.boosted_lm_words, - boosted_lm_score=params.boosted_lm_score, - ) - self._sync_model_name_to_metrics() def language_to_service_language(self, language: Language) -> Optional[str]: """Convert pipecat Language enum to NVIDIA Riva's language code. diff --git a/src/pipecat/services/nvidia/tts.py b/src/pipecat/services/nvidia/tts.py index c6a5f371e..6785e9631 100644 --- a/src/pipecat/services/nvidia/tts.py +++ b/src/pipecat/services/nvidia/tts.py @@ -103,21 +103,23 @@ class NvidiaTTSService(TTSService): use_ssl: Whether to use SSL for the NVIDIA Riva server. Defaults to True. **kwargs: Additional arguments passed to parent TTSService. """ - super().__init__(sample_rate=sample_rate, **kwargs) - params = params or NvidiaTTSService.InputParams() + super().__init__( + sample_rate=sample_rate, + settings=NvidiaTTSSettings( + model=model_function_map.get("model_name"), + voice=voice_id, + language=params.language, + quality=params.quality, + ), + **kwargs, + ) + self._server = server self._api_key = api_key self._function_id = model_function_map.get("function_id") self._use_ssl = use_ssl - self._settings = NvidiaTTSSettings( - model=model_function_map.get("model_name"), - voice=voice_id, - language=params.language, - quality=params.quality, - ) - self._sync_model_name_to_metrics() self._service = None self._config = None diff --git a/src/pipecat/services/openai/base_llm.py b/src/pipecat/services/openai/base_llm.py index 9ba0583a1..40a2672f8 100644 --- a/src/pipecat/services/openai/base_llm.py +++ b/src/pipecat/services/openai/base_llm.py @@ -133,28 +133,28 @@ class BaseOpenAILLMService(LLMService): retry_on_timeout: Whether to retry the request once if it times out. **kwargs: Additional arguments passed to the parent LLMService. """ - super().__init__(**kwargs) - params = params or BaseOpenAILLMService.InputParams() - self._settings = OpenAILLMSettings( - model=model, - frequency_penalty=params.frequency_penalty, - presence_penalty=params.presence_penalty, - seed=params.seed, - temperature=params.temperature, - top_p=params.top_p, - top_k=None, - max_tokens=params.max_tokens, - max_completion_tokens=params.max_completion_tokens, - service_tier=params.service_tier, - filter_incomplete_user_turns=False, - user_turn_completion_config=None, - extra=params.extra if isinstance(params.extra, dict) else {}, + super().__init__( + settings=OpenAILLMSettings( + model=model, + frequency_penalty=params.frequency_penalty, + presence_penalty=params.presence_penalty, + seed=params.seed, + temperature=params.temperature, + top_p=params.top_p, + top_k=None, + max_tokens=params.max_tokens, + max_completion_tokens=params.max_completion_tokens, + service_tier=params.service_tier, + filter_incomplete_user_turns=False, + user_turn_completion_config=None, + extra=params.extra if isinstance(params.extra, dict) else {}, + ), + **kwargs, ) self._retry_timeout_secs = retry_timeout_secs self._retry_on_timeout = retry_on_timeout - self._sync_model_name_to_metrics() self._full_model_name: str = "" self._client = self.create_client( api_key=api_key, diff --git a/src/pipecat/services/openai/image.py b/src/pipecat/services/openai/image.py index 36efc5987..f35a5ded8 100644 --- a/src/pipecat/services/openai/image.py +++ b/src/pipecat/services/openai/image.py @@ -11,6 +11,7 @@ for creating images from text prompts. """ import io +from dataclasses import dataclass from typing import AsyncGenerator, Literal, Optional import aiohttp @@ -24,6 +25,16 @@ from pipecat.frames.frames import ( URLImageRawFrame, ) from pipecat.services.image_service import ImageGenService +from pipecat.services.settings import ImageGenSettings + + +@dataclass +class OpenAIImageGenSettings(ImageGenSettings): + """Settings for the OpenAI image generation service. + + Parameters: + model: DALL-E model identifier. + """ class OpenAIImageGenService(ImageGenService): @@ -52,9 +63,7 @@ class OpenAIImageGenService(ImageGenService): image_size: Target size for generated images. model: DALL-E model to use for generation. Defaults to "dall-e-3". """ - super().__init__() - self._settings.model = model - self._sync_model_name_to_metrics() + super().__init__(settings=OpenAIImageGenSettings(model=model)) self._image_size = image_size self._client = AsyncOpenAI(api_key=api_key, base_url=base_url) self._aiohttp_session = aiohttp_session diff --git a/src/pipecat/services/openai/realtime/llm.py b/src/pipecat/services/openai/realtime/llm.py index a68304fdc..6665a9a75 100644 --- a/src/pipecat/services/openai/realtime/llm.py +++ b/src/pipecat/services/openai/realtime/llm.py @@ -171,25 +171,26 @@ class OpenAIRealtimeLLMService(LLMService): # Build WebSocket URL with model query parameter # Source: https://platform.openai.com/docs/guides/realtime-websocket full_url = f"{base_url}?model={model}" - super().__init__(base_url=full_url, **kwargs) + super().__init__( + base_url=full_url, + settings=OpenAIRealtimeLLMSettings( + model=model, + temperature=None, + max_tokens=None, + top_p=None, + top_k=None, + frequency_penalty=None, + presence_penalty=None, + seed=None, + filter_incomplete_user_turns=False, + user_turn_completion_config=None, + session_properties=session_properties or events.SessionProperties(), + ), + **kwargs, + ) self.api_key = api_key self.base_url = full_url - - self._settings = OpenAIRealtimeLLMSettings( - model=model, - temperature=None, - max_tokens=None, - top_p=None, - top_k=None, - frequency_penalty=None, - presence_penalty=None, - seed=None, - filter_incomplete_user_turns=False, - user_turn_completion_config=None, - session_properties=session_properties or events.SessionProperties(), - ) - self._sync_model_name_to_metrics() self._audio_input_paused = start_audio_paused self._video_input_paused = start_video_paused self._video_frame_detail = video_frame_detail diff --git a/src/pipecat/services/openai/stt.py b/src/pipecat/services/openai/stt.py index 8b690d015..9a52be114 100644 --- a/src/pipecat/services/openai/stt.py +++ b/src/pipecat/services/openai/stt.py @@ -221,6 +221,11 @@ class OpenAIRealtimeSTTService(WebsocketSTTService): super().__init__( ttfs_p99_latency=ttfs_p99_latency, + settings=OpenAIRealtimeSTTSettings( + model=model, + language=language, + prompt=prompt, + ), **kwargs, ) @@ -232,13 +237,6 @@ class OpenAIRealtimeSTTService(WebsocketSTTService): self._noise_reduction = noise_reduction self._should_interrupt = should_interrupt - self._settings = OpenAIRealtimeSTTSettings( - model=model, - language=language, - prompt=prompt, - ) - self._sync_model_name_to_metrics() - self._receive_task = None self._session_ready = False self._resampler = create_stream_resampler() diff --git a/src/pipecat/services/openai/tts.py b/src/pipecat/services/openai/tts.py index 2693bcc27..f95d79134 100644 --- a/src/pipecat/services/openai/tts.py +++ b/src/pipecat/services/openai/tts.py @@ -132,10 +132,6 @@ class OpenAITTSService(TTSService): f"OpenAI TTS only supports {self.OPENAI_SAMPLE_RATE}Hz sample rate. " f"Current rate of {sample_rate}Hz may cause issues." ) - super().__init__(sample_rate=sample_rate, **kwargs) - - self._client = AsyncOpenAI(api_key=api_key, base_url=base_url) - if instructions or speed: import warnings @@ -147,13 +143,18 @@ class OpenAITTSService(TTSService): stacklevel=2, ) - self._settings = OpenAITTSSettings( - model=model, - voice=voice, - instructions=params.instructions if params else instructions, - speed=params.speed if params else speed, + super().__init__( + sample_rate=sample_rate, + settings=OpenAITTSSettings( + model=model, + voice=voice, + instructions=params.instructions if params else instructions, + speed=params.speed if params else speed, + ), + **kwargs, ) - self._sync_model_name_to_metrics() + + self._client = AsyncOpenAI(api_key=api_key, base_url=base_url) def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/openai_realtime_beta/openai.py b/src/pipecat/services/openai_realtime_beta/openai.py index 6ffccfbef..983cd10df 100644 --- a/src/pipecat/services/openai_realtime_beta/openai.py +++ b/src/pipecat/services/openai_realtime_beta/openai.py @@ -156,25 +156,26 @@ class OpenAIRealtimeBetaLLMService(LLMService): ) full_url = f"{base_url}?model={model}" - super().__init__(base_url=full_url, **kwargs) + super().__init__( + base_url=full_url, + settings=OpenAIRealtimeBetaLLMSettings( + model=model, + temperature=None, + max_tokens=None, + top_p=None, + top_k=None, + frequency_penalty=None, + presence_penalty=None, + seed=None, + filter_incomplete_user_turns=False, + user_turn_completion_config=None, + session_properties=session_properties or events.SessionProperties(), + ), + **kwargs, + ) self.api_key = api_key self.base_url = full_url - - self._settings = OpenAIRealtimeBetaLLMSettings( - model=model, - temperature=None, - max_tokens=None, - top_p=None, - top_k=None, - frequency_penalty=None, - presence_penalty=None, - seed=None, - filter_incomplete_user_turns=False, - user_turn_completion_config=None, - session_properties=session_properties or events.SessionProperties(), - ) - self._sync_model_name_to_metrics() self._audio_input_paused = start_audio_paused self._send_transcription_frames = send_transcription_frames self._websocket = None diff --git a/src/pipecat/services/piper/tts.py b/src/pipecat/services/piper/tts.py index e6a2c6943..c4831b839 100644 --- a/src/pipecat/services/piper/tts.py +++ b/src/pipecat/services/piper/tts.py @@ -69,9 +69,10 @@ class PiperTTSService(TTSService): use_cuda: Use CUDA for GPU-accelerated inference. **kwargs: Additional arguments passed to the parent `TTSService`. """ - super().__init__(**kwargs) - - self._settings = PiperTTSSettings(model=None, voice=voice_id, language=None) + super().__init__( + settings=PiperTTSSettings(model=None, voice=voice_id, language=None), + **kwargs, + ) download_dir = download_dir or Path.cwd() @@ -199,7 +200,10 @@ class PiperHttpTTSService(TTSService): voice_id: Piper voice model identifier (e.g. `en_US-ryan-high`). **kwargs: Additional arguments passed to the parent TTSService. """ - super().__init__(**kwargs) + super().__init__( + settings=PiperHttpTTSSettings(model=None, voice=voice_id, language=None), + **kwargs, + ) if base_url.endswith("/"): logger.warning("Base URL ends with a slash, this is not allowed.") @@ -207,7 +211,6 @@ class PiperHttpTTSService(TTSService): self._base_url = base_url self._session = aiohttp_session - self._settings = PiperHttpTTSSettings(model=None, voice=voice_id, language=None) def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/resembleai/tts.py b/src/pipecat/services/resembleai/tts.py index c2ac758a7..1c2953b72 100644 --- a/src/pipecat/services/resembleai/tts.py +++ b/src/pipecat/services/resembleai/tts.py @@ -92,19 +92,19 @@ class ResembleAITTSService(AudioContextTTSService): sample_rate=sample_rate, reuse_context_id_within_turn=False, supports_word_timestamps=True, + settings=ResembleAITTSSettings( + model=None, + voice=voice_id, + language=None, + precision=precision, + output_format=output_format, + resemble_sample_rate=sample_rate, + ), **kwargs, ) self._api_key = api_key self._url = url - self._settings = ResembleAITTSSettings( - model=None, - voice=voice_id, - language=None, - precision=precision, - output_format=output_format, - resemble_sample_rate=sample_rate, - ) self._websocket = None self._request_id_counter = 0 diff --git a/src/pipecat/services/rime/tts.py b/src/pipecat/services/rime/tts.py index 484c99857..059db8178 100644 --- a/src/pipecat/services/rime/tts.py +++ b/src/pipecat/services/rime/tts.py @@ -202,6 +202,8 @@ class RimeTTSService(AudioContextTTSService): **kwargs: Additional arguments passed to parent class. """ # Initialize with parent class settings for proper frame handling + params = params or RimeTTSService.InputParams() + super().__init__( aggregate_sentences=aggregate_sentences, push_text_frames=False, @@ -210,6 +212,28 @@ class RimeTTSService(AudioContextTTSService): supports_word_timestamps=True, append_trailing_space=True, sample_rate=sample_rate, + settings=RimeTTSSettings( + model=model, + voice=voice_id, + audioFormat="pcm", + samplingRate=0, # updated in start() + language=self.language_to_service_language(params.language) + if params.language + else None, + segment=params.segment, + inlineSpeedAlpha=None, # Not applicable here + # Arcana params + repetition_penalty=params.repetition_penalty, + temperature=params.temperature, + top_p=params.top_p, + # Mistv2 params + speedAlpha=params.speed_alpha, + reduceLatency=params.reduce_latency, + pauseBetweenBrackets=params.pause_between_brackets, + phonemizeBetweenBrackets=params.phonemize_between_brackets, + noTextNormalization=params.no_text_normalization, + saveOovs=params.save_oovs, + ), **kwargs, ) @@ -221,34 +245,9 @@ class RimeTTSService(AudioContextTTSService): # and insert these tags for the purpose of the TTS service alone. self._text_aggregator = SkipTagsAggregator([("spell(", ")")]) - params = params or RimeTTSService.InputParams() - # Store service configuration self._api_key = api_key self._url = url - self._settings = RimeTTSSettings( - model=model, - voice=voice_id, - audioFormat="pcm", - samplingRate=0, # updated in start() - language=self.language_to_service_language(params.language) - if params.language - else None, - segment=params.segment, - inlineSpeedAlpha=None, # Not applicable here - # Arcana params - repetition_penalty=params.repetition_penalty, - temperature=params.temperature, - top_p=params.top_p, - # Mistv2 params - speedAlpha=params.speed_alpha, - reduceLatency=params.reduce_latency, - pauseBetweenBrackets=params.pause_between_brackets, - phonemizeBetweenBrackets=params.phonemize_between_brackets, - noTextNormalization=params.no_text_normalization, - saveOovs=params.save_oovs, - ) - self._sync_model_name_to_metrics() # State tracking self._receive_task = None @@ -657,34 +656,36 @@ class RimeHttpTTSService(TTSService): params: Additional configuration parameters. **kwargs: Additional arguments passed to parent TTSService. """ - super().__init__(sample_rate=sample_rate, **kwargs) - params = params or RimeHttpTTSService.InputParams() + super().__init__( + sample_rate=sample_rate, + settings=RimeTTSSettings( + model=model, + language=self.language_to_service_language(params.language) + if params.language + else "eng", + audioFormat="pcm", + samplingRate=0, + segment=None, + speedAlpha=params.speed_alpha, + reduceLatency=params.reduce_latency, + pauseBetweenBrackets=params.pause_between_brackets, + phonemizeBetweenBrackets=params.phonemize_between_brackets, + noTextNormalization=None, + saveOovs=None, + inlineSpeedAlpha=params.inline_speed_alpha if params.inline_speed_alpha else None, + repetition_penalty=None, + temperature=None, + top_p=None, + voice=voice_id, + ), + **kwargs, + ) + self._api_key = api_key self._session = aiohttp_session self._base_url = "https://users.rime.ai/v1/rime-tts" - self._settings = RimeTTSSettings( - model=model, - language=self.language_to_service_language(params.language) - if params.language - else "eng", - audioFormat="pcm", - samplingRate=0, - segment=None, - speedAlpha=params.speed_alpha, - reduceLatency=params.reduce_latency, - pauseBetweenBrackets=params.pause_between_brackets, - phonemizeBetweenBrackets=params.phonemize_between_brackets, - noTextNormalization=None, - saveOovs=None, - inlineSpeedAlpha=params.inline_speed_alpha if params.inline_speed_alpha else None, - repetition_penalty=None, - temperature=None, - top_p=None, - voice=voice_id, - ) - self._sync_model_name_to_metrics() def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. @@ -841,31 +842,30 @@ class RimeNonJsonTTSService(InterruptibleTTSService): aggregate_sentences: Whether to aggregate sentences within the TTSService. **kwargs: Additional arguments passed to parent class. """ + params = params or RimeNonJsonTTSService.InputParams() super().__init__( sample_rate=sample_rate, aggregate_sentences=aggregate_sentences, push_stop_frames=True, pause_frame_processing=True, append_trailing_space=True, + settings=RimeNonJsonTTSSettings( + voice=voice_id, + model=model, + audioFormat=audio_format, + samplingRate=sample_rate, + language=self.language_to_service_language(params.language) + if params.language + else None, + segment=params.segment, + repetition_penalty=params.repetition_penalty, + temperature=params.temperature, + top_p=params.top_p, + ), **kwargs, ) - params = params or RimeNonJsonTTSService.InputParams() self._api_key = api_key self._url = url - self._settings = RimeNonJsonTTSSettings( - voice=voice_id, - model=model, - audioFormat=audio_format, - samplingRate=sample_rate, - language=self.language_to_service_language(params.language) - if params.language - else None, - segment=params.segment, - repetition_penalty=params.repetition_penalty, - temperature=params.temperature, - top_p=params.top_p, - ) - self._sync_model_name_to_metrics() # Add any extra parameters for future compatibility if params.extra: self._settings.extra.update(params.extra) diff --git a/src/pipecat/services/sarvam/stt.py b/src/pipecat/services/sarvam/stt.py index 02d6e250f..379473c6f 100644 --- a/src/pipecat/services/sarvam/stt.py +++ b/src/pipecat/services/sarvam/stt.py @@ -240,11 +240,22 @@ class SarvamSTTService(STTService): f"Model '{model}' does not support language parameter (auto-detects language)." ) + # Resolve mode default from model config + mode = params.mode if params.mode is not None else self._config.default_mode + super().__init__( sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, keepalive_timeout=keepalive_timeout, keepalive_interval=keepalive_interval, + settings=SarvamSTTSettings( + model=model, + language=params.language, + prompt=params.prompt, + mode=mode, + vad_signals=params.vad_signals, + high_vad_sensitivity=params.high_vad_sensitivity, + ), **kwargs, ) @@ -268,19 +279,6 @@ class SarvamSTTService(STTService): self._socket_client = None self._receive_task = None - # Resolve mode default from model config - mode = params.mode if params.mode is not None else self._config.default_mode - - self._settings = SarvamSTTSettings( - model=model, - language=params.language, - prompt=params.prompt, - mode=mode, - vad_signals=params.vad_signals, - high_vad_sensitivity=params.high_vad_sensitivity, - ) - self._sync_model_name_to_metrics() - if params.vad_signals: self._register_event_handler("on_speech_started") self._register_event_handler("on_speech_stopped") diff --git a/src/pipecat/services/sarvam/tts.py b/src/pipecat/services/sarvam/tts.py index ade547798..7b63828a1 100644 --- a/src/pipecat/services/sarvam/tts.py +++ b/src/pipecat/services/sarvam/tts.py @@ -466,12 +466,6 @@ class SarvamHttpTTSService(TTSService): if voice_id is None: voice_id = self._config.default_speaker - super().__init__(sample_rate=sample_rate, **kwargs) - - self._api_key = api_key - self._base_url = base_url - self._session = aiohttp_session - # Validate and clamp pace to model's valid range pace = params.pace pace_min, pace_max = self._config.pace_range @@ -479,22 +473,32 @@ class SarvamHttpTTSService(TTSService): logger.warning(f"Pace {pace} is outside model range ({pace_min}-{pace_max}). Clamping.") pace = max(pace_min, min(pace_max, pace)) - # Build base settings - self._settings = SarvamHttpTTSSettings( - language=( - self.language_to_service_language(params.language) if params.language else "en-IN" + super().__init__( + sample_rate=sample_rate, + settings=SarvamHttpTTSSettings( + language=( + self.language_to_service_language(params.language) + if params.language + else "en-IN" + ), + enable_preprocessing=( + True + if self._config.preprocessing_always_enabled + else params.enable_preprocessing + ), + pace=pace, + pitch=None, + loudness=None, + temperature=None, + model=model, + voice=voice_id, ), - enable_preprocessing=( - True if self._config.preprocessing_always_enabled else params.enable_preprocessing - ), - pace=pace, - pitch=None, - loudness=None, - temperature=None, - model=model, - voice=voice_id, + **kwargs, ) - self._sync_model_name_to_metrics() + + self._api_key = api_key + self._base_url = base_url + self._session = aiohttp_session # Add parameters based on model support if self._config.supports_pitch: @@ -818,21 +822,8 @@ class SarvamTTSService(InterruptibleTTSService): if voice_id is None: voice_id = self._config.default_speaker - # Initialize parent class first - super().__init__( - aggregate_sentences=aggregate_sentences, - push_text_frames=True, - pause_frame_processing=True, - push_stop_frames=True, - sample_rate=sample_rate, - **kwargs, - ) params = params or SarvamTTSService.InputParams() - # WebSocket endpoint URL with model query parameter - self._websocket_url = f"{url}?model={model}" - self._api_key = api_key - # Validate and clamp pace to model's valid range pace = params.pace pace_min, pace_max = self._config.pace_range @@ -840,27 +831,42 @@ class SarvamTTSService(InterruptibleTTSService): logger.warning(f"Pace {pace} is outside model range ({pace_min}-{pace_max}). Clamping.") pace = max(pace_min, min(pace_max, pace)) - # Build base settings - self._settings = SarvamTTSSettings( - language=( - self.language_to_service_language(params.language) if params.language else "en-IN" + # Initialize parent class first + super().__init__( + aggregate_sentences=aggregate_sentences, + push_text_frames=True, + pause_frame_processing=True, + push_stop_frames=True, + sample_rate=sample_rate, + settings=SarvamTTSSettings( + language=( + self.language_to_service_language(params.language) + if params.language + else "en-IN" + ), + speech_sample_rate=str(sample_rate), + enable_preprocessing=( + True + if self._config.preprocessing_always_enabled + else params.enable_preprocessing + ), + min_buffer_size=params.min_buffer_size, + max_chunk_length=params.max_chunk_length, + output_audio_codec=params.output_audio_codec, + output_audio_bitrate=params.output_audio_bitrate, + pace=pace, + pitch=None, + loudness=None, + temperature=None, + model=model, + voice=voice_id, ), - speech_sample_rate=str(sample_rate), - enable_preprocessing=( - True if self._config.preprocessing_always_enabled else params.enable_preprocessing - ), - min_buffer_size=params.min_buffer_size, - max_chunk_length=params.max_chunk_length, - output_audio_codec=params.output_audio_codec, - output_audio_bitrate=params.output_audio_bitrate, - pace=pace, - pitch=None, - loudness=None, - temperature=None, - model=model, - voice=voice_id, + **kwargs, ) - self._sync_model_name_to_metrics() + + # WebSocket endpoint URL with model query parameter + self._websocket_url = f"{url}?model={model}" + self._api_key = api_key # Add parameters based on model support if self._config.supports_pitch: diff --git a/src/pipecat/services/settings.py b/src/pipecat/services/settings.py index 641cc23f5..5d215273f 100644 --- a/src/pipecat/services/settings.py +++ b/src/pipecat/services/settings.py @@ -319,6 +319,28 @@ class ServiceSettings: # --------------------------------------------------------------------------- +@dataclass +class ImageGenSettings(ServiceSettings): + """Runtime-updatable settings for image generation services. + + Used in both store and delta mode — see ``ServiceSettings``. + + Parameters: + model: Image generation model identifier. + """ + + +@dataclass +class VisionSettings(ServiceSettings): + """Runtime-updatable settings for vision services. + + Used in both store and delta mode — see ``ServiceSettings``. + + Parameters: + model: Vision model identifier. + """ + + @dataclass class LLMSettings(ServiceSettings): """Runtime-updatable settings for LLM services. diff --git a/src/pipecat/services/soniox/stt.py b/src/pipecat/services/soniox/stt.py index 3160d19a6..32cbee1f4 100644 --- a/src/pipecat/services/soniox/stt.py +++ b/src/pipecat/services/soniox/stt.py @@ -202,33 +202,32 @@ class SonioxSTTService(WebsocketSTTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to the STTService. """ + params = params or SonioxInputParams() + super().__init__( sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, keepalive_timeout=1, keepalive_interval=5, + settings=SonioxSTTSettings( + model=params.model, + language=None, + audio_format=params.audio_format, + num_channels=params.num_channels, + language_hints=params.language_hints, + language_hints_strict=params.language_hints_strict, + context=params.context, + enable_speaker_diarization=params.enable_speaker_diarization, + enable_language_identification=params.enable_language_identification, + client_reference_id=params.client_reference_id, + ), **kwargs, ) - params = params or SonioxInputParams() self._api_key = api_key self._url = url self._vad_force_turn_endpoint = vad_force_turn_endpoint - self._settings = SonioxSTTSettings( - model=params.model, - language=None, - audio_format=params.audio_format, - num_channels=params.num_channels, - language_hints=params.language_hints, - language_hints_strict=params.language_hints_strict, - context=params.context, - enable_speaker_diarization=params.enable_speaker_diarization, - enable_language_identification=params.enable_language_identification, - client_reference_id=params.client_reference_id, - ) - self._sync_model_name_to_metrics() - self._final_transcription_buffer = [] self._last_tokens_received: Optional[float] = None diff --git a/src/pipecat/services/speechmatics/stt.py b/src/pipecat/services/speechmatics/stt.py index 61bf8b69f..ac18a36e3 100644 --- a/src/pipecat/services/speechmatics/stt.py +++ b/src/pipecat/services/speechmatics/stt.py @@ -398,8 +398,6 @@ class SpeechmaticsSTTService(STTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to STTService. """ - super().__init__(sample_rate=sample_rate, ttfs_p99_latency=ttfs_p99_latency, **kwargs) - # Service parameters self._api_key: str = api_key or os.getenv("SPEECHMATICS_API_KEY") self._base_url: str = ( @@ -428,8 +426,8 @@ class SpeechmaticsSTTService(STTService): speaker_passive_format = params.speaker_passive_format or speaker_active_format # Settings — seeded from InputParams - self._settings = SpeechmaticsSTTSettings( - model=None, + settings = SpeechmaticsSTTSettings( + model=None, # Will be resolved from operating_point after config is built language=params.language, domain=params.domain, turn_detection_mode=params.turn_detection_mode, @@ -455,9 +453,17 @@ class SpeechmaticsSTTService(STTService): extra_params=params.extra_params, ) - # Build SDK config from settings + # Build SDK config from settings, then resolve model from operating_point self._client: VoiceAgentClient | None = None - self._config: VoiceAgentConfig = self._build_config() + self._config: VoiceAgentConfig = self._build_config(settings) + settings.model = self._config.operating_point.value + + super().__init__( + sample_rate=sample_rate, + ttfs_p99_latency=ttfs_p99_latency, + settings=settings, + **kwargs, + ) # Outbound frame queue self._outbound_frames: asyncio.Queue[Frame] = asyncio.Queue() @@ -468,10 +474,6 @@ class SpeechmaticsSTTService(STTService): EndOfUtteranceMode.EXTERNAL, ] - # Model + metrics (operating_point comes from the SDK config/preset) - self._settings.model = self._config.operating_point.value - self._sync_model_name_to_metrics() - # Message queue self._stt_msg_queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue() self._stt_msg_task: asyncio.Task | None = None @@ -524,7 +526,7 @@ class SpeechmaticsSTTService(STTService): logger.debug(f"{self} settings update requires reconnect: {changed.keys()}") # Connection-level fields changed — rebuild the SDK config # from the now-updated self._settings, then reconnect. - self._config = self._build_config() + self._config = self._build_config(self._settings) await self._disconnect() await self._connect() elif changed.keys() & SpeechmaticsSTTSettings.HOT_FIELDS: @@ -661,13 +663,17 @@ class SpeechmaticsSTTService(STTService): # CONFIGURATION # ============================================================================ - def _build_config(self) -> VoiceAgentConfig: - """Build a ``VoiceAgentConfig`` from the current ``self._settings``. + def _build_config(self, settings: SpeechmaticsSTTSettings) -> VoiceAgentConfig: + """Build a ``VoiceAgentConfig`` from the given settings. - Used both at init time and before reconnecting so the connection - always reflects the latest settings. + Used both at init time (with explicit settings, before + ``super().__init__`` has run) and before reconnecting so the + connection always reflects the latest settings. + + Args: + settings: Settings to build from. """ - s = self._settings + s = settings # Preset from turn detection mode config = VoiceAgentConfigPreset.load(s.turn_detection_mode.value) diff --git a/src/pipecat/services/speechmatics/tts.py b/src/pipecat/services/speechmatics/tts.py index 32fb0c2b3..1ddb895aa 100644 --- a/src/pipecat/services/speechmatics/tts.py +++ b/src/pipecat/services/speechmatics/tts.py @@ -95,7 +95,18 @@ class SpeechmaticsTTSService(TTSService): f"Speechmatics TTS only supports {self.SPEECHMATICS_SAMPLE_RATE}Hz sample rate. " f"Current rate of {sample_rate}Hz may cause issues." ) - super().__init__(sample_rate=sample_rate, **kwargs) + params = params or SpeechmaticsTTSService.InputParams() + + super().__init__( + sample_rate=sample_rate, + settings=SpeechmaticsTTSSettings( + model=None, + voice=voice_id, + language=None, + max_retries=params.max_retries, + ), + **kwargs, + ) # Service parameters self._api_key: str = api_key @@ -106,14 +117,6 @@ class SpeechmaticsTTSService(TTSService): if not self._api_key: raise ValueError("Missing Speechmatics API key") - params = params or SpeechmaticsTTSService.InputParams() - self._settings = SpeechmaticsTTSSettings( - model=None, - voice=voice_id, - language=None, - max_retries=params.max_retries, - ) - def can_generate_metrics(self) -> bool: """Check if this service can generate processing metrics. diff --git a/src/pipecat/services/stt_service.py b/src/pipecat/services/stt_service.py index 20e8cacc9..ebf007f6f 100644 --- a/src/pipecat/services/stt_service.py +++ b/src/pipecat/services/stt_service.py @@ -86,6 +86,7 @@ class STTService(AIService): ttfs_p99_latency: Optional[float] = None, keepalive_timeout: Optional[float] = None, keepalive_interval: float = 5.0, + settings: Optional[STTSettings] = None, **kwargs, ): """Initialize the STT service. @@ -109,14 +110,20 @@ class STTService(AIService): connection alive. None disables keepalive. Useful for services that close idle connections (e.g. behind a ServiceSwitcher). keepalive_interval: Seconds between idle checks when keepalive is enabled. + settings: The runtime-updatable settings for the STT service. **kwargs: Additional arguments passed to the parent AIService. """ - super().__init__(**kwargs) + super().__init__( + settings=settings + # Here in case subclass doesn't implement more specific settings + # (which hopefully should be rare) + or STTSettings(), + **kwargs, + ) self._audio_passthrough = audio_passthrough self._init_sample_rate = sample_rate self._sample_rate = 0 - self._settings = STTSettings() # Here in case subclass doesn't implement more specific settings (hopefully shouldn't happen) self._muted: bool = False self._user_id: str = "" self._ttfs_p99_latency = ttfs_p99_latency diff --git a/src/pipecat/services/tts_service.py b/src/pipecat/services/tts_service.py index e739a03d2..e36d4754f 100644 --- a/src/pipecat/services/tts_service.py +++ b/src/pipecat/services/tts_service.py @@ -147,6 +147,7 @@ class TTSService(AIService): text_filter: Optional[BaseTextFilter] = None, # Audio transport destination of the generated frames. transport_destination: Optional[str] = None, + settings: Optional[TTSSettings] = None, **kwargs, ): """Initialize the TTS service. @@ -183,9 +184,16 @@ class TTSService(AIService): Use `text_filters` instead, which allows multiple filters. transport_destination: Destination for generated audio frames. + settings: The runtime-updatable settings for the TTS service. **kwargs: Additional arguments passed to the parent AIService. """ - super().__init__(**kwargs) + super().__init__( + settings=settings + # Here in case subclass doesn't implement more specific settings + # (which hopefully should be rare) + or TTSSettings(), + **kwargs, + ) self._aggregate_sentences: bool = aggregate_sentences self._push_text_frames: bool = push_text_frames self._push_stop_frames: bool = push_stop_frames @@ -196,7 +204,6 @@ class TTSService(AIService): self._append_trailing_space: bool = append_trailing_space self._init_sample_rate = sample_rate self._sample_rate = 0 - self._settings = TTSSettings() # Here in case subclass doesn't implement more specific settings (hopefully shouldn't happen) self._text_aggregator: BaseTextAggregator = text_aggregator or SimpleTextAggregator() if text_aggregator: import warnings diff --git a/src/pipecat/services/ultravox/llm.py b/src/pipecat/services/ultravox/llm.py index 11525258a..d14c3b9ca 100644 --- a/src/pipecat/services/ultravox/llm.py +++ b/src/pipecat/services/ultravox/llm.py @@ -176,19 +176,21 @@ class UltravoxRealtimeLLMService(LLMService): May only be set with OneShotInputParams. **kwargs: Additional arguments passed to parent LLMService. """ - super().__init__(**kwargs) - self._settings = UltravoxRealtimeLLMSettings( - model=None, - temperature=None, - max_tokens=None, - top_p=None, - top_k=None, - frequency_penalty=None, - presence_penalty=None, - seed=None, - filter_incomplete_user_turns=False, - user_turn_completion_config=None, - output_medium=None, + super().__init__( + settings=UltravoxRealtimeLLMSettings( + model=None, + temperature=None, + max_tokens=None, + top_p=None, + top_k=None, + frequency_penalty=None, + presence_penalty=None, + seed=None, + filter_incomplete_user_turns=False, + user_turn_completion_config=None, + output_medium=None, + ), + **kwargs, ) self._params = params if one_shot_selected_tools: diff --git a/src/pipecat/services/vision_service.py b/src/pipecat/services/vision_service.py index d12737d84..572f3b423 100644 --- a/src/pipecat/services/vision_service.py +++ b/src/pipecat/services/vision_service.py @@ -12,11 +12,12 @@ visual content. """ from abc import abstractmethod -from typing import AsyncGenerator +from typing import AsyncGenerator, Optional from pipecat.frames.frames import Frame, UserImageRawFrame from pipecat.processors.frame_processor import FrameDirection from pipecat.services.ai_service import AIService +from pipecat.services.settings import VisionSettings class VisionService(AIService): @@ -27,13 +28,20 @@ class VisionService(AIService): with the AI service infrastructure for metrics and lifecycle management. """ - def __init__(self, **kwargs): + def __init__(self, *, settings: Optional[VisionSettings] = None, **kwargs): """Initialize the vision service. Args: + settings: The runtime-updatable settings for the vision service. **kwargs: Additional arguments passed to the parent AIService. """ - super().__init__(**kwargs) + super().__init__( + settings=settings + # Here in case subclass doesn't implement more specific settings + # (which hopefully should be rare) + or VisionSettings(), + **kwargs, + ) self._describe_text = None @abstractmethod diff --git a/src/pipecat/services/whisper/base_stt.py b/src/pipecat/services/whisper/base_stt.py index 9d2b3ab51..cf3342f4b 100644 --- a/src/pipecat/services/whisper/base_stt.py +++ b/src/pipecat/services/whisper/base_stt.py @@ -155,22 +155,23 @@ class BaseWhisperSTTService(SegmentedSTTService): Override for your deployment. See https://github.com/pipecat-ai/stt-benchmark **kwargs: Additional arguments passed to SegmentedSTTService. """ - super().__init__(ttfs_p99_latency=ttfs_p99_latency, **kwargs) + super().__init__( + ttfs_p99_latency=ttfs_p99_latency, + settings=BaseWhisperSTTSettings( + model=model, + language=self.language_to_service_language(language or Language.EN), + base_url=base_url, + prompt=prompt, + temperature=temperature, + ), + **kwargs, + ) self._client = self._create_client(api_key, base_url) - self._language = self.language_to_service_language(language or Language.EN) + self._language = self._settings.language self._prompt = prompt self._temperature = temperature self._include_prob_metrics = include_prob_metrics - self._settings = BaseWhisperSTTSettings( - model=model, - language=self._language, - base_url=base_url, - prompt=self._prompt, - temperature=self._temperature, - ) - self._sync_model_name_to_metrics() - def _create_client(self, api_key: Optional[str], base_url: Optional[str]): return AsyncOpenAI(api_key=api_key, base_url=base_url) diff --git a/src/pipecat/services/whisper/stt.py b/src/pipecat/services/whisper/stt.py index 205838314..d386d6ed2 100644 --- a/src/pipecat/services/whisper/stt.py +++ b/src/pipecat/services/whisper/stt.py @@ -233,21 +233,21 @@ class WhisperSTTService(SegmentedSTTService): language: The default language for transcription. **kwargs: Additional arguments passed to SegmentedSTTService. """ - super().__init__(**kwargs) + super().__init__( + settings=WhisperSTTSettings( + model=model if isinstance(model, str) else model.value, + language=language, + device=device, + compute_type=compute_type, + no_speech_prob=no_speech_prob, + ), + **kwargs, + ) self._device: str = device self._compute_type = compute_type self._no_speech_prob = no_speech_prob self._model: Optional[WhisperModel] = None - self._settings = WhisperSTTSettings( - model=model if isinstance(model, str) else model.value, - language=language, - device=self._device, - compute_type=self._compute_type, - no_speech_prob=self._no_speech_prob, - ) - self._sync_model_name_to_metrics() - self._load() def can_generate_metrics(self) -> bool: @@ -368,20 +368,21 @@ class WhisperSTTServiceMLX(WhisperSTTService): **kwargs: Additional arguments passed to SegmentedSTTService. """ # Skip WhisperSTTService.__init__ and call its parent directly - SegmentedSTTService.__init__(self, **kwargs) + SegmentedSTTService.__init__( + self, + settings=WhisperMLXSTTSettings( + model=model if isinstance(model, str) else model.value, + language=language, + no_speech_prob=no_speech_prob, + temperature=temperature, + engine="mlx", + ), + **kwargs, + ) self._no_speech_prob = no_speech_prob self._temperature = temperature - self._settings = WhisperMLXSTTSettings( - model=model if isinstance(model, str) else model.value, - language=language, - no_speech_prob=self._no_speech_prob, - temperature=self._temperature, - engine="mlx", - ) - self._sync_model_name_to_metrics() - # No need to call _load() as MLX Whisper loads models on demand @override diff --git a/src/pipecat/services/xtts/tts.py b/src/pipecat/services/xtts/tts.py index ab06ffb5a..8817c09b5 100644 --- a/src/pipecat/services/xtts/tts.py +++ b/src/pipecat/services/xtts/tts.py @@ -111,13 +111,15 @@ class XTTSService(TTSService): sample_rate: Audio sample rate. If None, uses default. **kwargs: Additional arguments passed to parent TTSService. """ - super().__init__(sample_rate=sample_rate, **kwargs) - - self._settings = XTTSTTSSettings( - model=None, - voice=voice_id, - language=self.language_to_service_language(language), - base_url=base_url, + super().__init__( + sample_rate=sample_rate, + settings=XTTSTTSSettings( + model=None, + voice=voice_id, + language=self.language_to_service_language(language), + base_url=base_url, + ), + **kwargs, ) self._studio_speakers: Optional[Dict[str, Any]] = None self._aiohttp_session = aiohttp_session