Change apply_update / _update_settings return type from set[str] to dict[str, Any]. The dict maps each changed field name to its pre-update value, enabling services to do granular diffing of complex settings objects. Existing call-site patterns ("field" in changed, if changed, iteration) work unchanged; set-difference sites use changed.keys() - {...}.
This commit is contained in:
@@ -267,10 +267,10 @@ class MySTTService(STTService):
|
||||
self._settings = MySTTSettings(model=model, region=region)
|
||||
```
|
||||
|
||||
To react to runtime setting changes, override `_update_settings`. The base implementation applies the delta to `self._settings` and returns the set of field names that changed. Your override should call `super()` first, then act on the changed fields:
|
||||
To react to runtime setting changes, override `_update_settings`. The base implementation applies the delta to `self._settings` and returns a `dict` mapping each changed field name to its **pre-update** value. Your override should call `super()` first, then act on the changed fields:
|
||||
|
||||
```python
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, reconfiguring the recognizer if needed."""
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
@@ -282,12 +282,14 @@ async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
return changed
|
||||
```
|
||||
|
||||
The dict keys work like a set for membership tests (`"language" in changed`) and truthiness (`if changed`). Use `changed.keys() - {"language"}` for set difference, or `changed["language"]` to inspect the previous value of a field.
|
||||
|
||||
Note that, in this example, the service requires a reconnect to apply the new language. Consider, for each setting, whether your service requires reconnection or can apply changes in-place.
|
||||
|
||||
If your service can't yet apply certain settings at runtime, call `self._warn_unhandled_updated_settings(changed)` with the set of unhandled field names so users get a clear log message:
|
||||
If your service can't yet apply certain settings at runtime, call `self._warn_unhandled_updated_settings(changed)` with the unhandled field names so users get a clear log message:
|
||||
|
||||
```python
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
if not changed:
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
from typing import Any, AsyncGenerator, Dict, Set
|
||||
from typing import Any, AsyncGenerator, Dict
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -97,12 +97,13 @@ class AIService(FrameProcessor):
|
||||
"""
|
||||
pass
|
||||
|
||||
async def _update_settings(self, update: ServiceSettings) -> Set[str]:
|
||||
"""Apply a settings update and return the set of changed field names.
|
||||
async def _update_settings(self, update: ServiceSettings) -> Dict[str, Any]:
|
||||
"""Apply a settings update and return the changed fields.
|
||||
|
||||
The update is applied to ``_settings`` and the changed-field set is
|
||||
returned. The ``model`` field is handled specially: when it changes,
|
||||
``set_model_name`` is called.
|
||||
The update is applied to ``_settings`` and a dict mapping each changed
|
||||
field name to its **pre-update** value is returned. The ``model``
|
||||
field is handled specially: when it changes, ``set_model_name`` is
|
||||
called.
|
||||
|
||||
Concrete services should override this method (calling ``super()``)
|
||||
to react to specific changed fields (e.g. reconnect on voice change).
|
||||
@@ -111,7 +112,7 @@ class AIService(FrameProcessor):
|
||||
update: A settings delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = self._settings.apply_update(update)
|
||||
|
||||
@@ -119,16 +120,15 @@ class AIService(FrameProcessor):
|
||||
self.set_model_name(self._settings.model)
|
||||
|
||||
if changed:
|
||||
logger.info(f"{self.name}: updated settings fields: {changed}")
|
||||
logger.info(f"{self.name}: updated settings fields: {set(changed)}")
|
||||
|
||||
return changed
|
||||
|
||||
def _warn_unhandled_updated_settings(self, unhandled: Set[str]):
|
||||
def _warn_unhandled_updated_settings(self, unhandled):
|
||||
"""Log a warning for settings changes that won't take effect at runtime.
|
||||
|
||||
Convenience helper for ``_update_settings`` overrides. Call with the
|
||||
set of field names that changed but that the service does not (yet)
|
||||
apply at runtime.
|
||||
Convenience helper for ``_update_settings`` overrides. Accepts any
|
||||
iterable of field names (a ``dict``, ``set``, ``dict_keys``, etc.).
|
||||
|
||||
Args:
|
||||
unhandled: Field names that changed but are not applied.
|
||||
|
||||
@@ -184,7 +184,7 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update.
|
||||
|
||||
Settings are stored but not applied to the active connection.
|
||||
@@ -193,7 +193,7 @@ class AssemblyAISTTService(WebsocketSTTService):
|
||||
update: A :class:`STTSettings` (or ``AssemblyAISTTSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
|
||||
@@ -307,7 +307,7 @@ class AWSNovaSonicLLMService(LLMService):
|
||||
# settings
|
||||
#
|
||||
|
||||
async def _update_settings(self, update: LLMSettings) -> set[str]:
|
||||
async def _update_settings(self, update: LLMSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update.
|
||||
|
||||
Settings are stored but not applied to the active connection.
|
||||
|
||||
@@ -15,7 +15,7 @@ import os
|
||||
import random
|
||||
import string
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -140,7 +140,7 @@ class AWSTranscribeSTTService(WebsocketSTTService):
|
||||
}
|
||||
return encoding_map.get(encoding, encoding)
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update.
|
||||
|
||||
Settings are stored but not applied to the active connection.
|
||||
|
||||
@@ -12,7 +12,7 @@ Speech SDK for real-time audio transcription.
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -123,7 +123,7 @@ class AzureSTTService(STTService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update.
|
||||
|
||||
Settings are stored but not applied to the active recognizer.
|
||||
|
||||
@@ -13,7 +13,7 @@ the Cartesia Live transcription API for real-time speech recognition.
|
||||
import json
|
||||
import urllib.parse
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -294,14 +294,14 @@ class CartesiaSTTService(WebsocketSTTService):
|
||||
|
||||
await self._disconnect_websocket()
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update.
|
||||
|
||||
Args:
|
||||
update: A :class:`STTSettings` (or ``CartesiaSTTSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
|
||||
@@ -344,7 +344,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update.
|
||||
|
||||
Settings are stored but not applied to the active connection.
|
||||
|
||||
@@ -330,7 +330,7 @@ class DeepgramFluxSTTService(WebsocketSTTService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update.
|
||||
|
||||
Settings are stored but not applied to the active connection.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"""Deepgram speech-to-text service implementation."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
from typing import Any, AsyncGenerator, Dict, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -195,7 +195,7 @@ class DeepgramSTTService(STTService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, keeping ``live_options`` in sync.
|
||||
|
||||
Top-level ``model`` and ``language`` are the source of truth. When
|
||||
|
||||
@@ -15,7 +15,7 @@ languages, and various Deepgram features.
|
||||
import asyncio
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -163,7 +163,7 @@ class DeepgramSageMakerSTTService(STTService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, keeping ``live_options`` in sync.
|
||||
|
||||
Top-level ``model`` and ``language`` are the source of truth. When
|
||||
|
||||
@@ -16,7 +16,7 @@ import io
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
import aiohttp
|
||||
from loguru import logger
|
||||
@@ -294,7 +294,7 @@ class ElevenLabsSTTService(SegmentedSTTService):
|
||||
"""
|
||||
return language_to_elevenlabs_language(language)
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update.
|
||||
|
||||
Converts language to ElevenLabs format before applying and keeps
|
||||
@@ -304,7 +304,7 @@ class ElevenLabsSTTService(SegmentedSTTService):
|
||||
update: A :class:`STTSettings` (or ``ElevenLabsSTTSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
# Convert language to ElevenLabs format before applying
|
||||
if is_given(update.language) and isinstance(update.language, Language):
|
||||
@@ -543,7 +543,7 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update and reconnect if anything changed.
|
||||
|
||||
Converts language to ElevenLabs format before applying and keeps
|
||||
@@ -553,7 +553,7 @@ class ElevenLabsRealtimeSTTService(WebsocketSTTService):
|
||||
update: A :class:`STTSettings` (or ``ElevenLabsRealtimeSTTSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
# Convert language to ElevenLabs format before applying
|
||||
if is_given(update.language) and isinstance(update.language, Language):
|
||||
|
||||
@@ -471,7 +471,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
|
||||
voice_settings[key] = val
|
||||
return voice_settings or None
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, reconnecting as needed.
|
||||
|
||||
Uses the declarative ``URL_FIELDS`` and ``VOICE_SETTINGS_FIELDS``
|
||||
@@ -482,7 +482,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
|
||||
update: A :class:`TTSSettings` (or ``ElevenLabsTTSSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
@@ -520,7 +520,7 @@ class ElevenLabsTTSService(AudioContextWordTTSService):
|
||||
# Reconnect applies all settings; only warn about fields not handled
|
||||
# by voice settings or URL changes.
|
||||
handled = ElevenLabsTTSSettings.URL_FIELDS | ElevenLabsTTSSettings.VOICE_SETTINGS_FIELDS
|
||||
self._warn_unhandled_updated_settings(changed - handled)
|
||||
self._warn_unhandled_updated_settings(changed.keys() - handled)
|
||||
|
||||
return changed
|
||||
|
||||
@@ -964,14 +964,14 @@ class ElevenLabsHttpTTSService(WordTTSService):
|
||||
def _set_voice_settings(self):
|
||||
return build_elevenlabs_voice_settings(self._settings)
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update and rebuild voice settings.
|
||||
|
||||
Args:
|
||||
update: A :class:`TTSSettings` (or ``ElevenLabsHttpTTSSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
if changed:
|
||||
|
||||
@@ -12,7 +12,7 @@ transcription using segmented audio processing.
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
@@ -251,7 +251,7 @@ class FalSTTService(SegmentedSTTService):
|
||||
"""
|
||||
return language_to_fal_language(language)
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, converting language if changed."""
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ for streaming text-to-speech synthesis with customizable voice parameters.
|
||||
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Literal, Optional
|
||||
from typing import Any, AsyncGenerator, Literal, Optional
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
@@ -184,7 +184,7 @@ class FishAudioTTSService(InterruptibleTTSService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update and reconnect if needed.
|
||||
|
||||
Any change to voice or model triggers a WebSocket reconnect.
|
||||
@@ -193,7 +193,7 @@ class FishAudioTTSService(InterruptibleTTSService):
|
||||
update: A :class:`TTSSettings` (or ``FishAudioTTSSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
if changed:
|
||||
|
||||
@@ -379,7 +379,7 @@ class GladiaSTTService(WebsocketSTTService):
|
||||
await super().start(frame)
|
||||
await self._connect()
|
||||
|
||||
async def _update_settings(self, update: GladiaSTTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: GladiaSTTSettings) -> dict[str, Any]:
|
||||
"""Apply settings update.
|
||||
|
||||
Settings are stored but not applied to the active session.
|
||||
@@ -388,7 +388,7 @@ class GladiaSTTService(WebsocketSTTService):
|
||||
update: A settings delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
|
||||
@@ -804,7 +804,7 @@ class GeminiLiveLLMService(LLMService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: LLMSettings) -> set[str]:
|
||||
async def _update_settings(self, update: LLMSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update.
|
||||
|
||||
Settings are stored but not applied to the active connection.
|
||||
|
||||
@@ -630,7 +630,7 @@ class GoogleSTTService(STTService):
|
||||
logger.debug(f"Switching STT languages to: {languages}")
|
||||
await self._update_settings(GoogleSTTSettings(languages=list(languages)))
|
||||
|
||||
async def _update_settings(self, update: GoogleSTTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: GoogleSTTSettings) -> dict[str, Any]:
|
||||
"""Apply settings update and reconnect if anything changed.
|
||||
|
||||
Handles ``language`` from base ``set_language`` by converting it to
|
||||
@@ -642,7 +642,7 @@ class GoogleSTTService(STTService):
|
||||
update: A settings delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
from pipecat.services.settings import is_given
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from pipecat.utils.tracing.service_decorators import traced_tts
|
||||
os.environ["GRPC_ENABLE_FORK_SUPPORT"] = "false"
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, List, Literal, Optional
|
||||
from typing import Any, AsyncGenerator, List, Literal, Optional
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
@@ -680,7 +680,7 @@ class GoogleHttpTTSService(TTSService):
|
||||
"""
|
||||
return language_to_google_tts_language(language)
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Override to handle speaking_rate validation.
|
||||
|
||||
Args:
|
||||
@@ -1024,7 +1024,7 @@ class GoogleTTSService(GoogleBaseTTSService):
|
||||
credentials, credentials_path
|
||||
)
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Override to handle speaking_rate validation.
|
||||
|
||||
Args:
|
||||
@@ -1259,14 +1259,14 @@ class GeminiTTSService(GoogleBaseTTSService):
|
||||
f"Current rate of {self.sample_rate}Hz may cause issues."
|
||||
)
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update with voice validation.
|
||||
|
||||
Args:
|
||||
update: Settings delta. Can include 'voice', 'prompt', etc.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
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.")
|
||||
|
||||
@@ -13,7 +13,7 @@ WebSocket API for streaming audio transcription.
|
||||
import base64
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
@@ -171,14 +171,14 @@ class GradiumSTTService(WebsocketSTTService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, sync params, and reconnect.
|
||||
|
||||
Args:
|
||||
update: A :class:`STTSettings` (or ``GradiumSTTSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
if not changed:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import base64
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
@@ -119,14 +119,14 @@ class GradiumTTSService(InterruptibleWordTTSService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update and reconnect if voice changed.
|
||||
|
||||
Args:
|
||||
update: A :class:`TTSSettings` (or ``GradiumTTSSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
prev_voice = self._voice_id
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
@@ -165,7 +165,7 @@ class InworldHttpTTSService(WordTTSService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update.
|
||||
|
||||
Settings are stored but not applied to the active connection.
|
||||
|
||||
@@ -313,14 +313,14 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService):
|
||||
await self._cancel_sequential_runner_task()
|
||||
await self._cancel_summary_task()
|
||||
|
||||
async def _update_settings(self, update: LLMSettings) -> set[str]:
|
||||
async def _update_settings(self, update: LLMSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, handling turn-completion fields.
|
||||
|
||||
Args:
|
||||
update: An LLM settings delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import asyncio
|
||||
import base64
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
import aiohttp
|
||||
from loguru import logger
|
||||
@@ -181,7 +181,7 @@ class NeuphonicTTSService(InterruptibleTTSService):
|
||||
"""
|
||||
return language_to_neuphonic_lang_code(language)
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update and reconnect with new configuration."""
|
||||
changed = await super()._update_settings(update)
|
||||
if changed:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import asyncio
|
||||
from concurrent.futures import CancelledError as FuturesCancelledError
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, List, Mapping, Optional
|
||||
from typing import Any, AsyncGenerator, List, Mapping, Optional
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
@@ -579,14 +579,14 @@ class NvidiaSegmentedSTTService(SegmentedSTTService):
|
||||
self._config = self._create_recognition_config()
|
||||
logger.debug(f"Initialized NvidiaSegmentedSTTService with model: {self.model_name}")
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update and sync internal state.
|
||||
|
||||
Args:
|
||||
update: A :class:`STTSettings` (or ``NvidiaSegmentedSTTSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Provides two STT services:
|
||||
import base64
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Literal, Optional, Union
|
||||
from typing import Any, AsyncGenerator, Literal, Optional, Union
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -268,7 +268,7 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update and send session update if needed.
|
||||
|
||||
Keeps ``_language_code`` and ``_prompt`` in sync with settings
|
||||
@@ -278,7 +278,7 @@ class OpenAIRealtimeSTTService(WebsocketSTTService):
|
||||
update: A :class:`STTSettings` (or ``OpenAIRealtimeSTTSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import json
|
||||
import struct
|
||||
import warnings
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
import aiohttp
|
||||
from loguru import logger
|
||||
@@ -215,7 +215,7 @@ class PlayHTTTSService(InterruptibleTTSService):
|
||||
"""
|
||||
return True
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update.
|
||||
|
||||
Settings are stored but not applied to the active connection.
|
||||
|
||||
@@ -13,7 +13,7 @@ using Rime's API for streaming and batch audio synthesis.
|
||||
import base64
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
import aiohttp
|
||||
from loguru import logger
|
||||
@@ -271,7 +271,7 @@ class RimeTTSService(AudioContextWordTTSService):
|
||||
self._extra_msg_fields["inlineSpeedAlpha"] = ",".join(speed_vals + [str(speed)])
|
||||
return f"[{text}]"
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update and reconnect if voice changed."""
|
||||
prev_voice = self._voice_id
|
||||
changed = await super()._update_settings(update)
|
||||
@@ -977,7 +977,7 @@ class RimeNonJsonTTSService(InterruptibleTTSService):
|
||||
except Exception as e:
|
||||
yield ErrorFrame(error=f"Unknown error occurred: {e}")
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update and reconnect if necessary.
|
||||
|
||||
Since all settings are WebSocket URL query parameters,
|
||||
|
||||
@@ -13,7 +13,7 @@ can handle multiple audio formats for Indian language speech recognition.
|
||||
|
||||
import base64
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Dict, Literal, Optional
|
||||
from typing import Any, AsyncGenerator, Dict, Literal, Optional
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
@@ -306,14 +306,14 @@ class SarvamSTTService(STTService):
|
||||
if self._socket_client:
|
||||
await self._socket_client.flush()
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, validate, sync state, and reconnect.
|
||||
|
||||
Args:
|
||||
update: A :class:`STTSettings` (or ``SarvamSTTSettings``) delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
|
||||
Raises:
|
||||
ValueError: If a setting is not supported by the current model.
|
||||
|
||||
@@ -42,7 +42,7 @@ import base64
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import AsyncGenerator, Dict, List, Optional, Tuple
|
||||
from typing import Any, AsyncGenerator, Dict, List, Optional, Tuple
|
||||
|
||||
import aiohttp
|
||||
from loguru import logger
|
||||
@@ -953,12 +953,12 @@ class SarvamTTSService(InterruptibleTTSService):
|
||||
if isinstance(frame, (LLMFullResponseEndFrame, EndFrame)):
|
||||
await self.flush_audio()
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update and resend config if voice changed."""
|
||||
changed = await super()._update_settings(update)
|
||||
if "voice" in changed:
|
||||
await self._send_config()
|
||||
self._warn_unhandled_updated_settings(changed - {"voice"})
|
||||
self._warn_unhandled_updated_settings(changed.keys() - {"voice"})
|
||||
return changed
|
||||
|
||||
async def _connect(self):
|
||||
|
||||
@@ -19,7 +19,7 @@ Key concepts:
|
||||
service's current settings *and* for update objects. Fields set to
|
||||
``NOT_GIVEN`` are simply skipped when applying an update.
|
||||
- **apply_update**: Applies a delta onto a target settings object and returns
|
||||
the set of field names that actually changed.
|
||||
a dict mapping each changed field name to its previous value.
|
||||
- **from_mapping**: Constructs a settings object from a plain dict,
|
||||
supporting field aliases (e.g. ``"voice_id"`` → ``"voice"``).
|
||||
- **Extras**: Unknown keys land in the ``extra`` dict so services that have
|
||||
@@ -30,7 +30,7 @@ from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from dataclasses import dataclass, field, fields
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Mapping, Optional, Set, Type, TypeVar
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Mapping, Optional, Type, TypeVar
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -140,8 +140,8 @@ class ServiceSettings:
|
||||
result.update(self.extra)
|
||||
return result
|
||||
|
||||
def apply_update(self: _S, update: _S) -> Set[str]:
|
||||
"""Apply *update* onto this settings object, returning changed field names.
|
||||
def apply_update(self: _S, update: _S) -> Dict[str, Any]:
|
||||
"""Apply *update* onto this settings object, returning changed fields.
|
||||
|
||||
Only fields in *update* that are **given** (i.e. not ``NOT_GIVEN``)
|
||||
are considered. A field is "changed" if its new value differs from
|
||||
@@ -154,17 +154,19 @@ class ServiceSettings:
|
||||
update: A settings object of the same type containing the delta.
|
||||
|
||||
Returns:
|
||||
The set of field names whose values actually changed.
|
||||
A dict mapping each changed field name to its **pre-update** value.
|
||||
Use ``changed.keys()`` for the set of names, or index with
|
||||
``changed["field"]`` to inspect the old value.
|
||||
|
||||
Examples::
|
||||
|
||||
current = TTSSettings(voice="alice", language="en")
|
||||
delta = TTSSettings(voice="bob")
|
||||
changed = current.apply_update(delta)
|
||||
# changed == {"voice"}
|
||||
# changed == {"voice": "alice"}
|
||||
# current.voice == "bob", current.language == "en"
|
||||
"""
|
||||
changed: Set[str] = set()
|
||||
changed: Dict[str, Any] = {}
|
||||
for f in fields(self):
|
||||
if f.name == "extra":
|
||||
continue
|
||||
@@ -174,14 +176,14 @@ class ServiceSettings:
|
||||
old_val = getattr(self, f.name)
|
||||
if old_val != new_val:
|
||||
setattr(self, f.name, new_val)
|
||||
changed.add(f.name)
|
||||
changed[f.name] = old_val
|
||||
|
||||
# Merge extra
|
||||
for key, new_val in update.extra.items():
|
||||
old_val = self.extra.get(key, NOT_GIVEN)
|
||||
if old_val != new_val:
|
||||
self.extra[key] = new_val
|
||||
changed.add(key)
|
||||
changed[key] = old_val
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ class SonioxSTTService(WebsocketSTTService):
|
||||
await super().start(frame)
|
||||
await self._connect()
|
||||
|
||||
async def _update_settings(self, update: SonioxSTTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: SonioxSTTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, keeping ``input_params`` in sync.
|
||||
|
||||
Top-level ``model`` is the source of truth. When it is given in
|
||||
@@ -231,7 +231,7 @@ class SonioxSTTService(WebsocketSTTService):
|
||||
update: A settings delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
model_given = is_given(getattr(update, "model", NOT_GIVEN))
|
||||
|
||||
|
||||
@@ -480,7 +480,7 @@ class SpeechmaticsSTTService(STTService):
|
||||
await super().start(frame)
|
||||
await self._connect()
|
||||
|
||||
async def _update_settings(self, update: SpeechmaticsSTTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: SpeechmaticsSTTSettings) -> dict[str, Any]:
|
||||
"""Apply settings update, reconnecting only when necessary.
|
||||
|
||||
Fields are classified into three categories (see
|
||||
@@ -497,7 +497,7 @@ class SpeechmaticsSTTService(STTService):
|
||||
update: A settings delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
@@ -505,7 +505,7 @@ class SpeechmaticsSTTService(STTService):
|
||||
return changed
|
||||
|
||||
no_reconnect = SpeechmaticsSTTSettings.HOT_FIELDS | SpeechmaticsSTTSettings.LOCAL_FIELDS
|
||||
needs_reconnect = bool(changed - no_reconnect)
|
||||
needs_reconnect = bool(changed.keys() - no_reconnect)
|
||||
|
||||
if needs_reconnect:
|
||||
# Connection-level fields changed — rebuild the SDK config
|
||||
|
||||
@@ -236,19 +236,19 @@ class STTService(AIService):
|
||||
await super().cleanup()
|
||||
await self._cancel_ttfb_timeout()
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply an STT settings update.
|
||||
|
||||
Handles ``model`` (via parent). Does **not** call ``set_language``
|
||||
— concrete services should override this method and handle language
|
||||
changes (including any reconnect logic) based on the returned
|
||||
changed-field set.
|
||||
changed-field dict.
|
||||
|
||||
Args:
|
||||
update: An STT settings delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
changed = await super()._update_settings(update)
|
||||
return changed
|
||||
|
||||
@@ -432,20 +432,20 @@ class TTSService(AIService):
|
||||
if not (agg_type == aggregation_type and func == transform_function)
|
||||
]
|
||||
|
||||
async def _update_settings(self, update: TTSSettings) -> set[str]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a TTS settings update.
|
||||
|
||||
Handles ``model`` (via parent) and syncs ``_voice_id`` when voice
|
||||
changes. Translates language values before applying. Does **not**
|
||||
call ``set_voice`` or ``set_model`` directly — concrete services
|
||||
should override this method and handle reconnect logic based on the
|
||||
returned changed-field set.
|
||||
returned changed-field dict.
|
||||
|
||||
Args:
|
||||
update: A TTS settings delta.
|
||||
|
||||
Returns:
|
||||
Set of field names whose values actually changed.
|
||||
Dict mapping changed field names to their previous values.
|
||||
"""
|
||||
# Translate language *before* applying so the stored value is canonical
|
||||
if is_given(update.language) and update.language is not None:
|
||||
|
||||
@@ -329,7 +329,7 @@ class UltravoxRealtimeLLMService(LLMService):
|
||||
changed = await super()._update_settings(update)
|
||||
if "output_medium" in changed:
|
||||
await self._update_output_medium(self._settings.output_medium)
|
||||
self._warn_unhandled_updated_settings(changed - {"output_medium"})
|
||||
self._warn_unhandled_updated_settings(changed.keys() - {"output_medium"})
|
||||
return changed
|
||||
|
||||
#
|
||||
|
||||
@@ -11,7 +11,7 @@ interface, including language mapping, metrics generation, and error handling.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import AsyncGenerator, Optional
|
||||
from typing import Any, AsyncGenerator, Optional
|
||||
|
||||
from loguru import logger
|
||||
from openai import AsyncOpenAI
|
||||
@@ -174,7 +174,7 @@ class BaseWhisperSTTService(SegmentedSTTService):
|
||||
def _create_client(self, api_key: Optional[str], base_url: Optional[str]):
|
||||
return AsyncOpenAI(api_key=api_key, base_url=base_url)
|
||||
|
||||
async def _update_settings(self, update: STTSettings) -> set[str]:
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, syncing instance variables.
|
||||
|
||||
Keeps ``_language``, ``_prompt``, and ``_temperature`` in sync with
|
||||
|
||||
@@ -100,7 +100,8 @@ class TestApplyUpdate:
|
||||
current = TTSSettings(voice="alice", language="en")
|
||||
delta = TTSSettings(voice="bob")
|
||||
changed = current.apply_update(delta)
|
||||
assert changed == {"voice"}
|
||||
assert changed.keys() == {"voice"}
|
||||
assert changed["voice"] == "alice" # old value
|
||||
assert current.voice == "bob"
|
||||
assert current.language == "en"
|
||||
|
||||
@@ -108,14 +109,14 @@ class TestApplyUpdate:
|
||||
current = TTSSettings(voice="alice", language="en")
|
||||
delta = TTSSettings(voice="alice")
|
||||
changed = current.apply_update(delta)
|
||||
assert changed == set()
|
||||
assert changed == {}
|
||||
assert current.voice == "alice"
|
||||
|
||||
def test_apply_update_not_given_skipped(self):
|
||||
current = TTSSettings(voice="alice", language="en")
|
||||
delta = TTSSettings() # all NOT_GIVEN
|
||||
changed = current.apply_update(delta)
|
||||
assert changed == set()
|
||||
assert changed == {}
|
||||
assert current.voice == "alice"
|
||||
assert current.language == "en"
|
||||
|
||||
@@ -123,7 +124,9 @@ class TestApplyUpdate:
|
||||
current = LLMSettings(temperature=0.7, max_tokens=100)
|
||||
delta = LLMSettings(temperature=0.9, max_tokens=200, top_p=0.95)
|
||||
changed = current.apply_update(delta)
|
||||
assert changed == {"temperature", "max_tokens", "top_p"}
|
||||
assert changed.keys() == {"temperature", "max_tokens", "top_p"}
|
||||
assert changed["temperature"] == 0.7
|
||||
assert changed["max_tokens"] == 100
|
||||
assert current.temperature == 0.9
|
||||
assert current.max_tokens == 200
|
||||
assert current.top_p == 0.95
|
||||
@@ -135,6 +138,7 @@ class TestApplyUpdate:
|
||||
delta.extra = {"speed": 1.2}
|
||||
changed = current.apply_update(delta)
|
||||
assert "speed" in changed
|
||||
assert changed["speed"] == 1.0 # old value
|
||||
assert current.extra == {"speed": 1.2, "stability": 0.5}
|
||||
|
||||
def test_apply_update_extra_no_change(self):
|
||||
@@ -143,13 +147,14 @@ class TestApplyUpdate:
|
||||
delta = TTSSettings()
|
||||
delta.extra = {"speed": 1.0}
|
||||
changed = current.apply_update(delta)
|
||||
assert changed == set()
|
||||
assert changed == {}
|
||||
|
||||
def test_apply_update_model_field(self):
|
||||
current = ServiceSettings(model="old-model")
|
||||
delta = ServiceSettings(model="new-model")
|
||||
changed = current.apply_update(delta)
|
||||
assert changed == {"model"}
|
||||
assert changed.keys() == {"model"}
|
||||
assert changed["model"] == "old-model"
|
||||
assert current.model == "new-model"
|
||||
|
||||
def test_apply_update_none_is_a_valid_value(self):
|
||||
@@ -165,6 +170,7 @@ class TestApplyUpdate:
|
||||
delta = TTSSettings(language="en")
|
||||
changed = current.apply_update(delta)
|
||||
assert "language" in changed
|
||||
assert changed["language"] is None # old value was None
|
||||
assert current.language == "en"
|
||||
|
||||
|
||||
@@ -293,7 +299,9 @@ class TestRoundtrip:
|
||||
delta = TTSSettings.from_mapping(raw)
|
||||
|
||||
changed = current.apply_update(delta)
|
||||
assert changed == {"voice", "speed"}
|
||||
assert changed.keys() == {"voice", "speed"}
|
||||
assert changed["voice"] == "alice"
|
||||
assert changed["speed"] == 1.0
|
||||
assert current.voice == "bob"
|
||||
assert current.language == "en"
|
||||
assert current.extra["speed"] == 1.2
|
||||
@@ -303,6 +311,7 @@ class TestRoundtrip:
|
||||
current = LLMSettings(model="gpt-4o", temperature=0.7)
|
||||
delta = LLMSettings.from_mapping({"model": "gpt-4o-mini", "temperature": 0.9})
|
||||
changed = current.apply_update(delta)
|
||||
assert changed == {"model", "temperature"}
|
||||
assert changed.keys() == {"model", "temperature"}
|
||||
assert changed["model"] == "gpt-4o"
|
||||
assert current.model == "gpt-4o-mini"
|
||||
assert current.temperature == 0.9
|
||||
|
||||
Reference in New Issue
Block a user