From b78a293ffb70adae599a98196552d6721cf0db09 Mon Sep 17 00:00:00 2001 From: Paul Kompfner Date: Tue, 24 Feb 2026 12:20:14 -0500 Subject: [PATCH] Flatten `input_params` into individual fields on `SonioxSTTSettings` and `GladiaSTTSettings` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes each service-specific field individually visible to the delta/update mechanism (`apply_update`, `given_fields`) and removes the need for complex sync logic between `input_params` and top-level fields like `model`. - Soniox: replace `input_params: SonioxInputParams` with 8 individual fields, simplify `_update_settings` by removing model sync logic, remove unused `is_given` import - Gladia: replace `input_params: GladiaInputParams` with 11 individual fields, resolve deprecated `language` → `language_config` at init time rather than at `_prepare_settings` time --- src/pipecat/services/gladia/stt.py | 113 ++++++++++++++++++++--------- src/pipecat/services/soniox/stt.py | 72 ++++++++++-------- 2 files changed, 120 insertions(+), 65 deletions(-) diff --git a/src/pipecat/services/gladia/stt.py b/src/pipecat/services/gladia/stt.py index d0a6f5a84..c1ce02d87 100644 --- a/src/pipecat/services/gladia/stt.py +++ b/src/pipecat/services/gladia/stt.py @@ -32,7 +32,13 @@ from pipecat.frames.frames import ( UserStartedSpeakingFrame, UserStoppedSpeakingFrame, ) -from pipecat.services.gladia.config import GladiaInputParams +from pipecat.services.gladia.config import ( + GladiaInputParams, + LanguageConfig, + MessagesConfig, + PreProcessingConfig, + RealtimeProcessingConfig, +) from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven from pipecat.services.stt_latency import GLADIA_TTFS_P99 from pipecat.services.stt_service import WebsocketSTTService @@ -185,10 +191,36 @@ class GladiaSTTSettings(STTSettings): """Settings for Gladia STT service. Parameters: - input_params: Gladia ``GladiaInputParams`` for detailed configuration. + encoding: Audio encoding format. + bit_depth: Audio bit depth. + channels: Number of audio channels. + custom_metadata: Additional metadata to include with requests. + endpointing: Silence duration in seconds to mark end of speech. + maximum_duration_without_endpointing: Maximum utterance duration without silence. + language_config: Detailed language configuration. + pre_processing: Audio pre-processing options. + realtime_processing: Real-time processing features. + messages_config: WebSocket message filtering options. + enable_vad: Enable VAD to trigger end of utterance detection. """ - input_params: GladiaInputParams | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + encoding: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + bit_depth: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + channels: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + custom_metadata: Dict[str, Any] | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + endpointing: float | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + maximum_duration_without_endpointing: int | None | _NotGiven = field( + default_factory=lambda: NOT_GIVEN + ) + language_config: LanguageConfig | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + pre_processing: PreProcessingConfig | None | _NotGiven = field( + default_factory=lambda: NOT_GIVEN + ) + realtime_processing: RealtimeProcessingConfig | None | _NotGiven = field( + default_factory=lambda: NOT_GIVEN + ) + messages_config: MessagesConfig | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + enable_vad: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) class GladiaSTTService(WebsocketSTTService): @@ -280,7 +312,29 @@ class GladiaSTTService(WebsocketSTTService): self._region = region self._url = url self._receive_task = None - self._settings = GladiaSTTSettings(model=model, language=None, input_params=params) + + # Resolve deprecated language → language_config at init time + language_config = params.language_config + if not language_config and params.language: + language_code = self.language_to_service_language(params.language) + if language_code: + language_config = LanguageConfig(languages=[language_code], code_switching=False) + + self._settings = GladiaSTTSettings( + model=model, + language=None, + encoding=params.encoding, + bit_depth=params.bit_depth, + channels=params.channels, + custom_metadata=params.custom_metadata, + endpointing=params.endpointing, + maximum_duration_without_endpointing=params.maximum_duration_without_endpointing, + language_config=language_config, + pre_processing=params.pre_processing, + realtime_processing=params.realtime_processing, + messages_config=params.messages_config, + enable_vad=params.enable_vad, + ) self._sync_model_name_to_metrics() # Session management @@ -321,52 +375,43 @@ class GladiaSTTService(WebsocketSTTService): return language_to_gladia_language(language) def _prepare_settings(self) -> Dict[str, Any]: - params = self._settings.input_params + s = self._settings settings = { - "encoding": params.encoding or "wav/pcm", - "bit_depth": params.bit_depth or 16, + "encoding": s.encoding or "wav/pcm", + "bit_depth": s.bit_depth or 16, "sample_rate": self.sample_rate, - "channels": params.channels or 1, - "model": self._settings.model, + "channels": s.channels or 1, + "model": s.model, } # Add custom_metadata if provided - settings["custom_metadata"] = dict(params.custom_metadata or {}) + settings["custom_metadata"] = dict(s.custom_metadata or {}) settings["custom_metadata"]["pipecat"] = pipecat_version() # Add endpointing parameters if provided - if params.endpointing is not None: - settings["endpointing"] = params.endpointing - if params.maximum_duration_without_endpointing is not None: + if s.endpointing is not None: + settings["endpointing"] = s.endpointing + if s.maximum_duration_without_endpointing is not None: settings["maximum_duration_without_endpointing"] = ( - params.maximum_duration_without_endpointing + s.maximum_duration_without_endpointing ) - # Add language configuration (prioritize language_config over deprecated language) - if params.language_config: - settings["language_config"] = params.language_config.model_dump(exclude_none=True) - elif params.language: # Backward compatibility for deprecated parameter - language_code = self.language_to_service_language(params.language) - if language_code: - settings["language_config"] = { - "languages": [language_code], - "code_switching": False, - } + # Add language configuration + if s.language_config: + settings["language_config"] = s.language_config.model_dump(exclude_none=True) # Add pre_processing configuration if provided - if params.pre_processing: - settings["pre_processing"] = params.pre_processing.model_dump(exclude_none=True) + if s.pre_processing: + settings["pre_processing"] = s.pre_processing.model_dump(exclude_none=True) # Add realtime_processing configuration if provided - if params.realtime_processing: - settings["realtime_processing"] = params.realtime_processing.model_dump( - exclude_none=True - ) + if s.realtime_processing: + settings["realtime_processing"] = s.realtime_processing.model_dump(exclude_none=True) # Add messages_config if provided - if params.messages_config: - settings["messages_config"] = params.messages_config.model_dump(exclude_none=True) + if s.messages_config: + settings["messages_config"] = s.messages_config.model_dump(exclude_none=True) return settings @@ -562,7 +607,7 @@ class GladiaSTTService(WebsocketSTTService): Broadcasts UserStartedSpeakingFrame and optionally triggers interruption when VAD is enabled. """ - if not self._settings.input_params.enable_vad or self._is_speaking: + if not self._settings.enable_vad or self._is_speaking: return logger.debug(f"{self} User started speaking") @@ -577,7 +622,7 @@ class GladiaSTTService(WebsocketSTTService): Broadcasts UserStoppedSpeakingFrame when VAD is enabled. """ - if not self._settings.input_params.enable_vad or not self._is_speaking: + if not self._settings.enable_vad or not self._is_speaking: return self._is_speaking = False await self.broadcast_frame(UserStoppedSpeakingFrame) diff --git a/src/pipecat/services/soniox/stt.py b/src/pipecat/services/soniox/stt.py index 356b23162..3160d19a6 100644 --- a/src/pipecat/services/soniox/stt.py +++ b/src/pipecat/services/soniox/stt.py @@ -24,7 +24,7 @@ from pipecat.frames.frames import ( VADUserStoppedSpeakingFrame, ) from pipecat.processors.frame_processor import FrameDirection -from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven, is_given +from pipecat.services.settings import NOT_GIVEN, STTSettings, _NotGiven from pipecat.services.stt_latency import SONIOX_TTFS_P99 from pipecat.services.stt_service import WebsocketSTTService from pipecat.transcriptions.language import Language @@ -141,10 +141,28 @@ class SonioxSTTSettings(STTSettings): """Settings for Soniox STT service. Parameters: - input_params: Soniox ``SonioxInputParams`` for detailed configuration. + audio_format: Audio format to use for transcription. + num_channels: Number of channels to use for transcription. + language_hints: List of language hints to use for transcription. + language_hints_strict: If true, strictly enforce language hints. + context: Customization for transcription. String for models with + context_version 1 and SonioxContextObject for models with + context_version 2. + enable_speaker_diarization: Whether to enable speaker diarization. + enable_language_identification: Whether to enable language identification. + client_reference_id: Client reference ID to use for transcription. """ - input_params: SonioxInputParams | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + audio_format: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + num_channels: int | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + language_hints: List[Language] | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + language_hints_strict: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + context: SonioxContextObject | str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + enable_speaker_diarization: bool | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + enable_language_identification: bool | None | _NotGiven = field( + default_factory=lambda: NOT_GIVEN + ) + client_reference_id: str | None | _NotGiven = field(default_factory=lambda: NOT_GIVEN) class SonioxSTTService(WebsocketSTTService): @@ -199,7 +217,15 @@ class SonioxSTTService(WebsocketSTTService): self._settings = SonioxSTTSettings( model=params.model, - input_params=params, + language=None, + audio_format=params.audio_format, + num_channels=params.num_channels, + language_hints=params.language_hints, + language_hints_strict=params.language_hints_strict, + context=params.context, + enable_speaker_diarization=params.enable_speaker_diarization, + enable_language_identification=params.enable_language_identification, + client_reference_id=params.client_reference_id, ) self._sync_model_name_to_metrics() @@ -226,12 +252,7 @@ class SonioxSTTService(WebsocketSTTService): await self._connect() async def _update_settings(self, delta: SonioxSTTSettings) -> dict[str, Any]: - """Apply a settings delta, keeping ``input_params`` in sync. - - Top-level ``model`` is the source of truth. When it is given in - *delta* its value is propagated into ``input_params``. When only - ``input_params`` is given, its ``model`` is propagated *up* to the - top-level field. + """Apply settings delta. Settings are stored but not applied to the active connection. @@ -241,22 +262,11 @@ class SonioxSTTService(WebsocketSTTService): Returns: Dict mapping changed field names to their previous values. """ - model_given = is_given(getattr(delta, "model", NOT_GIVEN)) - changed = await super()._update_settings(delta) if not changed: return changed - # --- Sync model -------------------------------------------------- - if model_given: - # Top-level model wins → push into input_params. - self._settings.input_params.model = self._settings.model - elif "input_params" in changed and self._settings.input_params.model is not None: - # Only input_params was given → pull model up. - self._settings.model = self._settings.input_params.model - self._sync_model_name_to_metrics() - # TODO: someday we could reconnect here to apply updated settings. # Code might look something like the below: # await self._disconnect() @@ -377,26 +387,26 @@ class SonioxSTTService(WebsocketSTTService): # Either one or the other is required. enable_endpoint_detection = not self._vad_force_turn_endpoint - params = self._settings.input_params + s = self._settings - context = params.context + context = s.context if isinstance(context, SonioxContextObject): context = context.model_dump() # Send the initial configuration message. config = { "api_key": self._api_key, - "model": self._settings.model, - "audio_format": params.audio_format, - "num_channels": params.num_channels or 1, + "model": s.model, + "audio_format": s.audio_format, + "num_channels": s.num_channels or 1, "enable_endpoint_detection": enable_endpoint_detection, "sample_rate": self.sample_rate, - "language_hints": _prepare_language_hints(params.language_hints), - "language_hints_strict": params.language_hints_strict, + "language_hints": _prepare_language_hints(s.language_hints), + "language_hints_strict": s.language_hints_strict, "context": context, - "enable_speaker_diarization": params.enable_speaker_diarization, - "enable_language_identification": params.enable_language_identification, - "client_reference_id": params.client_reference_id, + "enable_speaker_diarization": s.enable_speaker_diarization, + "enable_language_identification": s.enable_language_identification, + "client_reference_id": s.client_reference_id, } # Send the configuration message.