Remove typed-settings migration scaffolding and rename _update_settings_from_typed to _update_settings.
Now that all services use typed `ServiceSettings` objects, this removes the interim scaffolding that supported both dict-based and typed settings paths in parallel. Specifically: removes old dict-based `_update_settings(settings: Mapping)` methods from base classes, removes `isinstance(self._settings, ServiceSettings)` guards, simplifies `process_frame` branching, and renames `_update_settings_from_typed` to `_update_settings` across all ~30 service implementations. Also renames the no-arg `_update_settings()` helper on realtime services to `_send_session_update()` to avoid collision, adds `from_mapping` overrides on `GoogleLLMSettings` and `AnthropicLLMSettings` for ThinkingConfig dict-to-object conversion, and replaces a broken no-arg `_update_settings()` call in Gemini Live with a TODO.
This commit is contained in:
@@ -2113,13 +2113,13 @@ class TTSStoppedFrame(ControlFrame):
|
|||||||
class ServiceUpdateSettingsFrame(ControlFrame):
|
class ServiceUpdateSettingsFrame(ControlFrame):
|
||||||
"""Base frame for updating service settings.
|
"""Base frame for updating service settings.
|
||||||
|
|
||||||
Supports both the legacy ``settings`` dict and the new typed ``update``
|
Supports both a ``settings`` dict (for backward compatibility) and an
|
||||||
object. When both are provided, ``update`` takes precedence.
|
``update`` object. When both are provided, ``update`` takes precedence.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
settings: Dictionary of setting name to value mappings (legacy).
|
settings: Dictionary of setting name to value mappings (legacy).
|
||||||
update: Typed :class:`~pipecat.services.settings.ServiceSettings`
|
update: :class:`~pipecat.services.settings.ServiceSettings` object
|
||||||
object describing the delta to apply.
|
describing the delta to apply.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
settings: Mapping[str, Any] = field(default_factory=dict)
|
settings: Mapping[str, Any] = field(default_factory=dict)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Provides the foundation for all AI services in the Pipecat framework, including
|
|||||||
model management, settings handling, and frame processing lifecycle methods.
|
model management, settings handling, and frame processing lifecycle methods.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, AsyncGenerator, Dict, Mapping, Set
|
from typing import Any, AsyncGenerator, Dict, Set
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ class AIService(FrameProcessor):
|
|||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._model_name: str = ""
|
self._model_name: str = ""
|
||||||
self._settings: Dict[str, Any] | ServiceSettings = {}
|
self._settings: ServiceSettings = ServiceSettings()
|
||||||
self._session_properties: Dict[str, Any] = {}
|
self._session_properties: Dict[str, Any] = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -97,71 +97,22 @@ class AIService(FrameProcessor):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _update_settings(self, settings: Mapping[str, Any]):
|
async def _update_settings(self, update: ServiceSettings) -> Set[str]:
|
||||||
from pipecat.services.openai.realtime.events import SessionProperties
|
"""Apply a settings update and return the set of changed field names.
|
||||||
|
|
||||||
for key, value in settings.items():
|
The update is applied to ``_settings`` and the changed-field set is
|
||||||
logger.debug("Update request for:", key, value)
|
returned. The ``model`` field is handled specially: when it changes,
|
||||||
|
``set_model_name`` is called.
|
||||||
|
|
||||||
if key in self._settings:
|
Concrete services should override this method (calling ``super()``)
|
||||||
logger.info(f"Updating LLM setting {key} to: [{value}]")
|
to react to specific changed fields (e.g. reconnect on voice change).
|
||||||
self._settings[key] = value
|
|
||||||
elif key in SessionProperties.model_fields:
|
|
||||||
logger.debug("Attempting to update", key, value)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from pipecat.services.openai.realtime.events import TurnDetection
|
|
||||||
|
|
||||||
if isinstance(self._session_properties, SessionProperties):
|
|
||||||
current_properties = self._session_properties
|
|
||||||
else:
|
|
||||||
current_properties = SessionProperties(**self._session_properties)
|
|
||||||
|
|
||||||
if key == "turn_detection" and isinstance(value, dict):
|
|
||||||
turn_detection = TurnDetection(**value)
|
|
||||||
setattr(current_properties, key, turn_detection)
|
|
||||||
else:
|
|
||||||
setattr(current_properties, key, value)
|
|
||||||
|
|
||||||
validated_properties = SessionProperties.model_validate(
|
|
||||||
current_properties.model_dump()
|
|
||||||
)
|
|
||||||
logger.info(f"Updating LLM setting {key} to: [{value}]")
|
|
||||||
self._session_properties = validated_properties.model_dump()
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Unexpected error updating session property {key}: {e}")
|
|
||||||
elif key == "model":
|
|
||||||
logger.info(f"Updating LLM setting {key} to: [{value}]")
|
|
||||||
self.set_model_name(value)
|
|
||||||
else:
|
|
||||||
logger.warning(f"Unknown setting for {self.name} service: {key}")
|
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: ServiceSettings) -> Set[str]:
|
|
||||||
"""Apply a typed settings update and return the set of changed field names.
|
|
||||||
|
|
||||||
If ``_settings`` is a :class:`ServiceSettings` object, the update is
|
|
||||||
applied to it and the changed-field set is returned. The ``model``
|
|
||||||
field is handled specially: when it changes, ``set_model_name`` is
|
|
||||||
called.
|
|
||||||
|
|
||||||
Services that have been migrated to typed settings should override
|
|
||||||
this method (calling ``super()``) to react to specific changed fields
|
|
||||||
(e.g. reconnect on voice change).
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A typed settings delta.
|
update: A settings delta.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
if not isinstance(self._settings, ServiceSettings):
|
|
||||||
logger.warning(
|
|
||||||
f"{self.name}: received typed settings update but _settings "
|
|
||||||
f"is not a ServiceSettings — falling back to dict-based update"
|
|
||||||
)
|
|
||||||
await self._update_settings(update.to_dict())
|
|
||||||
return set()
|
|
||||||
|
|
||||||
changed = self._settings.apply_update(update)
|
changed = self._settings.apply_update(update)
|
||||||
|
|
||||||
if "model" in changed:
|
if "model" in changed:
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ from pipecat.processors.aggregators.openai_llm_context import (
|
|||||||
from pipecat.processors.frame_processor import FrameDirection
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
from pipecat.services.llm_service import FunctionCallFromLLM, LLMService
|
from pipecat.services.llm_service import FunctionCallFromLLM, LLMService
|
||||||
from pipecat.services.settings import NOT_GIVEN as _NOT_GIVEN
|
from pipecat.services.settings import NOT_GIVEN as _NOT_GIVEN
|
||||||
from pipecat.services.settings import LLMSettings
|
from pipecat.services.settings import LLMSettings, is_given
|
||||||
from pipecat.utils.tracing.service_decorators import traced_llm
|
from pipecat.utils.tracing.service_decorators import traced_llm
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -72,7 +72,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AnthropicLLMSettings(LLMSettings):
|
class AnthropicLLMSettings(LLMSettings):
|
||||||
"""Typed settings for Anthropic LLM services.
|
"""Settings for Anthropic LLM services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
enable_prompt_caching: Whether to enable prompt caching.
|
enable_prompt_caching: Whether to enable prompt caching.
|
||||||
@@ -82,6 +82,18 @@ class AnthropicLLMSettings(LLMSettings):
|
|||||||
enable_prompt_caching: Any = field(default_factory=lambda: _NOT_GIVEN)
|
enable_prompt_caching: Any = field(default_factory=lambda: _NOT_GIVEN)
|
||||||
thinking: Any = field(default_factory=lambda: _NOT_GIVEN)
|
thinking: Any = field(default_factory=lambda: _NOT_GIVEN)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_mapping(cls, settings):
|
||||||
|
"""Convert a plain dict to settings, coercing thinking dicts.
|
||||||
|
|
||||||
|
For backward compatibility, a ``thinking`` value that is a plain dict
|
||||||
|
is converted to a :class:`AnthropicLLMService.ThinkingConfig`.
|
||||||
|
"""
|
||||||
|
instance = super().from_mapping(settings)
|
||||||
|
if is_given(instance.thinking) and isinstance(instance.thinking, dict):
|
||||||
|
instance.thinking = AnthropicLLMService.ThinkingConfig(**instance.thinking)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AnthropicContextAggregatorPair:
|
class AnthropicContextAggregatorPair:
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AssemblyAISTTSettings(STTSettings):
|
class AssemblyAISTTSettings(STTSettings):
|
||||||
"""Typed settings for the AssemblyAI STT service.
|
"""Settings for the AssemblyAI STT service.
|
||||||
|
|
||||||
See :class:`AssemblyAIConnectionParams` for detailed parameter descriptions.
|
See :class:`AssemblyAIConnectionParams` for detailed parameter descriptions.
|
||||||
|
|
||||||
@@ -184,8 +184,8 @@ class AssemblyAISTTService(WebsocketSTTService):
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and reconnect if anything changed.
|
"""Apply a settings update and reconnect if anything changed.
|
||||||
|
|
||||||
Any change triggers a WebSocket reconnect since all connection
|
Any change triggers a WebSocket reconnect since all connection
|
||||||
parameters are encoded in the WebSocket URL.
|
parameters are encoded in the WebSocket URL.
|
||||||
@@ -196,7 +196,7 @@ class AssemblyAISTTService(WebsocketSTTService):
|
|||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ def language_to_async_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AsyncAITTSSettings(TTSSettings):
|
class AsyncAITTSSettings(TTSSettings):
|
||||||
"""Typed settings for Async AI TTS services.
|
"""Settings for Async AI TTS services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
output_container: Audio container format (e.g. "raw").
|
output_container: Audio container format (e.g. "raw").
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AWSBedrockLLMSettings(LLMSettings):
|
class AWSBedrockLLMSettings(LLMSettings):
|
||||||
"""Typed settings for AWS Bedrock LLM services.
|
"""Settings for AWS Bedrock LLM services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
latency: Performance mode - "standard" or "optimized".
|
latency: Performance mode - "standard" or "optimized".
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AWSTranscribeSTTSettings(STTSettings):
|
class AWSTranscribeSTTSettings(STTSettings):
|
||||||
"""Typed settings for the AWS Transcribe STT service.
|
"""Settings for the AWS Transcribe STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
sample_rate: Audio sample rate in Hz (8000 or 16000).
|
sample_rate: Audio sample rate in Hz (8000 or 16000).
|
||||||
@@ -140,13 +140,13 @@ class AWSTranscribeSTTService(WebsocketSTTService):
|
|||||||
}
|
}
|
||||||
return encoding_map.get(encoding, encoding)
|
return encoding_map.get(encoding, encoding)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update, reconnecting if needed.
|
"""Apply a settings update, reconnecting if needed.
|
||||||
|
|
||||||
Any change to connection-relevant settings (model, language, etc.)
|
Any change to connection-relevant settings (model, language, etc.)
|
||||||
triggers a WebSocket reconnect so the new configuration takes effect.
|
triggers a WebSocket reconnect so the new configuration takes effect.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if changed and self._websocket:
|
if changed and self._websocket:
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ def language_to_aws_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AWSPollyTTSSettings(TTSSettings):
|
class AWSPollyTTSSettings(TTSSettings):
|
||||||
"""Typed settings for AWS Polly TTS service.
|
"""Settings for AWS Polly TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
engine: TTS engine to use ('standard', 'neural', etc.).
|
engine: TTS engine to use ('standard', 'neural', etc.).
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AzureSTTSettings(STTSettings):
|
class AzureSTTSettings(STTSettings):
|
||||||
"""Typed settings for the Azure STT service.
|
"""Settings for the Azure STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
region: Azure region for the Speech service.
|
region: Azure region for the Speech service.
|
||||||
@@ -123,13 +123,13 @@ class AzureSTTService(STTService):
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update, reconfiguring the recognizer if needed.
|
"""Apply a settings update, reconfiguring the recognizer if needed.
|
||||||
|
|
||||||
When ``language`` changes the ``SpeechConfig`` is updated and the
|
When ``language`` changes the ``SpeechConfig`` is updated and the
|
||||||
speech recognizer is restarted so that the new language takes effect.
|
speech recognizer is restarted so that the new language takes effect.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if "language" in changed:
|
if "language" in changed:
|
||||||
# Convert Language enum to Azure language code if needed.
|
# Convert Language enum to Azure language code if needed.
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ def sample_rate_to_output_format(sample_rate: int) -> SpeechSynthesisOutputForma
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AzureTTSSettings(TTSSettings):
|
class AzureTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Azure TTS services.
|
"""Settings for Azure TTS services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
emphasis: Emphasis level for speech ("strong", "moderate", "reduced").
|
emphasis: Emphasis level for speech ("strong", "moderate", "reduced").
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ def _get_aligned_audio(buffer: bytes) -> tuple[bytes, bytes]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CambTTSSettings(TTSSettings):
|
class CambTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Camb.ai TTS service.
|
"""Settings for Camb.ai TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
user_instructions: Custom instructions for mars-instruct model only.
|
user_instructions: Custom instructions for mars-instruct model only.
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CartesiaSTTSettings(STTSettings):
|
class CartesiaSTTSettings(STTSettings):
|
||||||
"""Typed settings for the Cartesia STT service.
|
"""Settings for the Cartesia STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
encoding: Audio encoding format (e.g. ``"pcm_s16le"``).
|
encoding: Audio encoding format (e.g. ``"pcm_s16le"``).
|
||||||
@@ -294,8 +294,8 @@ class CartesiaSTTService(WebsocketSTTService):
|
|||||||
|
|
||||||
await self._disconnect_websocket()
|
await self._disconnect_websocket()
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and reconnect if anything changed.
|
"""Apply a settings update and reconnect if anything changed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A :class:`STTSettings` (or ``CartesiaSTTSettings``) delta.
|
update: A :class:`STTSettings` (or ``CartesiaSTTSettings``) delta.
|
||||||
@@ -303,7 +303,7 @@ class CartesiaSTTService(WebsocketSTTService):
|
|||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if changed:
|
if changed:
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ class CartesiaEmotion(str, Enum):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CartesiaTTSSettings(TTSSettings):
|
class CartesiaTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Cartesia TTS services.
|
"""Settings for Cartesia TTS services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
output_container: Audio container format (e.g. "raw").
|
output_container: Audio container format (e.g. "raw").
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DeepgramSTTSettings(STTSettings):
|
class DeepgramSTTSettings(STTSettings):
|
||||||
"""Typed settings for the Deepgram STT service.
|
"""Settings for the Deepgram STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
live_options: Deepgram ``LiveOptions`` for detailed configuration.
|
live_options: Deepgram ``LiveOptions`` for detailed configuration.
|
||||||
@@ -195,8 +195,8 @@ class DeepgramSTTService(STTService):
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update, keeping ``live_options`` in sync.
|
"""Apply a settings update, keeping ``live_options`` in sync.
|
||||||
|
|
||||||
Top-level ``model`` and ``language`` are the source of truth. When
|
Top-level ``model`` and ``language`` are the source of truth. When
|
||||||
they are given in *update* their values are propagated into
|
they are given in *update* their values are propagated into
|
||||||
@@ -213,7 +213,7 @@ class DeepgramSTTService(STTService):
|
|||||||
getattr(update, "language", NOT_GIVEN)
|
getattr(update, "language", NOT_GIVEN)
|
||||||
)
|
)
|
||||||
|
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DeepgramSageMakerSTTSettings(STTSettings):
|
class DeepgramSageMakerSTTSettings(STTSettings):
|
||||||
"""Typed settings for the Deepgram SageMaker STT service.
|
"""Settings for the Deepgram SageMaker STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
live_options: Deepgram ``LiveOptions`` for detailed configuration.
|
live_options: Deepgram ``LiveOptions`` for detailed configuration.
|
||||||
@@ -163,8 +163,8 @@ class DeepgramSageMakerSTTService(STTService):
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update, keeping ``live_options`` in sync.
|
"""Apply a settings update, keeping ``live_options`` in sync.
|
||||||
|
|
||||||
Top-level ``model`` and ``language`` are the source of truth. When
|
Top-level ``model`` and ``language`` are the source of truth. When
|
||||||
they change their values are propagated into ``live_options``.
|
they change their values are propagated into ``live_options``.
|
||||||
@@ -178,7 +178,7 @@ class DeepgramSageMakerSTTService(STTService):
|
|||||||
getattr(update, "language", NOT_GIVEN)
|
getattr(update, "language", NOT_GIVEN)
|
||||||
)
|
)
|
||||||
|
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DeepgramTTSSettings(TTSSettings):
|
class DeepgramTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Deepgram TTS service.
|
"""Settings for Deepgram TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
encoding: Audio encoding format (linear16, mulaw, alaw).
|
encoding: Audio encoding format (linear16, mulaw, alaw).
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ def language_to_elevenlabs_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ElevenLabsSTTSettings(STTSettings):
|
class ElevenLabsSTTSettings(STTSettings):
|
||||||
"""Typed settings for the ElevenLabs file-based STT service.
|
"""Settings for the ElevenLabs file-based STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
tag_audio_events: Whether to include audio event tags in transcription.
|
tag_audio_events: Whether to include audio event tags in transcription.
|
||||||
@@ -182,7 +182,7 @@ class ElevenLabsSTTSettings(STTSettings):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ElevenLabsRealtimeSTTSettings(STTSettings):
|
class ElevenLabsRealtimeSTTSettings(STTSettings):
|
||||||
"""Typed settings for the ElevenLabs Realtime STT service.
|
"""Settings for the ElevenLabs Realtime STT service.
|
||||||
|
|
||||||
See ``ElevenLabsRealtimeSTTService.InputParams`` for detailed descriptions.
|
See ``ElevenLabsRealtimeSTTService.InputParams`` for detailed descriptions.
|
||||||
|
|
||||||
@@ -294,8 +294,8 @@ class ElevenLabsSTTService(SegmentedSTTService):
|
|||||||
"""
|
"""
|
||||||
return language_to_elevenlabs_language(language)
|
return language_to_elevenlabs_language(language)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update.
|
"""Apply a settings update.
|
||||||
|
|
||||||
Converts language to ElevenLabs format before applying and keeps
|
Converts language to ElevenLabs format before applying and keeps
|
||||||
``_model_id`` in sync with the model setting.
|
``_model_id`` in sync with the model setting.
|
||||||
@@ -312,7 +312,7 @@ class ElevenLabsSTTService(SegmentedSTTService):
|
|||||||
if converted is not None:
|
if converted is not None:
|
||||||
update.language = converted
|
update.language = converted
|
||||||
|
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if "model" in changed:
|
if "model" in changed:
|
||||||
self._model_id = self._settings.model
|
self._model_id = self._settings.model
|
||||||
@@ -543,8 +543,8 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and reconnect if anything changed.
|
"""Apply a settings update and reconnect if anything changed.
|
||||||
|
|
||||||
Converts language to ElevenLabs format before applying and keeps
|
Converts language to ElevenLabs format before applying and keeps
|
||||||
``_model_id`` in sync.
|
``_model_id`` in sync.
|
||||||
@@ -561,7 +561,7 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
|||||||
if converted is not None:
|
if converted is not None:
|
||||||
update.language = converted
|
update.language = converted
|
||||||
|
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ def build_elevenlabs_voice_settings(
|
|||||||
"""Build voice settings dictionary for ElevenLabs based on provided settings.
|
"""Build voice settings dictionary for ElevenLabs based on provided settings.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
settings: Dictionary or typed settings containing voice settings parameters.
|
settings: Dictionary or settings containing voice settings parameters.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary of voice settings or None if no valid settings are provided.
|
Dictionary of voice settings or None if no valid settings are provided.
|
||||||
@@ -186,7 +186,7 @@ class PronunciationDictionaryLocator(BaseModel):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ElevenLabsTTSSettings(TTSSettings):
|
class ElevenLabsTTSSettings(TTSSettings):
|
||||||
"""Typed settings for the ElevenLabs WebSocket TTS service.
|
"""Settings for the ElevenLabs WebSocket TTS service.
|
||||||
|
|
||||||
Fields that appear in the WebSocket URL (``voice``, ``model``,
|
Fields that appear in the WebSocket URL (``voice``, ``model``,
|
||||||
``language``) require a full reconnect when changed. Fields that
|
``language``) require a full reconnect when changed. Fields that
|
||||||
@@ -230,7 +230,7 @@ class ElevenLabsTTSSettings(TTSSettings):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ElevenLabsHttpTTSSettings(TTSSettings):
|
class ElevenLabsHttpTTSSettings(TTSSettings):
|
||||||
"""Typed settings for the ElevenLabs HTTP TTS service.
|
"""Settings for the ElevenLabs HTTP TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
optimize_streaming_latency: Latency optimization level (0-4).
|
optimize_streaming_latency: Latency optimization level (0-4).
|
||||||
@@ -471,8 +471,8 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
|
|||||||
voice_settings[key] = val
|
voice_settings[key] = val
|
||||||
return voice_settings or None
|
return voice_settings or None
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
"""Apply a typed settings update, reconnecting as needed.
|
"""Apply a settings update, reconnecting as needed.
|
||||||
|
|
||||||
Uses the declarative ``URL_FIELDS`` and ``VOICE_SETTINGS_FIELDS``
|
Uses the declarative ``URL_FIELDS`` and ``VOICE_SETTINGS_FIELDS``
|
||||||
sets on :class:`ElevenLabsTTSSettings` to decide whether to
|
sets on :class:`ElevenLabsTTSSettings` to decide whether to
|
||||||
@@ -484,7 +484,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
|
|||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return changed
|
return changed
|
||||||
@@ -958,8 +958,8 @@ class ElevenLabsHttpTTSService(WordTTSService):
|
|||||||
def _set_voice_settings(self):
|
def _set_voice_settings(self):
|
||||||
return build_elevenlabs_voice_settings(self._settings)
|
return build_elevenlabs_voice_settings(self._settings)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and rebuild voice settings.
|
"""Apply a settings update and rebuild voice settings.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A :class:`TTSSettings` (or ``ElevenLabsHttpTTSSettings``) delta.
|
update: A :class:`TTSSettings` (or ``ElevenLabsHttpTTSSettings``) delta.
|
||||||
@@ -967,7 +967,7 @@ class ElevenLabsHttpTTSService(WordTTSService):
|
|||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if changed:
|
if changed:
|
||||||
self._voice_settings = self._set_voice_settings()
|
self._voice_settings = self._set_voice_settings()
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ def language_to_fal_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FalSTTSettings(STTSettings):
|
class FalSTTSettings(STTSettings):
|
||||||
"""Typed settings for the Fal Wizper STT service.
|
"""Settings for the Fal Wizper STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
task: Task to perform ('transcribe' or 'translate'). Defaults to
|
task: Task to perform ('transcribe' or 'translate'). Defaults to
|
||||||
@@ -251,9 +251,9 @@ class FalSTTService(SegmentedSTTService):
|
|||||||
"""
|
"""
|
||||||
return language_to_fal_language(language)
|
return language_to_fal_language(language)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update, converting language if changed."""
|
"""Apply a settings update, converting language if changed."""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if "language" in changed:
|
if "language" in changed:
|
||||||
# Convert the Language enum to a Fal language code.
|
# Convert the Language enum to a Fal language code.
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ FishAudioOutputFormat = Literal["opus", "mp3", "pcm", "wav"]
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FishAudioTTSSettings(TTSSettings):
|
class FishAudioTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Fish Audio TTS service.
|
"""Settings for Fish Audio TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
fish_sample_rate: Audio sample rate sent to the API.
|
fish_sample_rate: Audio sample rate sent to the API.
|
||||||
@@ -184,8 +184,8 @@ class FishAudioTTSService(InterruptibleTTSService):
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and reconnect if needed.
|
"""Apply a settings update and reconnect if needed.
|
||||||
|
|
||||||
Any change to voice or model triggers a WebSocket reconnect.
|
Any change to voice or model triggers a WebSocket reconnect.
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ class FishAudioTTSService(InterruptibleTTSService):
|
|||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if changed:
|
if changed:
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ class _InputParamsDescriptor:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GladiaSTTSettings(STTSettings):
|
class GladiaSTTSettings(STTSettings):
|
||||||
"""Typed settings for Gladia STT service.
|
"""Settings for Gladia STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
input_params: Gladia ``GladiaInputParams`` for detailed configuration.
|
input_params: Gladia ``GladiaInputParams`` for detailed configuration.
|
||||||
@@ -379,19 +379,19 @@ class GladiaSTTService(WebsocketSTTService):
|
|||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
await self._connect()
|
await self._connect()
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: GladiaSTTSettings) -> set[str]:
|
async def _update_settings(self, update: GladiaSTTSettings) -> set[str]:
|
||||||
"""Apply typed settings update.
|
"""Apply settings update.
|
||||||
|
|
||||||
Gladia sessions are fixed at creation time, so any change requires
|
Gladia sessions are fixed at creation time, so any change requires
|
||||||
a full session teardown and reconnect.
|
a full session teardown and reconnect.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A typed settings delta.
|
update: A settings delta.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ class InputParams(BaseModel):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GeminiLiveLLMSettings(LLMSettings):
|
class GeminiLiveLLMSettings(LLMSettings):
|
||||||
"""Typed settings for Gemini Live LLM services.
|
"""Settings for Gemini Live LLM services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
modalities: Response modalities.
|
modalities: Response modalities.
|
||||||
@@ -976,7 +976,8 @@ class GeminiLiveLLMService(LLMService):
|
|||||||
# (we have an example that does just that, actually).
|
# (we have an example that does just that, actually).
|
||||||
await self._create_single_response(frame.messages)
|
await self._create_single_response(frame.messages)
|
||||||
elif isinstance(frame, LLMSetToolsFrame):
|
elif isinstance(frame, LLMSetToolsFrame):
|
||||||
await self._update_settings()
|
# TODO: implement runtime tool updates for Gemini Live.
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ from pipecat.services.openai.llm import (
|
|||||||
OpenAIAssistantContextAggregator,
|
OpenAIAssistantContextAggregator,
|
||||||
OpenAIUserContextAggregator,
|
OpenAIUserContextAggregator,
|
||||||
)
|
)
|
||||||
from pipecat.services.settings import NOT_GIVEN, LLMSettings
|
from pipecat.services.settings import NOT_GIVEN, LLMSettings, is_given
|
||||||
from pipecat.utils.tracing.service_decorators import traced_llm
|
from pipecat.utils.tracing.service_decorators import traced_llm
|
||||||
|
|
||||||
# Suppress gRPC fork warnings
|
# Suppress gRPC fork warnings
|
||||||
@@ -675,7 +675,7 @@ class GoogleLLMContext(OpenAILLMContext):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GoogleLLMSettings(LLMSettings):
|
class GoogleLLMSettings(LLMSettings):
|
||||||
"""Typed settings for Google LLM services.
|
"""Settings for Google LLM services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
thinking: Thinking configuration.
|
thinking: Thinking configuration.
|
||||||
@@ -683,6 +683,18 @@ class GoogleLLMSettings(LLMSettings):
|
|||||||
|
|
||||||
thinking: Any = field(default_factory=lambda: NOT_GIVEN)
|
thinking: Any = field(default_factory=lambda: NOT_GIVEN)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_mapping(cls, settings):
|
||||||
|
"""Convert a plain dict to settings, coercing thinking dicts.
|
||||||
|
|
||||||
|
For backward compatibility, a ``thinking`` value that is a plain dict
|
||||||
|
is converted to a :class:`GoogleLLMService.ThinkingConfig`.
|
||||||
|
"""
|
||||||
|
instance = super().from_mapping(settings)
|
||||||
|
if is_given(instance.thinking) and isinstance(instance.thinking, dict):
|
||||||
|
instance.thinking = GoogleLLMService.ThinkingConfig(**instance.thinking)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class GoogleLLMService(LLMService):
|
class GoogleLLMService(LLMService):
|
||||||
"""Google AI (Gemini) LLM service implementation.
|
"""Google AI (Gemini) LLM service implementation.
|
||||||
@@ -1227,14 +1239,6 @@ class GoogleLLMService(LLMService):
|
|||||||
# Do nothing - we're shutting down anyway
|
# Do nothing - we're shutting down anyway
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _update_settings(self, settings):
|
|
||||||
"""Override to handle ThinkingConfig validation."""
|
|
||||||
# Convert thinking dict to ThinkingConfig if needed
|
|
||||||
if "thinking" in settings and isinstance(settings["thinking"], dict):
|
|
||||||
settings = dict(settings) # Make a copy to avoid modifying the original
|
|
||||||
settings["thinking"] = self.ThinkingConfig(**settings["thinking"])
|
|
||||||
await super()._update_settings(settings)
|
|
||||||
|
|
||||||
def create_context_aggregator(
|
def create_context_aggregator(
|
||||||
self,
|
self,
|
||||||
context: OpenAILLMContext,
|
context: OpenAILLMContext,
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ def language_to_google_stt_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GoogleSTTSettings(STTSettings):
|
class GoogleSTTSettings(STTSettings):
|
||||||
"""Typed settings for Google Cloud Speech-to-Text V2.
|
"""Settings for Google Cloud Speech-to-Text V2.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
languages: List of ``Language`` enums for recognition
|
languages: List of ``Language`` enums for recognition
|
||||||
@@ -628,10 +628,10 @@ class GoogleSTTService(STTService):
|
|||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
)
|
)
|
||||||
logger.debug(f"Switching STT languages to: {languages}")
|
logger.debug(f"Switching STT languages to: {languages}")
|
||||||
await self._update_settings_from_typed(GoogleSTTSettings(languages=list(languages)))
|
await self._update_settings(GoogleSTTSettings(languages=list(languages)))
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: GoogleSTTSettings) -> set[str]:
|
async def _update_settings(self, update: GoogleSTTSettings) -> set[str]:
|
||||||
"""Apply typed settings update and reconnect if anything changed.
|
"""Apply settings update and reconnect if anything changed.
|
||||||
|
|
||||||
Handles ``language`` from base ``set_language`` by converting it to
|
Handles ``language`` from base ``set_language`` by converting it to
|
||||||
``languages``. Emits a deprecation warning if ``language_codes`` is
|
``languages``. Emits a deprecation warning if ``language_codes`` is
|
||||||
@@ -639,7 +639,7 @@ class GoogleSTTService(STTService):
|
|||||||
Reconnects the stream on any change.
|
Reconnects the stream on any change.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A typed settings delta.
|
update: A settings delta.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
@@ -663,7 +663,7 @@ class GoogleSTTService(STTService):
|
|||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
await self._reconnect_if_needed()
|
await self._reconnect_if_needed()
|
||||||
@@ -742,7 +742,7 @@ class GoogleSTTService(STTService):
|
|||||||
"GoogleSTTSettings(...) instead.",
|
"GoogleSTTSettings(...) instead.",
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
)
|
)
|
||||||
# Build a typed settings delta from the provided options
|
# Build a settings delta from the provided options
|
||||||
update = GoogleSTTSettings()
|
update = GoogleSTTSettings()
|
||||||
|
|
||||||
if languages is not None:
|
if languages is not None:
|
||||||
@@ -770,7 +770,7 @@ class GoogleSTTService(STTService):
|
|||||||
logger.debug(f"Updating location to: {location}")
|
logger.debug(f"Updating location to: {location}")
|
||||||
self._location = location
|
self._location = location
|
||||||
|
|
||||||
await self._update_settings_from_typed(update)
|
await self._update_settings(update)
|
||||||
|
|
||||||
async def _connect(self):
|
async def _connect(self):
|
||||||
"""Initialize streaming recognition config and stream."""
|
"""Initialize streaming recognition config and stream."""
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ def language_to_gemini_tts_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GoogleHttpTTSSettings(TTSSettings):
|
class GoogleHttpTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Google HTTP TTS service.
|
"""Settings for Google HTTP TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
pitch: Voice pitch adjustment (e.g., "+2st", "-50%").
|
pitch: Voice pitch adjustment (e.g., "+2st", "-50%").
|
||||||
@@ -505,7 +505,7 @@ class GoogleHttpTTSSettings(TTSSettings):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GoogleStreamTTSSettings(TTSSettings):
|
class GoogleStreamTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Google streaming TTS service.
|
"""Settings for Google streaming TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
language: Language for synthesis. Defaults to English.
|
language: Language for synthesis. Defaults to English.
|
||||||
@@ -518,7 +518,7 @@ class GoogleStreamTTSSettings(TTSSettings):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GeminiTTSSettings(TTSSettings):
|
class GeminiTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Gemini TTS service.
|
"""Settings for Gemini TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
language: Language for synthesis. Defaults to English.
|
language: Language for synthesis. Defaults to English.
|
||||||
@@ -680,11 +680,11 @@ class GoogleHttpTTSService(TTSService):
|
|||||||
"""
|
"""
|
||||||
return language_to_google_tts_language(language)
|
return language_to_google_tts_language(language)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
"""Override to handle speaking_rate validation.
|
"""Override to handle speaking_rate validation.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: Typed settings delta. Can include 'speaking_rate' (float).
|
update: Settings delta. Can include 'speaking_rate' (float).
|
||||||
"""
|
"""
|
||||||
if isinstance(update, GoogleHttpTTSSettings) and is_given(update.speaking_rate):
|
if isinstance(update, GoogleHttpTTSSettings) and is_given(update.speaking_rate):
|
||||||
rate_value = float(update.speaking_rate)
|
rate_value = float(update.speaking_rate)
|
||||||
@@ -693,7 +693,7 @@ class GoogleHttpTTSService(TTSService):
|
|||||||
f"Invalid speaking_rate value: {rate_value}. Must be between 0.25 and 2.0"
|
f"Invalid speaking_rate value: {rate_value}. Must be between 0.25 and 2.0"
|
||||||
)
|
)
|
||||||
update.speaking_rate = NOT_GIVEN
|
update.speaking_rate = NOT_GIVEN
|
||||||
return await super()._update_settings_from_typed(update)
|
return await super()._update_settings(update)
|
||||||
|
|
||||||
def _construct_ssml(self, text: str) -> str:
|
def _construct_ssml(self, text: str) -> str:
|
||||||
ssml = "<speak>"
|
ssml = "<speak>"
|
||||||
@@ -1024,11 +1024,11 @@ class GoogleTTSService(GoogleBaseTTSService):
|
|||||||
credentials, credentials_path
|
credentials, credentials_path
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
"""Override to handle speaking_rate validation.
|
"""Override to handle speaking_rate validation.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: Typed settings delta. Can include 'speaking_rate' (float).
|
update: Settings delta. Can include 'speaking_rate' (float).
|
||||||
"""
|
"""
|
||||||
if isinstance(update, GoogleStreamTTSSettings) and is_given(update.speaking_rate):
|
if isinstance(update, GoogleStreamTTSSettings) and is_given(update.speaking_rate):
|
||||||
rate_value = float(update.speaking_rate)
|
rate_value = float(update.speaking_rate)
|
||||||
@@ -1037,7 +1037,7 @@ class GoogleTTSService(GoogleBaseTTSService):
|
|||||||
f"Invalid speaking_rate value: {rate_value}. Must be between 0.25 and 2.0"
|
f"Invalid speaking_rate value: {rate_value}. Must be between 0.25 and 2.0"
|
||||||
)
|
)
|
||||||
update.speaking_rate = NOT_GIVEN
|
update.speaking_rate = NOT_GIVEN
|
||||||
return await super()._update_settings_from_typed(update)
|
return await super()._update_settings(update)
|
||||||
|
|
||||||
@traced_tts
|
@traced_tts
|
||||||
async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]:
|
async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]:
|
||||||
@@ -1259,11 +1259,11 @@ class GeminiTTSService(GoogleBaseTTSService):
|
|||||||
f"Current rate of {self.sample_rate}Hz may cause issues."
|
f"Current rate of {self.sample_rate}Hz may cause issues."
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
"""Apply a typed settings update with voice validation.
|
"""Apply a settings update with voice validation.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: Typed settings delta. Can include 'voice', 'prompt', etc.
|
update: Settings delta. Can include 'voice', 'prompt', etc.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
@@ -1271,7 +1271,7 @@ class GeminiTTSService(GoogleBaseTTSService):
|
|||||||
if is_given(update.voice) and update.voice not in self.AVAILABLE_VOICES:
|
if is_given(update.voice) and update.voice not in self.AVAILABLE_VOICES:
|
||||||
logger.warning(f"Voice '{update.voice}' not in known voices list. Using anyway.")
|
logger.warning(f"Voice '{update.voice}' not in known voices list. Using anyway.")
|
||||||
|
|
||||||
return await super()._update_settings_from_typed(update)
|
return await super()._update_settings(update)
|
||||||
|
|
||||||
@traced_tts
|
@traced_tts
|
||||||
async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]:
|
async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]:
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ def language_to_gradium_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GradiumSTTSettings(STTSettings):
|
class GradiumSTTSettings(STTSettings):
|
||||||
"""Typed settings for the Gradium STT service.
|
"""Settings for the Gradium STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
delay_in_frames: Delay in audio frames (80ms each) before text is
|
delay_in_frames: Delay in audio frames (80ms each) before text is
|
||||||
@@ -171,8 +171,8 @@ class GradiumSTTService(WebsocketSTTService):
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update, sync params, and reconnect.
|
"""Apply a settings update, sync params, and reconnect.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A :class:`STTSettings` (or ``GradiumSTTSettings``) delta.
|
update: A :class:`STTSettings` (or ``GradiumSTTSettings``) delta.
|
||||||
@@ -180,7 +180,7 @@ class GradiumSTTService(WebsocketSTTService):
|
|||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if not changed:
|
if not changed:
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ SAMPLE_RATE = 48000
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GradiumTTSSettings(TTSSettings):
|
class GradiumTTSSettings(TTSSettings):
|
||||||
"""Typed settings for the Gradium TTS service.
|
"""Settings for the Gradium TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
output_format: Audio output format.
|
output_format: Audio output format.
|
||||||
@@ -119,8 +119,8 @@ class GradiumTTSService(InterruptibleWordTTSService):
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and reconnect if voice changed.
|
"""Apply a settings update and reconnect if voice changed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A :class:`TTSSettings` (or ``GradiumTTSSettings``) delta.
|
update: A :class:`TTSSettings` (or ``GradiumTTSSettings``) delta.
|
||||||
@@ -129,7 +129,7 @@ class GradiumTTSService(InterruptibleWordTTSService):
|
|||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
prev_voice = self._voice_id
|
prev_voice = self._voice_id
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if self._voice_id != prev_voice:
|
if self._voice_id != prev_voice:
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class CurrentAudioResponse:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GrokRealtimeLLMSettings(LLMSettings):
|
class GrokRealtimeLLMSettings(LLMSettings):
|
||||||
"""Typed settings for Grok Realtime LLM services.
|
"""Settings for Grok Realtime LLM services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
session_properties: Grok Realtime session configuration.
|
session_properties: Grok Realtime session configuration.
|
||||||
@@ -349,13 +349,13 @@ class GrokRealtimeLLMService(LLMService):
|
|||||||
frame: The frame to process.
|
frame: The frame to process.
|
||||||
direction: The direction of frame flow in the pipeline.
|
direction: The direction of frame flow in the pipeline.
|
||||||
"""
|
"""
|
||||||
# Legacy dict path: frame.settings contains SessionProperties fields,
|
# Backward-compatible dict path: frame.settings contains SessionProperties
|
||||||
# not our Settings fields, so we construct SessionProperties directly.
|
# fields, not our Settings fields, so we construct SessionProperties
|
||||||
# The new typed path (frame.update) falls through to super, which calls
|
# directly. The frame.update path falls through to super, which calls
|
||||||
# _update_settings_from_typed → our override handles the rest.
|
# _update_settings → our override handles the rest.
|
||||||
if isinstance(frame, LLMUpdateSettingsFrame) and frame.update is None:
|
if isinstance(frame, LLMUpdateSettingsFrame) and frame.update is None:
|
||||||
self._settings.session_properties = events.SessionProperties(**frame.settings)
|
self._settings.session_properties = events.SessionProperties(**frame.settings)
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -379,7 +379,7 @@ class GrokRealtimeLLMService(LLMService):
|
|||||||
elif isinstance(frame, LLMMessagesAppendFrame):
|
elif isinstance(frame, LLMMessagesAppendFrame):
|
||||||
await self._handle_messages_append(frame)
|
await self._handle_messages_append(frame)
|
||||||
elif isinstance(frame, LLMSetToolsFrame):
|
elif isinstance(frame, LLMSetToolsFrame):
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
|
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
@@ -456,14 +456,14 @@ class GrokRealtimeLLMService(LLMService):
|
|||||||
return
|
return
|
||||||
await self.push_error(error_msg=f"Error sending client event: {e}", exception=e)
|
await self.push_error(error_msg=f"Error sending client event: {e}", exception=e)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update):
|
async def _update_settings(self, update):
|
||||||
"""Apply a typed settings update, sending a session update if needed."""
|
"""Apply a settings update, sending a session update if needed."""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if "session_properties" in changed:
|
if "session_properties" in changed:
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
async def _update_settings(self):
|
async def _send_session_update(self):
|
||||||
"""Update session settings on the server."""
|
"""Update session settings on the server."""
|
||||||
settings = self._settings.session_properties
|
settings = self._settings.session_properties
|
||||||
adapter: GrokRealtimeLLMAdapter = self.get_llm_adapter()
|
adapter: GrokRealtimeLLMAdapter = self.get_llm_adapter()
|
||||||
@@ -543,7 +543,7 @@ class GrokRealtimeLLMService(LLMService):
|
|||||||
|
|
||||||
async def _handle_evt_conversation_created(self, evt):
|
async def _handle_evt_conversation_created(self, evt):
|
||||||
"""Handle conversation.created event - first event after connecting."""
|
"""Handle conversation.created event - first event after connecting."""
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
|
|
||||||
async def _handle_evt_response_created(self, evt):
|
async def _handle_evt_response_created(self, evt):
|
||||||
"""Handle response.created event - response generation started."""
|
"""Handle response.created event - response generation started."""
|
||||||
@@ -746,7 +746,7 @@ class GrokRealtimeLLMService(LLMService):
|
|||||||
self._messages_added_manually[evt.item.id] = True
|
self._messages_added_manually[evt.item.id] = True
|
||||||
await self.send_client_event(evt)
|
await self.send_client_event(evt)
|
||||||
|
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
self._llm_needs_conversation_setup = False
|
self._llm_needs_conversation_setup = False
|
||||||
|
|
||||||
logger.debug("Creating Grok response")
|
logger.debug("Creating Grok response")
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GroqTTSSettings(TTSSettings):
|
class GroqTTSSettings(TTSSettings):
|
||||||
"""Typed settings for the Groq TTS service.
|
"""Settings for the Groq TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
output_format: Audio output format.
|
output_format: Audio output format.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from .utils import ConfigOption
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class HathoraSTTSettings(STTSettings):
|
class HathoraSTTSettings(STTSettings):
|
||||||
"""Typed settings for the Hathora STT service.
|
"""Settings for the Hathora STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
config: Some models support additional config, refer to
|
config: Some models support additional config, refer to
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ def _decode_audio_payload(
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class HathoraTTSSettings(TTSSettings):
|
class HathoraTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Hathora TTS service.
|
"""Settings for Hathora TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
speed: Speech speed multiplier (if supported by model).
|
speed: Speech speed multiplier (if supported by model).
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ from pipecat.utils.tracing.service_decorators import traced_tts
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class InworldTTSSettings(TTSSettings):
|
class InworldTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Inworld TTS services.
|
"""Settings for Inworld TTS services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
audio_encoding: Audio encoding format (e.g. LINEAR16).
|
audio_encoding: Audio encoding format (e.g. LINEAR16).
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ def language_to_kokoro_language(language: Language) -> str:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class KokoroTTSSettings(TTSSettings):
|
class KokoroTTSSettings(TTSSettings):
|
||||||
"""Typed settings for the Kokoro TTS service.
|
"""Settings for the Kokoro TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
lang_code: Kokoro language code for synthesis.
|
lang_code: Kokoro language code for synthesis.
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ from pipecat.processors.aggregators.llm_response import (
|
|||||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
||||||
from pipecat.processors.frame_processor import FrameDirection
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
from pipecat.services.ai_service import AIService
|
from pipecat.services.ai_service import AIService
|
||||||
from pipecat.services.settings import LLMSettings, ServiceSettings, is_given
|
from pipecat.services.settings import LLMSettings, is_given
|
||||||
from pipecat.turns.user_turn_completion_mixin import UserTurnCompletionLLMServiceMixin
|
from pipecat.turns.user_turn_completion_mixin import UserTurnCompletionLLMServiceMixin
|
||||||
from pipecat.utils.context.llm_context_summarization import (
|
from pipecat.utils.context.llm_context_summarization import (
|
||||||
LLMContextSummarizationUtil,
|
LLMContextSummarizationUtil,
|
||||||
@@ -313,16 +313,16 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService):
|
|||||||
await self._cancel_sequential_runner_task()
|
await self._cancel_sequential_runner_task()
|
||||||
await self._cancel_summary_task()
|
await self._cancel_summary_task()
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: LLMSettings) -> set[str]:
|
async def _update_settings(self, update: LLMSettings) -> set[str]:
|
||||||
"""Apply a typed settings update, handling turn-completion fields.
|
"""Apply a settings update, handling turn-completion fields.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A typed LLM settings delta.
|
update: An LLM settings delta.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if "filter_incomplete_user_turns" in changed:
|
if "filter_incomplete_user_turns" in changed:
|
||||||
self._filter_incomplete_user_turns = self._settings.filter_incomplete_user_turns
|
self._filter_incomplete_user_turns = self._settings.filter_incomplete_user_turns
|
||||||
@@ -336,35 +336,6 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService):
|
|||||||
|
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
async def _update_settings(self, settings: Mapping[str, Any]):
|
|
||||||
"""Update LLM service settings.
|
|
||||||
|
|
||||||
Handles turn completion settings specially since they are not model
|
|
||||||
parameters and should not be passed to the underlying LLM API.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
settings: Dictionary of settings to update.
|
|
||||||
"""
|
|
||||||
# Turn completion settings to extract (not model parameters)
|
|
||||||
turn_completion_keys = {"filter_incomplete_user_turns", "user_turn_completion_config"}
|
|
||||||
|
|
||||||
# Handle turn completion settings
|
|
||||||
if "filter_incomplete_user_turns" in settings:
|
|
||||||
self._filter_incomplete_user_turns = settings["filter_incomplete_user_turns"]
|
|
||||||
logger.info(
|
|
||||||
f"{self}: Incomplete turn filtering {'enabled' if self._filter_incomplete_user_turns else 'disabled'}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Configure the mixin with config object
|
|
||||||
if self._filter_incomplete_user_turns and "user_turn_completion_config" in settings:
|
|
||||||
self.set_user_turn_completion_config(settings["user_turn_completion_config"])
|
|
||||||
|
|
||||||
# Remove turn completion settings before passing to parent
|
|
||||||
settings = {k: v for k, v in settings.items() if k not in turn_completion_keys}
|
|
||||||
|
|
||||||
# Let the parent handle remaining model parameters
|
|
||||||
await super()._update_settings(settings)
|
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
"""Process a frame.
|
"""Process a frame.
|
||||||
|
|
||||||
@@ -379,16 +350,12 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService):
|
|||||||
elif isinstance(frame, LLMConfigureOutputFrame):
|
elif isinstance(frame, LLMConfigureOutputFrame):
|
||||||
self._skip_tts = frame.skip_tts
|
self._skip_tts = frame.skip_tts
|
||||||
elif isinstance(frame, LLMUpdateSettingsFrame):
|
elif isinstance(frame, LLMUpdateSettingsFrame):
|
||||||
# New path: typed settings update object.
|
|
||||||
if frame.update is not None:
|
if frame.update is not None:
|
||||||
await self._update_settings_from_typed(frame.update)
|
await self._update_settings(frame.update)
|
||||||
# Legacy path: plain dict, but service uses typed settings — convert.
|
elif frame.settings:
|
||||||
elif isinstance(self._settings, ServiceSettings):
|
# Backward-compatible path: convert legacy dict to settings object.
|
||||||
update = type(self._settings).from_mapping(frame.settings)
|
update = type(self._settings).from_mapping(frame.settings)
|
||||||
await self._update_settings_from_typed(update)
|
await self._update_settings(update)
|
||||||
# Legacy path: plain dict, service still uses dict-based settings.
|
|
||||||
else:
|
|
||||||
await self._update_settings(frame.settings)
|
|
||||||
elif isinstance(frame, LLMContextSummaryRequestFrame):
|
elif isinstance(frame, LLMContextSummaryRequestFrame):
|
||||||
await self._handle_summary_request(frame)
|
await self._handle_summary_request(frame)
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ def language_to_lmnt_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LmntTTSSettings(TTSSettings):
|
class LmntTTSSettings(TTSSettings):
|
||||||
"""Typed settings for LMNT TTS service.
|
"""Settings for LMNT TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
format: Audio output format. Defaults to "raw".
|
format: Audio output format. Defaults to "raw".
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ def language_to_minimax_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MiniMaxTTSSettings(TTSSettings):
|
class MiniMaxTTSSettings(TTSSettings):
|
||||||
"""Typed settings for MiniMax TTS service.
|
"""Settings for MiniMax TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
stream: Whether to use streaming mode.
|
stream: Whether to use streaming mode.
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ def language_to_neuphonic_lang_code(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class NeuphonicTTSSettings(TTSSettings):
|
class NeuphonicTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Neuphonic TTS service.
|
"""Settings for Neuphonic TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
lang_code: Neuphonic language code.
|
lang_code: Neuphonic language code.
|
||||||
@@ -181,9 +181,9 @@ class NeuphonicTTSService(InterruptibleTTSService):
|
|||||||
"""
|
"""
|
||||||
return language_to_neuphonic_lang_code(language)
|
return language_to_neuphonic_lang_code(language)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and reconnect with new configuration."""
|
"""Apply a settings update and reconnect with new configuration."""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if changed:
|
if changed:
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
await self._connect()
|
await self._connect()
|
||||||
|
|||||||
@@ -93,14 +93,14 @@ def language_to_nvidia_riva_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class NvidiaSTTSettings(STTSettings):
|
class NvidiaSTTSettings(STTSettings):
|
||||||
"""Typed settings for the NVIDIA Riva streaming STT service."""
|
"""Settings for the NVIDIA Riva streaming STT service."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class NvidiaSegmentedSTTSettings(STTSettings):
|
class NvidiaSegmentedSTTSettings(STTSettings):
|
||||||
"""Typed settings for the NVIDIA Riva segmented STT service.
|
"""Settings for the NVIDIA Riva segmented STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
profanity_filter: Whether to filter profanity from results.
|
profanity_filter: Whether to filter profanity from results.
|
||||||
@@ -579,8 +579,8 @@ class NvidiaSegmentedSTTService(SegmentedSTTService):
|
|||||||
self._config = self._create_recognition_config()
|
self._config = self._create_recognition_config()
|
||||||
logger.debug(f"Initialized NvidiaSegmentedSTTService with model: {self.model_name}")
|
logger.debug(f"Initialized NvidiaSegmentedSTTService with model: {self.model_name}")
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and sync internal state.
|
"""Apply a settings update and sync internal state.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A :class:`STTSettings` (or ``NvidiaSegmentedSTTSettings``) delta.
|
update: A :class:`STTSettings` (or ``NvidiaSegmentedSTTSettings``) delta.
|
||||||
@@ -588,7 +588,7 @@ class NvidiaSegmentedSTTService(SegmentedSTTService):
|
|||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
self._config = self._create_recognition_config()
|
self._config = self._create_recognition_config()
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ from pipecat.utils.tracing.service_decorators import traced_llm
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class OpenAILLMSettings(LLMSettings):
|
class OpenAILLMSettings(LLMSettings):
|
||||||
"""Typed settings for OpenAI-compatible LLM services.
|
"""Settings for OpenAI-compatible LLM services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
max_completion_tokens: Maximum completion tokens to generate.
|
max_completion_tokens: Maximum completion tokens to generate.
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class CurrentAudioResponse:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class OpenAIRealtimeLLMSettings(LLMSettings):
|
class OpenAIRealtimeLLMSettings(LLMSettings):
|
||||||
"""Typed settings for OpenAI Realtime LLM services.
|
"""Settings for OpenAI Realtime LLM services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
session_properties: OpenAI Realtime session configuration.
|
session_properties: OpenAI Realtime session configuration.
|
||||||
@@ -411,13 +411,13 @@ class OpenAIRealtimeLLMService(LLMService):
|
|||||||
frame: The frame to process.
|
frame: The frame to process.
|
||||||
direction: The direction of frame flow in the pipeline.
|
direction: The direction of frame flow in the pipeline.
|
||||||
"""
|
"""
|
||||||
# Legacy dict path: frame.settings contains SessionProperties fields,
|
# Backward-compatible dict path: frame.settings contains SessionProperties
|
||||||
# not our Settings fields, so we construct SessionProperties directly.
|
# fields, not our Settings fields, so we construct SessionProperties
|
||||||
# The new typed path (frame.update) falls through to super, which calls
|
# directly. The frame.update path falls through to super, which calls
|
||||||
# _update_settings_from_typed → our override handles the rest.
|
# _update_settings → our override handles the rest.
|
||||||
if isinstance(frame, LLMUpdateSettingsFrame) and frame.update is None:
|
if isinstance(frame, LLMUpdateSettingsFrame) and frame.update is None:
|
||||||
self._settings.session_properties = events.SessionProperties(**frame.settings)
|
self._settings.session_properties = events.SessionProperties(**frame.settings)
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -449,7 +449,7 @@ class OpenAIRealtimeLLMService(LLMService):
|
|||||||
elif isinstance(frame, LLMMessagesAppendFrame):
|
elif isinstance(frame, LLMMessagesAppendFrame):
|
||||||
await self._handle_messages_append(frame)
|
await self._handle_messages_append(frame)
|
||||||
elif isinstance(frame, LLMSetToolsFrame):
|
elif isinstance(frame, LLMSetToolsFrame):
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
|
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
@@ -534,14 +534,14 @@ class OpenAIRealtimeLLMService(LLMService):
|
|||||||
# treat a send-side error as fatal.
|
# treat a send-side error as fatal.
|
||||||
await self.push_error(error_msg=f"Error sending client event: {e}", exception=e)
|
await self.push_error(error_msg=f"Error sending client event: {e}", exception=e)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update):
|
async def _update_settings(self, update):
|
||||||
"""Apply a typed settings update, sending a session update if needed."""
|
"""Apply a settings update, sending a session update if needed."""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if "session_properties" in changed:
|
if "session_properties" in changed:
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
async def _update_settings(self):
|
async def _send_session_update(self):
|
||||||
settings = self._settings.session_properties
|
settings = self._settings.session_properties
|
||||||
adapter: OpenAIRealtimeLLMAdapter = self.get_llm_adapter()
|
adapter: OpenAIRealtimeLLMAdapter = self.get_llm_adapter()
|
||||||
|
|
||||||
@@ -613,7 +613,7 @@ class OpenAIRealtimeLLMService(LLMService):
|
|||||||
async def _handle_evt_session_created(self, evt):
|
async def _handle_evt_session_created(self, evt):
|
||||||
# session.created is received right after connecting. Send a message
|
# session.created is received right after connecting. Send a message
|
||||||
# to configure the session properties.
|
# to configure the session properties.
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
|
|
||||||
async def _handle_evt_session_updated(self, evt):
|
async def _handle_evt_session_updated(self, evt):
|
||||||
# If this is our first context frame, run the LLM
|
# If this is our first context frame, run the LLM
|
||||||
@@ -896,7 +896,7 @@ class OpenAIRealtimeLLMService(LLMService):
|
|||||||
await self.send_client_event(evt)
|
await self.send_client_event(evt)
|
||||||
|
|
||||||
# Send new settings if needed
|
# Send new settings if needed
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
|
|
||||||
# We're done configuring the LLM for this session
|
# We're done configuring the LLM for this session
|
||||||
self._llm_needs_conversation_setup = False
|
self._llm_needs_conversation_setup = False
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ _OPENAI_SAMPLE_RATE = 24000
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class OpenAIRealtimeSTTSettings(STTSettings):
|
class OpenAIRealtimeSTTSettings(STTSettings):
|
||||||
"""Typed settings for the OpenAI Realtime STT service.
|
"""Settings for the OpenAI Realtime STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
prompt: Optional prompt text to guide transcription style.
|
prompt: Optional prompt text to guide transcription style.
|
||||||
@@ -268,10 +268,10 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
|
|||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and send session update if needed.
|
"""Apply a settings update and send session update if needed.
|
||||||
|
|
||||||
Keeps ``_language_code`` and ``_prompt`` in sync with typed settings
|
Keeps ``_language_code`` and ``_prompt`` in sync with settings
|
||||||
and sends a ``session.update`` to the server when the session is active.
|
and sends a ``session.update`` to the server when the session is active.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -280,7 +280,7 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
|
|||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ VALID_VOICES: Dict[str, ValidVoice] = {
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class OpenAITTSSettings(TTSSettings):
|
class OpenAITTSSettings(TTSSettings):
|
||||||
"""Typed settings for OpenAI TTS service.
|
"""Settings for OpenAI TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
instructions: Instructions to guide voice synthesis behavior.
|
instructions: Instructions to guide voice synthesis behavior.
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class CurrentAudioResponse:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class OpenAIRealtimeBetaLLMSettings(LLMSettings):
|
class OpenAIRealtimeBetaLLMSettings(LLMSettings):
|
||||||
"""Typed settings for OpenAI Realtime Beta LLM services.
|
"""Settings for OpenAI Realtime Beta LLM services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
session_properties: OpenAI Realtime session configuration.
|
session_properties: OpenAI Realtime session configuration.
|
||||||
@@ -357,13 +357,13 @@ class OpenAIRealtimeBetaLLMService(LLMService):
|
|||||||
frame: The frame to process.
|
frame: The frame to process.
|
||||||
direction: The direction of frame flow in the pipeline.
|
direction: The direction of frame flow in the pipeline.
|
||||||
"""
|
"""
|
||||||
# Legacy dict path: frame.settings contains SessionProperties fields,
|
# Backward-compatible dict path: frame.settings contains SessionProperties
|
||||||
# not our Settings fields, so we construct SessionProperties directly.
|
# fields, not our Settings fields, so we construct SessionProperties
|
||||||
# The new typed path (frame.update) falls through to super, which calls
|
# directly. The frame.update path falls through to super, which calls
|
||||||
# _update_settings_from_typed → our override handles the rest.
|
# _update_settings → our override handles the rest.
|
||||||
if isinstance(frame, LLMUpdateSettingsFrame) and frame.update is None:
|
if isinstance(frame, LLMUpdateSettingsFrame) and frame.update is None:
|
||||||
self._settings.session_properties = events.SessionProperties(**frame.settings)
|
self._settings.session_properties = events.SessionProperties(**frame.settings)
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -403,7 +403,7 @@ class OpenAIRealtimeBetaLLMService(LLMService):
|
|||||||
elif isinstance(frame, RealtimeMessagesUpdateFrame):
|
elif isinstance(frame, RealtimeMessagesUpdateFrame):
|
||||||
self._context = frame.context
|
self._context = frame.context
|
||||||
elif isinstance(frame, LLMSetToolsFrame):
|
elif isinstance(frame, LLMSetToolsFrame):
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
elif isinstance(frame, RealtimeFunctionCallResultFrame):
|
elif isinstance(frame, RealtimeFunctionCallResultFrame):
|
||||||
await self._handle_function_call_result(frame.result_frame)
|
await self._handle_function_call_result(frame.result_frame)
|
||||||
|
|
||||||
@@ -478,14 +478,14 @@ class OpenAIRealtimeBetaLLMService(LLMService):
|
|||||||
# treat a send-side error as fatal.
|
# treat a send-side error as fatal.
|
||||||
await self.push_error(error_msg=f"Error sending client event: {e}", exception=e)
|
await self.push_error(error_msg=f"Error sending client event: {e}", exception=e)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update):
|
async def _update_settings(self, update):
|
||||||
"""Apply a typed settings update, sending a session update if needed."""
|
"""Apply a settings update, sending a session update if needed."""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if "session_properties" in changed:
|
if "session_properties" in changed:
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
async def _update_settings(self):
|
async def _send_session_update(self):
|
||||||
settings = self._settings.session_properties
|
settings = self._settings.session_properties
|
||||||
# tools given in the context override the tools in the session properties
|
# tools given in the context override the tools in the session properties
|
||||||
if self._context and self._context.tools:
|
if self._context and self._context.tools:
|
||||||
@@ -540,7 +540,7 @@ class OpenAIRealtimeBetaLLMService(LLMService):
|
|||||||
async def _handle_evt_session_created(self, evt):
|
async def _handle_evt_session_created(self, evt):
|
||||||
# session.created is received right after connecting. Send a message
|
# session.created is received right after connecting. Send a message
|
||||||
# to configure the session properties.
|
# to configure the session properties.
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
|
|
||||||
async def _handle_evt_session_updated(self, evt):
|
async def _handle_evt_session_updated(self, evt):
|
||||||
# If this is our first context frame, run the LLM
|
# If this is our first context frame, run the LLM
|
||||||
@@ -779,7 +779,7 @@ class OpenAIRealtimeBetaLLMService(LLMService):
|
|||||||
self._context.llm_needs_initial_messages = False
|
self._context.llm_needs_initial_messages = False
|
||||||
|
|
||||||
if self._context.llm_needs_settings_update:
|
if self._context.llm_needs_settings_update:
|
||||||
await self._update_settings()
|
await self._send_session_update()
|
||||||
self._context.llm_needs_settings_update = False
|
self._context.llm_needs_settings_update = False
|
||||||
|
|
||||||
logger.debug(f"Creating response: {self._context.get_messages_for_logging()}")
|
logger.debug(f"Creating response: {self._context.get_messages_for_logging()}")
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ def language_to_playht_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PlayHTTTSSettings(TTSSettings):
|
class PlayHTTTSSettings(TTSSettings):
|
||||||
"""Typed settings for PlayHT TTS services.
|
"""Settings for PlayHT TTS services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
output_format: Audio output format.
|
output_format: Audio output format.
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ except ModuleNotFoundError as e:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ResembleAITTSSettings(TTSSettings):
|
class ResembleAITTSSettings(TTSSettings):
|
||||||
"""Typed settings for Resemble AI TTS service.
|
"""Settings for Resemble AI TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
precision: PCM bit depth (PCM_32, PCM_24, PCM_16, or MULAW).
|
precision: PCM bit depth (PCM_32, PCM_24, PCM_16, or MULAW).
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ def language_to_rime_language(language: Language) -> str:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RimeTTSSettings(TTSSettings):
|
class RimeTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Rime WS JSON and HTTP TTS services.
|
"""Settings for Rime WS JSON and HTTP TTS services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
speaker: Voice speaker ID.
|
speaker: Voice speaker ID.
|
||||||
@@ -101,7 +101,7 @@ class RimeTTSSettings(TTSSettings):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RimeNonJsonTTSSettings(TTSSettings):
|
class RimeNonJsonTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Rime non-JSON WS TTS service.
|
"""Settings for Rime non-JSON WS TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
speaker: Voice speaker ID.
|
speaker: Voice speaker ID.
|
||||||
@@ -271,10 +271,10 @@ class RimeTTSService(AudioContextWordTTSService):
|
|||||||
self._extra_msg_fields["inlineSpeedAlpha"] = ",".join(speed_vals + [str(speed)])
|
self._extra_msg_fields["inlineSpeedAlpha"] = ",".join(speed_vals + [str(speed)])
|
||||||
return f"[{text}]"
|
return f"[{text}]"
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and reconnect if voice changed."""
|
"""Apply a settings update and reconnect if voice changed."""
|
||||||
prev_voice = self._voice_id
|
prev_voice = self._voice_id
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if "voice" in changed:
|
if "voice" in changed:
|
||||||
self._settings.speaker = self._voice_id
|
self._settings.speaker = self._voice_id
|
||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
@@ -975,13 +975,13 @@ class RimeNonJsonTTSService(InterruptibleTTSService):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
yield ErrorFrame(error=f"Unknown error occurred: {e}")
|
yield ErrorFrame(error=f"Unknown error occurred: {e}")
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and reconnect if necessary.
|
"""Apply a settings update and reconnect if necessary.
|
||||||
|
|
||||||
Since all settings are WebSocket URL query parameters,
|
Since all settings are WebSocket URL query parameters,
|
||||||
any setting change requires reconnecting to apply the new values.
|
any setting change requires reconnecting to apply the new values.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
# Sync voice and model to settings dict fields
|
# Sync voice and model to settings dict fields
|
||||||
if "voice" in changed:
|
if "voice" in changed:
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ MODEL_CONFIGS: Dict[str, ModelConfig] = {
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SarvamSTTSettings(STTSettings):
|
class SarvamSTTSettings(STTSettings):
|
||||||
"""Typed settings for the Sarvam STT service.
|
"""Settings for the Sarvam STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
prompt: Optional prompt to guide transcription/translation style.
|
prompt: Optional prompt to guide transcription/translation style.
|
||||||
@@ -306,8 +306,8 @@ class SarvamSTTService(STTService):
|
|||||||
if self._socket_client:
|
if self._socket_client:
|
||||||
await self._socket_client.flush()
|
await self._socket_client.flush()
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update, validate, sync state, and reconnect.
|
"""Apply a settings update, validate, sync state, and reconnect.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A :class:`STTSettings` (or ``SarvamSTTSettings``) delta.
|
update: A :class:`STTSettings` (or ``SarvamSTTSettings``) delta.
|
||||||
@@ -336,7 +336,7 @@ class SarvamSTTService(STTService):
|
|||||||
if not self._config.supports_mode:
|
if not self._config.supports_mode:
|
||||||
raise ValueError(f"Model '{self.model_name}' does not support mode parameter.")
|
raise ValueError(f"Model '{self.model_name}' does not support mode parameter.")
|
||||||
|
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ def language_to_sarvam_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SarvamHttpTTSSettings(TTSSettings):
|
class SarvamHttpTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Sarvam HTTP TTS service.
|
"""Settings for Sarvam HTTP TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
language: Sarvam language code.
|
language: Sarvam language code.
|
||||||
@@ -277,7 +277,7 @@ class SarvamHttpTTSSettings(TTSSettings):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SarvamWSTTSSettings(TTSSettings):
|
class SarvamWSTTSSettings(TTSSettings):
|
||||||
"""Typed settings for Sarvam WebSocket TTS service.
|
"""Settings for Sarvam WebSocket TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
target_language_code: Sarvam language code.
|
target_language_code: Sarvam language code.
|
||||||
@@ -953,9 +953,9 @@ class SarvamTTSService(InterruptibleTTSService):
|
|||||||
if isinstance(frame, (LLMFullResponseEndFrame, EndFrame)):
|
if isinstance(frame, (LLMFullResponseEndFrame, EndFrame)):
|
||||||
await self.flush_audio()
|
await self.flush_audio()
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
"""Apply a typed settings update and resend config if voice changed."""
|
"""Apply a settings update and resend config if voice changed."""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if "voice" in changed:
|
if "voice" in changed:
|
||||||
await self._send_config()
|
await self._send_config()
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -4,13 +4,12 @@
|
|||||||
# SPDX-License-Identifier: BSD 2-Clause License
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
#
|
#
|
||||||
|
|
||||||
"""Typed settings infrastructure for Pipecat AI services.
|
"""Settings infrastructure for Pipecat AI services.
|
||||||
|
|
||||||
This module provides typed dataclass-based settings objects that replace the
|
This module provides dataclass-based settings objects for service configuration.
|
||||||
stringly-typed ``Mapping[str, Any]`` dictionaries previously used for service
|
Each service type has a corresponding settings class (e.g. ``TTSSettings``,
|
||||||
configuration. Each service type has a corresponding settings class (e.g.
|
``LLMSettings``) whose fields use the ``NOT_GIVEN`` sentinel to distinguish
|
||||||
``TTSSettings``, ``LLMSettings``) whose fields use the ``NOT_GIVEN`` sentinel
|
"leave unchanged" from an explicit ``None``.
|
||||||
to distinguish "leave unchanged" from an explicit ``None``.
|
|
||||||
|
|
||||||
Key concepts:
|
Key concepts:
|
||||||
|
|
||||||
@@ -21,7 +20,7 @@ Key concepts:
|
|||||||
``NOT_GIVEN`` are simply skipped when applying an update.
|
``NOT_GIVEN`` are simply skipped when applying an update.
|
||||||
- **apply_update**: Applies a delta onto a target settings object and returns
|
- **apply_update**: Applies a delta onto a target settings object and returns
|
||||||
the set of field names that actually changed.
|
the set of field names that actually changed.
|
||||||
- **from_mapping**: Constructs a typed settings object from a plain dict,
|
- **from_mapping**: Constructs a settings object from a plain dict,
|
||||||
supporting field aliases (e.g. ``"voice_id"`` → ``"voice"``).
|
supporting field aliases (e.g. ``"voice_id"`` → ``"voice"``).
|
||||||
- **Extras**: Unknown keys land in the ``extra`` dict so services that have
|
- **Extras**: Unknown keys land in the ``extra`` dict so services that have
|
||||||
non-standard settings don't lose data.
|
non-standard settings don't lose data.
|
||||||
@@ -91,7 +90,7 @@ _S = TypeVar("_S", bound="ServiceSettings")
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ServiceSettings:
|
class ServiceSettings:
|
||||||
"""Base class for typed service settings.
|
"""Base class for service settings.
|
||||||
|
|
||||||
Every AI service type (LLM, TTS, STT) extends this with its own fields.
|
Every AI service type (LLM, TTS, STT) extends this with its own fields.
|
||||||
Fields default to ``NOT_GIVEN`` so that an instance can represent either
|
Fields default to ``NOT_GIVEN`` so that an instance can represent either
|
||||||
@@ -188,7 +187,10 @@ class ServiceSettings:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_mapping(cls: Type[_S], settings: Mapping[str, Any]) -> _S:
|
def from_mapping(cls: Type[_S], settings: Mapping[str, Any]) -> _S:
|
||||||
"""Construct a typed settings object from a plain dictionary.
|
"""Construct a settings object from a plain dictionary.
|
||||||
|
|
||||||
|
This exists for backward compatibility with code that passes plain
|
||||||
|
dicts via ``*UpdateSettingsFrame(settings={...})``.
|
||||||
|
|
||||||
Keys are matched to dataclass fields by name. Keys listed in
|
Keys are matched to dataclass fields by name. Keys listed in
|
||||||
``_aliases`` are translated to their canonical name first. Any
|
``_aliases`` are translated to their canonical name first. Any
|
||||||
@@ -250,7 +252,7 @@ class ServiceSettings:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LLMSettings(ServiceSettings):
|
class LLMSettings(ServiceSettings):
|
||||||
"""Typed settings for LLM services.
|
"""Settings for LLM services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
model: LLM model identifier.
|
model: LLM model identifier.
|
||||||
@@ -285,7 +287,7 @@ class LLMSettings(ServiceSettings):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TTSSettings(ServiceSettings):
|
class TTSSettings(ServiceSettings):
|
||||||
"""Typed settings for TTS services.
|
"""Settings for TTS services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
model: TTS model identifier.
|
model: TTS model identifier.
|
||||||
@@ -301,7 +303,7 @@ class TTSSettings(ServiceSettings):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class STTSettings(ServiceSettings):
|
class STTSettings(ServiceSettings):
|
||||||
"""Typed settings for STT services.
|
"""Settings for STT services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
model: STT model identifier.
|
model: STT model identifier.
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ def _prepare_language_hints(
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SonioxSTTSettings(STTSettings):
|
class SonioxSTTSettings(STTSettings):
|
||||||
"""Typed settings for Soniox STT service.
|
"""Settings for Soniox STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
input_params: Soniox ``SonioxInputParams`` for detailed configuration.
|
input_params: Soniox ``SonioxInputParams`` for detailed configuration.
|
||||||
@@ -217,8 +217,8 @@ class SonioxSTTService(WebsocketSTTService):
|
|||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
await self._connect()
|
await self._connect()
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: SonioxSTTSettings) -> set[str]:
|
async def _update_settings(self, update: SonioxSTTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update, keeping ``input_params`` in sync.
|
"""Apply a settings update, keeping ``input_params`` in sync.
|
||||||
|
|
||||||
Top-level ``model`` is the source of truth. When it is given in
|
Top-level ``model`` is the source of truth. When it is given in
|
||||||
*update* its value is propagated into ``input_params``. When only
|
*update* its value is propagated into ``input_params``. When only
|
||||||
@@ -228,14 +228,14 @@ class SonioxSTTService(WebsocketSTTService):
|
|||||||
Any change triggers a WebSocket reconnect.
|
Any change triggers a WebSocket reconnect.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A typed settings delta.
|
update: A settings delta.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
model_given = is_given(getattr(update, "model", NOT_GIVEN))
|
model_given = is_given(getattr(update, "model", NOT_GIVEN))
|
||||||
|
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class TurnDetectionMode(str, Enum):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SpeechmaticsSTTSettings(STTSettings):
|
class SpeechmaticsSTTSettings(STTSettings):
|
||||||
"""Typed settings for Speechmatics STT service.
|
"""Settings for Speechmatics STT service.
|
||||||
|
|
||||||
See ``SpeechmaticsSTTService.InputParams`` for detailed descriptions of each field.
|
See ``SpeechmaticsSTTService.InputParams`` for detailed descriptions of each field.
|
||||||
|
|
||||||
@@ -415,7 +415,7 @@ class SpeechmaticsSTTService(STTService):
|
|||||||
)
|
)
|
||||||
speaker_passive_format = params.speaker_passive_format or speaker_active_format
|
speaker_passive_format = params.speaker_passive_format or speaker_active_format
|
||||||
|
|
||||||
# Typed settings — seeded from InputParams
|
# Settings — seeded from InputParams
|
||||||
self._settings = SpeechmaticsSTTSettings(
|
self._settings = SpeechmaticsSTTSettings(
|
||||||
language=params.language,
|
language=params.language,
|
||||||
domain=params.domain,
|
domain=params.domain,
|
||||||
@@ -480,8 +480,8 @@ class SpeechmaticsSTTService(STTService):
|
|||||||
await super().start(frame)
|
await super().start(frame)
|
||||||
await self._connect()
|
await self._connect()
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: SpeechmaticsSTTSettings) -> set[str]:
|
async def _update_settings(self, update: SpeechmaticsSTTSettings) -> set[str]:
|
||||||
"""Apply typed settings update, reconnecting only when necessary.
|
"""Apply settings update, reconnecting only when necessary.
|
||||||
|
|
||||||
Fields are classified into three categories (see
|
Fields are classified into three categories (see
|
||||||
``SpeechmaticsSTTSettings``):
|
``SpeechmaticsSTTSettings``):
|
||||||
@@ -494,12 +494,12 @@ class SpeechmaticsSTTService(STTService):
|
|||||||
time and therefore require a full disconnect / reconnect.
|
time and therefore require a full disconnect / reconnect.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A typed settings delta.
|
update: A settings delta.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import time
|
|||||||
import warnings
|
import warnings
|
||||||
import wave
|
import wave
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from typing import Any, AsyncGenerator, Dict, Mapping, Optional
|
from typing import Any, AsyncGenerator, Optional
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from websockets.protocol import State
|
from websockets.protocol import State
|
||||||
@@ -35,7 +35,7 @@ from pipecat.frames.frames import (
|
|||||||
from pipecat.metrics.metrics import TTFBMetricsData
|
from pipecat.metrics.metrics import TTFBMetricsData
|
||||||
from pipecat.processors.frame_processor import FrameDirection
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
from pipecat.services.ai_service import AIService
|
from pipecat.services.ai_service import AIService
|
||||||
from pipecat.services.settings import ServiceSettings, STTSettings
|
from pipecat.services.settings import STTSettings
|
||||||
from pipecat.services.stt_latency import DEFAULT_TTFS_P99
|
from pipecat.services.stt_latency import DEFAULT_TTFS_P99
|
||||||
from pipecat.services.websocket_service import WebsocketService
|
from pipecat.services.websocket_service import WebsocketService
|
||||||
from pipecat.transcriptions.language import Language
|
from pipecat.transcriptions.language import Language
|
||||||
@@ -183,11 +183,8 @@ class STTService(AIService):
|
|||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
logger.info(f"Switching STT model to: [{model}]")
|
logger.info(f"Switching STT model to: [{model}]")
|
||||||
if isinstance(self._settings, ServiceSettings):
|
settings_cls = type(self._settings)
|
||||||
settings_cls = type(self._settings)
|
await self._update_settings(settings_cls(model=model))
|
||||||
await self._update_settings_from_typed(settings_cls(model=model))
|
|
||||||
else:
|
|
||||||
self.set_model_name(model)
|
|
||||||
|
|
||||||
async def set_language(self, language: Language):
|
async def set_language(self, language: Language):
|
||||||
"""Set the language for speech recognition.
|
"""Set the language for speech recognition.
|
||||||
@@ -206,11 +203,8 @@ class STTService(AIService):
|
|||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
logger.info(f"Switching STT language to: [{language}]")
|
logger.info(f"Switching STT language to: [{language}]")
|
||||||
if isinstance(self._settings, ServiceSettings):
|
settings_cls = type(self._settings)
|
||||||
settings_cls = type(self._settings)
|
await self._update_settings(settings_cls(language=language))
|
||||||
await self._update_settings_from_typed(settings_cls(language=language))
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def run_stt(self, audio: bytes) -> AsyncGenerator[Frame, None]:
|
async def run_stt(self, audio: bytes) -> AsyncGenerator[Frame, None]:
|
||||||
@@ -242,23 +236,8 @@ class STTService(AIService):
|
|||||||
await super().cleanup()
|
await super().cleanup()
|
||||||
await self._cancel_ttfb_timeout()
|
await self._cancel_ttfb_timeout()
|
||||||
|
|
||||||
async def _update_settings(self, settings: Mapping[str, Any]):
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
logger.info(f"Updating STT settings: {self._settings}")
|
"""Apply an STT settings update.
|
||||||
for key, value in settings.items():
|
|
||||||
if key in self._settings:
|
|
||||||
logger.info(f"Updating STT setting {key} to: [{value}]")
|
|
||||||
self._settings[key] = value
|
|
||||||
if key == "language":
|
|
||||||
await self.set_language(value)
|
|
||||||
elif key == "language":
|
|
||||||
await self.set_language(value)
|
|
||||||
elif key == "model":
|
|
||||||
self.set_model_name(value)
|
|
||||||
else:
|
|
||||||
logger.warning(f"Unknown setting for STT service: {key}")
|
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
|
||||||
"""Apply a typed STT settings update.
|
|
||||||
|
|
||||||
Handles ``model`` (via parent). Does **not** call ``set_language``
|
Handles ``model`` (via parent). Does **not** call ``set_language``
|
||||||
— concrete services should override this method and handle language
|
— concrete services should override this method and handle language
|
||||||
@@ -266,12 +245,12 @@ class STTService(AIService):
|
|||||||
changed-field set.
|
changed-field set.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A typed STT settings delta.
|
update: An STT settings delta.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
async def process_audio_frame(self, frame: AudioRawFrame, direction: FrameDirection):
|
async def process_audio_frame(self, frame: AudioRawFrame, direction: FrameDirection):
|
||||||
@@ -335,16 +314,12 @@ class STTService(AIService):
|
|||||||
await self._handle_vad_user_stopped_speaking(frame)
|
await self._handle_vad_user_stopped_speaking(frame)
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
elif isinstance(frame, STTUpdateSettingsFrame):
|
elif isinstance(frame, STTUpdateSettingsFrame):
|
||||||
# New path: typed settings update object.
|
|
||||||
if frame.update is not None:
|
if frame.update is not None:
|
||||||
await self._update_settings_from_typed(frame.update)
|
await self._update_settings(frame.update)
|
||||||
# Legacy path: plain dict, but service uses typed settings — convert.
|
elif frame.settings:
|
||||||
elif isinstance(self._settings, ServiceSettings):
|
# Backward-compatible path: convert legacy dict to settings object.
|
||||||
update = type(self._settings).from_mapping(frame.settings)
|
update = type(self._settings).from_mapping(frame.settings)
|
||||||
await self._update_settings_from_typed(update)
|
await self._update_settings(update)
|
||||||
# Legacy path: plain dict, service still uses dict-based settings.
|
|
||||||
else:
|
|
||||||
await self._update_settings(frame.settings)
|
|
||||||
elif isinstance(frame, STTMuteFrame):
|
elif isinstance(frame, STTMuteFrame):
|
||||||
self._muted = frame.mute
|
self._muted = frame.mute
|
||||||
logger.debug(f"STT service {'muted' if frame.mute else 'unmuted'}")
|
logger.debug(f"STT service {'muted' if frame.mute else 'unmuted'}")
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ from typing import (
|
|||||||
Callable,
|
Callable,
|
||||||
Dict,
|
Dict,
|
||||||
List,
|
List,
|
||||||
Mapping,
|
|
||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
Sequence,
|
||||||
Tuple,
|
Tuple,
|
||||||
@@ -53,7 +52,7 @@ from pipecat.frames.frames import (
|
|||||||
)
|
)
|
||||||
from pipecat.processors.frame_processor import FrameDirection
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
from pipecat.services.ai_service import AIService
|
from pipecat.services.ai_service import AIService
|
||||||
from pipecat.services.settings import ServiceSettings, TTSSettings, is_given
|
from pipecat.services.settings import TTSSettings, is_given
|
||||||
from pipecat.services.websocket_service import WebsocketService
|
from pipecat.services.websocket_service import WebsocketService
|
||||||
from pipecat.transcriptions.language import Language
|
from pipecat.transcriptions.language import Language
|
||||||
from pipecat.utils.text.base_text_aggregator import BaseTextAggregator
|
from pipecat.utils.text.base_text_aggregator import BaseTextAggregator
|
||||||
@@ -280,11 +279,8 @@ class TTSService(AIService):
|
|||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
logger.info(f"Switching TTS model to: [{model}]")
|
logger.info(f"Switching TTS model to: [{model}]")
|
||||||
if isinstance(self._settings, ServiceSettings):
|
settings_cls = type(self._settings)
|
||||||
settings_cls = type(self._settings)
|
await self._update_settings(settings_cls(model=model))
|
||||||
await self._update_settings_from_typed(settings_cls(model=model))
|
|
||||||
else:
|
|
||||||
self.set_model_name(model)
|
|
||||||
|
|
||||||
async def set_voice(self, voice: str):
|
async def set_voice(self, voice: str):
|
||||||
"""Set the voice for speech synthesis.
|
"""Set the voice for speech synthesis.
|
||||||
@@ -303,11 +299,8 @@ class TTSService(AIService):
|
|||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
logger.info(f"Switching TTS voice to: [{voice}]")
|
logger.info(f"Switching TTS voice to: [{voice}]")
|
||||||
if isinstance(self._settings, ServiceSettings):
|
settings_cls = type(self._settings)
|
||||||
settings_cls = type(self._settings)
|
await self._update_settings(settings_cls(voice=voice))
|
||||||
await self._update_settings_from_typed(settings_cls(voice=voice))
|
|
||||||
else:
|
|
||||||
self._voice_id = voice
|
|
||||||
|
|
||||||
def create_context_id(self) -> str:
|
def create_context_id(self) -> str:
|
||||||
"""Generate a unique context ID for a TTS request.
|
"""Generate a unique context ID for a TTS request.
|
||||||
@@ -439,25 +432,8 @@ class TTSService(AIService):
|
|||||||
if not (agg_type == aggregation_type and func == transform_function)
|
if not (agg_type == aggregation_type and func == transform_function)
|
||||||
]
|
]
|
||||||
|
|
||||||
async def _update_settings(self, settings: Mapping[str, Any]):
|
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||||
for key, value in settings.items():
|
"""Apply a TTS settings update.
|
||||||
if key in self._settings:
|
|
||||||
logger.info(f"Updating TTS setting {key} to: [{value}]")
|
|
||||||
self._settings[key] = value
|
|
||||||
if key == "language":
|
|
||||||
self._settings[key] = self.language_to_service_language(value)
|
|
||||||
elif key == "model":
|
|
||||||
self.set_model_name(value)
|
|
||||||
elif key == "voice" or key == "voice_id":
|
|
||||||
self._voice_id = value
|
|
||||||
elif key == "text_filter":
|
|
||||||
for filter in self._text_filters:
|
|
||||||
await filter.update_settings(value)
|
|
||||||
else:
|
|
||||||
logger.warning(f"Unknown setting for TTS service: {key}")
|
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: TTSSettings) -> set[str]:
|
|
||||||
"""Apply a typed TTS settings update.
|
|
||||||
|
|
||||||
Handles ``model`` (via parent) and syncs ``_voice_id`` when voice
|
Handles ``model`` (via parent) and syncs ``_voice_id`` when voice
|
||||||
changes. Translates language values before applying. Does **not**
|
changes. Translates language values before applying. Does **not**
|
||||||
@@ -466,7 +442,7 @@ class TTSService(AIService):
|
|||||||
returned changed-field set.
|
returned changed-field set.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
update: A typed TTS settings delta.
|
update: A TTS settings delta.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Set of field names whose values actually changed.
|
Set of field names whose values actually changed.
|
||||||
@@ -477,10 +453,10 @@ class TTSService(AIService):
|
|||||||
if converted is not None:
|
if converted is not None:
|
||||||
update.language = converted
|
update.language = converted
|
||||||
|
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
# Keep _voice_id in sync for code that reads it directly
|
# Keep _voice_id in sync for code that reads it directly
|
||||||
if "voice" in changed and isinstance(self._settings, TTSSettings):
|
if "voice" in changed:
|
||||||
self._voice_id = self._settings.voice
|
self._voice_id = self._settings.voice
|
||||||
|
|
||||||
return changed
|
return changed
|
||||||
@@ -566,16 +542,12 @@ class TTSService(AIService):
|
|||||||
await self.flush_audio()
|
await self.flush_audio()
|
||||||
self._processing_text = processing_text
|
self._processing_text = processing_text
|
||||||
elif isinstance(frame, TTSUpdateSettingsFrame):
|
elif isinstance(frame, TTSUpdateSettingsFrame):
|
||||||
# New path: typed settings update object.
|
|
||||||
if frame.update is not None:
|
if frame.update is not None:
|
||||||
await self._update_settings_from_typed(frame.update)
|
await self._update_settings(frame.update)
|
||||||
# Legacy path: plain dict, but service uses typed settings — convert.
|
elif frame.settings:
|
||||||
elif isinstance(self._settings, ServiceSettings):
|
# Backward-compatible path: convert legacy dict to settings object.
|
||||||
update = type(self._settings).from_mapping(frame.settings)
|
update = type(self._settings).from_mapping(frame.settings)
|
||||||
await self._update_settings_from_typed(update)
|
await self._update_settings(update)
|
||||||
# Legacy path: plain dict, service still uses dict-based settings.
|
|
||||||
else:
|
|
||||||
await self._update_settings(frame.settings)
|
|
||||||
elif isinstance(frame, BotStoppedSpeakingFrame):
|
elif isinstance(frame, BotStoppedSpeakingFrame):
|
||||||
await self._maybe_resume_frame_processing()
|
await self._maybe_resume_frame_processing()
|
||||||
await self.push_frame(frame, direction)
|
await self.push_frame(frame, direction)
|
||||||
|
|||||||
@@ -325,8 +325,8 @@ class UltravoxRealtimeLLMService(LLMService):
|
|||||||
await self.cancel_task(self._receive_task, timeout=1.0)
|
await self.cancel_task(self._receive_task, timeout=1.0)
|
||||||
self._receive_task = None
|
self._receive_task = None
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: UltravoxRealtimeLLMSettings):
|
async def _update_settings(self, update: UltravoxRealtimeLLMSettings):
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
if "output_medium" in changed:
|
if "output_medium" in changed:
|
||||||
await self._update_output_medium(self._settings.output_medium)
|
await self._update_output_medium(self._settings.output_medium)
|
||||||
return changed
|
return changed
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from pipecat.utils.tracing.service_decorators import traced_stt
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BaseWhisperSTTSettings(STTSettings):
|
class BaseWhisperSTTSettings(STTSettings):
|
||||||
"""Typed settings for Whisper API-based STT services.
|
"""Settings for Whisper API-based STT services.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
base_url: API base URL.
|
base_url: API base URL.
|
||||||
@@ -174,13 +174,13 @@ class BaseWhisperSTTService(SegmentedSTTService):
|
|||||||
def _create_client(self, api_key: Optional[str], base_url: Optional[str]):
|
def _create_client(self, api_key: Optional[str], base_url: Optional[str]):
|
||||||
return AsyncOpenAI(api_key=api_key, base_url=base_url)
|
return AsyncOpenAI(api_key=api_key, base_url=base_url)
|
||||||
|
|
||||||
async def _update_settings_from_typed(self, update: STTSettings) -> set[str]:
|
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||||
"""Apply a typed settings update, syncing instance variables.
|
"""Apply a settings update, syncing instance variables.
|
||||||
|
|
||||||
Keeps ``_language``, ``_prompt``, and ``_temperature`` in sync with
|
Keeps ``_language``, ``_prompt``, and ``_temperature`` in sync with
|
||||||
the typed settings fields.
|
the settings fields.
|
||||||
"""
|
"""
|
||||||
changed = await super()._update_settings_from_typed(update)
|
changed = await super()._update_settings(update)
|
||||||
|
|
||||||
if "language" in changed:
|
if "language" in changed:
|
||||||
self._language = self.language_to_service_language(Language(self._settings.language))
|
self._language = self.language_to_service_language(Language(self._settings.language))
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ def language_to_whisper_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WhisperSTTSettings(STTSettings):
|
class WhisperSTTSettings(STTSettings):
|
||||||
"""Typed settings for the local Whisper (Faster Whisper) STT service.
|
"""Settings for the local Whisper (Faster Whisper) STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
device: Inference device ('cpu', 'cuda', or 'auto').
|
device: Inference device ('cpu', 'cuda', or 'auto').
|
||||||
@@ -191,7 +191,7 @@ class WhisperSTTSettings(STTSettings):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WhisperMLXSTTSettings(STTSettings):
|
class WhisperMLXSTTSettings(STTSettings):
|
||||||
"""Typed settings for the MLX Whisper STT service.
|
"""Settings for the MLX Whisper STT service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
no_speech_prob: Probability threshold for filtering non-speech segments.
|
no_speech_prob: Probability threshold for filtering non-speech segments.
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ def language_to_xtts_language(language: Language) -> Optional[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class XTTSTTSSettings(TTSSettings):
|
class XTTSTTSSettings(TTSSettings):
|
||||||
"""Typed settings for XTTS TTS service.
|
"""Settings for XTTS TTS service.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
base_url: Base URL of the XTTS streaming server.
|
base_url: Base URL of the XTTS streaming server.
|
||||||
|
|||||||
Reference in New Issue
Block a user