Update COMMUNITY_INTEGRATIONS.md with full Service Settings guidance
Broaden the "Dynamic Settings Updates" section into "Service Settings" covering the complete settings pattern: defining a Settings subclass, wiring it into __init__ with defaults + apply_update, and distinguishing init-only config from runtime-updatable fields.
This commit is contained in:
@@ -231,49 +231,102 @@ def can_generate_metrics(self) -> bool:
|
||||
return True
|
||||
```
|
||||
|
||||
### Dynamic Settings Updates
|
||||
### Service Settings
|
||||
|
||||
STT, LLM, and TTS services support runtime configuration changes via `*UpdateSettingsFrame`s (e.g. `STTUpdateSettingsFrame`, `TTSUpdateSettingsFrame`, `LLMUpdateSettingsFrame`).
|
||||
Every STT, LLM, TTS, and image-generation service exposes a **Settings dataclass** that serves two roles:
|
||||
|
||||
Each service declares a settings dataclass that extends the appropriate base (`STTSettings`, `TTSSettings`, `LLMSettings`). Fields default to `NOT_GIVEN` so that update objects can represent sparse deltas:
|
||||
1. **Store mode** — the service's `self._settings` holds the current value of every runtime-updatable field.
|
||||
2. **Delta mode** — an update frame carries only the fields that changed; unset fields remain `NOT_GIVEN`.
|
||||
|
||||
#### Defining your Settings class
|
||||
|
||||
Extend `STTSettings`, `TTSSettings`, `LLMSettings`, or `ImageGenSettings`. The base classes already provide common fields (e.g. `model`, `voice`, `language`). You only need to add **service-specific knobs that should be runtime-updatable**:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from pipecat.services.settings import STTSettings, NOT_GIVEN
|
||||
from pipecat.services.settings import TTSSettings, NOT_GIVEN
|
||||
|
||||
@dataclass
|
||||
class MySTTSettings(STTSettings):
|
||||
"""Settings for my STT service.
|
||||
class MyTTSSettings(TTSSettings):
|
||||
"""Settings for MyTTS service.
|
||||
|
||||
Parameters:
|
||||
region: Cloud region for the service.
|
||||
speaking_rate: Speed multiplier (0.5–2.0).
|
||||
"""
|
||||
|
||||
region: str = field(default_factory=lambda: NOT_GIVEN)
|
||||
speaking_rate: float | None = field(default_factory=lambda: NOT_GIVEN)
|
||||
```
|
||||
|
||||
The service stores its current settings in `self._settings` and declares the type with a class-level annotation for editor support:
|
||||
**What goes in Settings vs. `__init__` params:**
|
||||
|
||||
| Belongs in Settings | Stays as `__init__` params |
|
||||
| -------------------------------------------------------- | ----------------------------------------- |
|
||||
| Model name, voice, language | API keys, auth tokens |
|
||||
| Service-specific tuning knobs (rate, pitch, temperature) | Base URLs, endpoint overrides |
|
||||
| Anything users may want to change mid-session | Audio encoding, sample format |
|
||||
| | Connection parameters (timeouts, retries) |
|
||||
|
||||
The rule of thumb: if a caller might send an update frame to change it at runtime, it belongs in Settings. Everything else is init-only config stored as `self._xxx`.
|
||||
|
||||
#### Wiring settings into `__init__`
|
||||
|
||||
Accept an **optional** `settings` parameter. Build a `default_settings` object with all fields set to real values, then merge any caller overrides with `apply_update`:
|
||||
|
||||
```python
|
||||
class MySTTService(STTService):
|
||||
_settings: MySTTSettings
|
||||
from typing import Optional
|
||||
|
||||
def __init__(self, *, model: str, language: str, region: str, **kwargs):
|
||||
# An initial value should be provided for every settings field.
|
||||
# This will be validated at service start.
|
||||
# (If you track sample_rate, it can be a placeholder value like 0; see
|
||||
# "Sample Rate Handling").
|
||||
super().__init__(
|
||||
settings=MySTTSettings(model=model, language=language, region=region), **kwargs
|
||||
class MyTTSService(TTSService):
|
||||
_settings: MyTTSSettings
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
api_key: str,
|
||||
settings: Optional[MyTTSSettings] = None,
|
||||
**kwargs,
|
||||
):
|
||||
# 1. Defaults — every field has a real value (store mode).
|
||||
default_settings = MyTTSSettings(
|
||||
model="my-model-v1",
|
||||
voice="default-voice",
|
||||
language="en",
|
||||
speaking_rate=1.0,
|
||||
)
|
||||
|
||||
# 2. Merge caller overrides (only given fields win).
|
||||
if settings is not None:
|
||||
default_settings.apply_update(settings)
|
||||
|
||||
# 3. Pass the fully-populated settings to the base class.
|
||||
super().__init__(settings=default_settings, **kwargs)
|
||||
|
||||
# 4. Init-only config stored separately.
|
||||
self._api_key = api_key
|
||||
```
|
||||
|
||||
This pattern lets callers override only what they care about:
|
||||
|
||||
```python
|
||||
# Uses all defaults
|
||||
svc = MyTTSService(api_key="sk-xxx")
|
||||
|
||||
# Overrides just the voice
|
||||
svc = MyTTSService(
|
||||
api_key="sk-xxx",
|
||||
settings=MyTTSSettings(voice="custom-voice"),
|
||||
)
|
||||
```
|
||||
|
||||
#### Reacting to runtime changes
|
||||
|
||||
STT, LLM, and TTS services support runtime configuration changes via `*UpdateSettingsFrame`s (e.g. `STTUpdateSettingsFrame`, `TTSUpdateSettingsFrame`, `LLMUpdateSettingsFrame`).
|
||||
|
||||
To react to runtime setting changes, override `_update_settings`. The base implementation applies the delta to `self._settings` and returns a `dict` mapping each changed field name to its **pre-update** value. Your override should call `super()` first, then act on the changed fields. A common implementation might look like:
|
||||
|
||||
```python
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, reconfiguring the recognizer if needed."""
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
"""Apply a settings update, reconfiguring the connection if needed."""
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
if not changed:
|
||||
@@ -292,7 +345,7 @@ Note that, in this example, the service requires a reconnect to apply the new la
|
||||
If your service can't yet apply certain settings at runtime, call `self._warn_unhandled_updated_settings(changed)` with any unhandled field names so users get a clear log message:
|
||||
|
||||
```python
|
||||
async def _update_settings(self, update: STTSettings) -> dict[str, Any]:
|
||||
async def _update_settings(self, update: TTSSettings) -> dict[str, Any]:
|
||||
changed = await super()._update_settings(update)
|
||||
|
||||
if not changed:
|
||||
|
||||
Reference in New Issue
Block a user