From 885912198a4d74d241182a0cbb32b3591a8238b0 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 22 Jul 2026 12:19:02 +0100 Subject: [PATCH 1/3] Replace operating_point with model in voice SDK Switch VoiceAgentConfig, presets and client wiring from `operating_point` to the new `model` field (defaulting to `Model.ENHANCED`). `operating_point` is kept as a deprecated field that is automatically migrated to `model` during validation, so existing configs (including JSON) continue to work. Update docstrings, presets and tests accordingly. --- sdk/voice/speechmatics/voice/_client.py | 1 + sdk/voice/speechmatics/voice/_models.py | 18 +++++++++++++----- sdk/voice/speechmatics/voice/_presets.py | 19 +++++++++---------- tests/voice/test_04_models.py | 6 +++--- tests/voice/test_14_presets.py | 6 +++--- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_client.py b/sdk/voice/speechmatics/voice/_client.py index c0988dd3..97b246cb 100644 --- a/sdk/voice/speechmatics/voice/_client.py +++ b/sdk/voice/speechmatics/voice/_client.py @@ -400,6 +400,7 @@ def _prepare_config( language=config.language, domain=config.domain, output_locale=config.output_locale, + model=config.model, operating_point=config.operating_point, diarization="speaker" if config.enable_diarization else None, enable_partials=True, diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index b4a432c2..89183775 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -17,6 +17,7 @@ from typing_extensions import Self from speechmatics.rt import AudioEncoding +from speechmatics.rt import Model from speechmatics.rt import OperatingPoint from speechmatics.rt import SpeakerIdentifier @@ -487,9 +488,7 @@ class VoiceAgentConfig(BaseModel): agent configuration for the `VoiceAgentClient`. Parameters: - operating_point: Operating point for transcription accuracy vs. latency tradeoff. It is - recommended to use `OperatingPoint.ENHANCED` for most use cases. Defaults to - `OperatingPoint.ENHANCED`. + model: Transcription model to use. Defaults to `Model.ENHANCED`. domain: Domain for Speechmatics API. Defaults to `None`. @@ -651,7 +650,7 @@ class VoiceAgentConfig(BaseModel): Complete example with multiple features: >>> config = VoiceAgentConfig( ... language="en", - ... operating_point=OperatingPoint.ENHANCED, + ... model=Model.ENHANCED, ... enable_diarization=True, ... speaker_sensitivity=0.7, ... max_speakers=3, @@ -670,7 +669,7 @@ class VoiceAgentConfig(BaseModel): """ # Service configuration - operating_point: OperatingPoint = OperatingPoint.ENHANCED + model: Model = Model.ENHANCED domain: Optional[str] = None language: str = "en" output_locale: Optional[str] = None @@ -711,6 +710,9 @@ class VoiceAgentConfig(BaseModel): audio_encoding: AudioEncoding = AudioEncoding.PCM_S16LE chunk_size: int = 160 + # Deprecated + operating_point: Optional[OperatingPoint] = None + # Validation @model_validator(mode="after") # type: ignore[misc] def validate_config(self) -> Self: @@ -751,6 +753,12 @@ def validate_config(self) -> Self: if self.sample_rate not in [8000, 16000]: errors.append("sample_rate must be 8000 or 16000") + # Deprecated `operating_point` - move to new `model` + if self.operating_point: + self.model = Model(self.operating_point.value) + self.operating_point = None + errors.append("migrated operating_point to model") + # Raise error if any validation errors if errors: raise ValueError(f"{len(errors)} config error(s): {'; '.join(errors)}") diff --git a/sdk/voice/speechmatics/voice/_presets.py b/sdk/voice/speechmatics/voice/_presets.py index 2bcb092f..013f727c 100644 --- a/sdk/voice/speechmatics/voice/_presets.py +++ b/sdk/voice/speechmatics/voice/_presets.py @@ -8,7 +8,7 @@ from ._models import EndOfTurnConfig from ._models import EndOfUtteranceMode -from ._models import OperatingPoint +from ._models import Model from ._models import SmartTurnConfig from ._models import SpeechSegmentConfig from ._models import VoiceActivityConfig @@ -26,12 +26,11 @@ def FAST(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # noq delay to finalizing the spoken sentences. It is not recommended for conversation, as it will not account for pauses, slow speech or disfluencies. - Note that this uses our standard operating point so will have marginally lower - accuracy that the enhanced operating point. + Note that this uses our standard model. """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - operating_point=OperatingPoint.STANDARD, + model=Model.STANDARD, enable_diarization=True, max_delay=2.0, end_of_utterance_silence_trigger=0.25, @@ -51,7 +50,7 @@ def FIXED(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # no """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - operating_point=OperatingPoint.ENHANCED, + model=Model.ENHANCED, enable_diarization=True, max_delay=2.0, end_of_utterance_silence_trigger=0.5, @@ -75,7 +74,7 @@ def ADAPTIVE(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - operating_point=OperatingPoint.ENHANCED, + model=Model.ENHANCED, enable_diarization=True, max_delay=2.0, end_of_utterance_silence_trigger=0.7, @@ -104,7 +103,7 @@ def SMART_TURN(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - operating_point=OperatingPoint.ENHANCED, + model=Model.ENHANCED, enable_diarization=True, max_delay=2.0, end_of_utterance_silence_trigger=0.8, @@ -131,7 +130,7 @@ def SCRIBE(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # n """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - operating_point=OperatingPoint.ENHANCED, + model=Model.ENHANCED, enable_diarization=True, max_delay=2.0, end_of_utterance_silence_trigger=1.0, @@ -150,7 +149,7 @@ def CAPTIONS(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - operating_point=OperatingPoint.ENHANCED, + model=Model.ENHANCED, enable_diarization=True, max_delay=0.7, end_of_utterance_silence_trigger=0.5, @@ -170,7 +169,7 @@ def EXTERNAL(overlay: Optional[VoiceAgentConfig] = None) -> VoiceAgentConfig: # """ return VoiceAgentConfigPreset._merge_configs( VoiceAgentConfig( - operating_point=OperatingPoint.ENHANCED, + model=Model.ENHANCED, enable_diarization=True, max_delay=2.0, end_of_utterance_mode=EndOfUtteranceMode.EXTERNAL, diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 04c698eb..6504fddf 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -7,7 +7,7 @@ from speechmatics.voice._models import AgentServerMessageType from speechmatics.voice._models import AnnotationFlags from speechmatics.voice._models import AnnotationResult -from speechmatics.voice._models import OperatingPoint +from speechmatics.voice._models import Model from speechmatics.voice._models import SessionMetricsMessage from speechmatics.voice._models import SpeakerFocusConfig from speechmatics.voice._models import SpeakerFocusMode @@ -55,8 +55,8 @@ async def test_voice_agent_config(): assert config_from_json.known_speakers[0].label == "John" # From JSON - preset: VoiceAgentConfig = VoiceAgentConfig.from_json('{"operating_point": "enhanced"}') - assert preset.operating_point == OperatingPoint.ENHANCED + preset: VoiceAgentConfig = VoiceAgentConfig.from_json('{"model": "enhanced"}') + assert preset.model == Model.ENHANCED @pytest.mark.asyncio diff --git a/tests/voice/test_14_presets.py b/tests/voice/test_14_presets.py index a5cc898f..e397d473 100644 --- a/tests/voice/test_14_presets.py +++ b/tests/voice/test_14_presets.py @@ -1,7 +1,7 @@ import pytest from speechmatics.voice import VoiceAgentConfig -from speechmatics.voice._models import OperatingPoint +from speechmatics.voice._models import Model from speechmatics.voice._models import SpeechSegmentConfig from speechmatics.voice._presets import VoiceAgentConfigPreset @@ -42,10 +42,10 @@ async def test_presets(): async def test_json_presets(): """Test VoiceAgentConfigPreset JSON presets.""" - # With a JSON string overlay + # With a JSON string overlay (using deprecated `operating_point`, internally changed to `model`) preset: VoiceAgentConfig = VoiceAgentConfigPreset.load("fast", '{"operating_point": "enhanced"}') assert preset is not None - assert preset.operating_point == OperatingPoint.ENHANCED + assert preset.model == Model.ENHANCED # Check using incorrect preset name with pytest.raises(ValueError): From 42286da0c3f2dcbe431516af551a055eb7518fa0 Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 22 Jul 2026 12:27:35 +0100 Subject: [PATCH 2/3] Fix operating_point deprecation raising validation error The migration path appended "migrated operating_point to model" to the validation errors list, which caused every config using the deprecated `operating_point` to raise a ValueError. Emit a DeprecationWarning and migrate silently instead. Co-Authored-By: Claude Opus 4.8 (1M context) --- sdk/voice/speechmatics/voice/_models.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 89183775..6d4996d4 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -5,6 +5,7 @@ from __future__ import annotations import datetime +import warnings from enum import Enum from typing import Any from typing import Literal @@ -753,11 +754,15 @@ def validate_config(self) -> Self: if self.sample_rate not in [8000, 16000]: errors.append("sample_rate must be 8000 or 16000") - # Deprecated `operating_point` - move to new `model` + # Deprecated `operating_point` - migrate to new `model` if self.operating_point: + warnings.warn( + "`operating_point` is deprecated, use `model` instead", + DeprecationWarning, + stacklevel=2, + ) self.model = Model(self.operating_point.value) self.operating_point = None - errors.append("migrated operating_point to model") # Raise error if any validation errors if errors: From dd46a9db2b312378fe0a7c45123022dbf6c6534a Mon Sep 17 00:00:00 2001 From: Sam Sykes Date: Wed, 22 Jul 2026 12:38:24 +0100 Subject: [PATCH 3/3] Align operating_point deprecation with RT SDK Match the RT SDK's handling of the deprecated `operating_point` field: raise a ValueError when both `model` and `operating_point` are set explicitly, and use the `from warnings import warn` style and message wording. Add a test covering migration, no-op and conflict cases. Co-Authored-By: Claude Opus 4.8 (1M context) --- sdk/voice/speechmatics/voice/_models.py | 10 ++++++---- tests/voice/test_04_models.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/sdk/voice/speechmatics/voice/_models.py b/sdk/voice/speechmatics/voice/_models.py index 6d4996d4..698d5dff 100644 --- a/sdk/voice/speechmatics/voice/_models.py +++ b/sdk/voice/speechmatics/voice/_models.py @@ -5,11 +5,11 @@ from __future__ import annotations import datetime -import warnings from enum import Enum from typing import Any from typing import Literal from typing import Optional +from warnings import warn from pydantic import BaseModel as PydanticBaseModel from pydantic import ConfigDict @@ -755,9 +755,11 @@ def validate_config(self) -> Self: errors.append("sample_rate must be 8000 or 16000") # Deprecated `operating_point` - migrate to new `model` - if self.operating_point: - warnings.warn( - "`operating_point` is deprecated, use `model` instead", + if self.operating_point is not None: + if "model" in self.model_fields_set: + raise ValueError("Cannot specify both 'model' and 'operating_point'. Use 'model' instead.") + warn( + "'operating_point' is deprecated, use 'model' instead.", DeprecationWarning, stacklevel=2, ) diff --git a/tests/voice/test_04_models.py b/tests/voice/test_04_models.py index 6504fddf..e8e8fa2f 100644 --- a/tests/voice/test_04_models.py +++ b/tests/voice/test_04_models.py @@ -8,6 +8,7 @@ from speechmatics.voice._models import AnnotationFlags from speechmatics.voice._models import AnnotationResult from speechmatics.voice._models import Model +from speechmatics.voice._models import OperatingPoint from speechmatics.voice._models import SessionMetricsMessage from speechmatics.voice._models import SpeakerFocusConfig from speechmatics.voice._models import SpeakerFocusMode @@ -59,6 +60,25 @@ async def test_voice_agent_config(): assert preset.model == Model.ENHANCED +@pytest.mark.asyncio +async def test_operating_point_deprecation(): + """Test that the deprecated `operating_point` field migrates to `model`.""" + + # `operating_point` only -> migrates to `model` with a DeprecationWarning + with pytest.warns(DeprecationWarning, match="operating_point"): + config = VoiceAgentConfig(operating_point=OperatingPoint.STANDARD) + assert config.model == Model.STANDARD + assert config.operating_point is None + + # `model` only -> used as-is, no warning + config = VoiceAgentConfig(model=Model.STANDARD) + assert config.model == Model.STANDARD + + # Both set -> error + with pytest.raises(ValueError, match="Cannot specify both 'model' and 'operating_point'"): + VoiceAgentConfig(model=Model.ENHANCED, operating_point=OperatingPoint.STANDARD) + + @pytest.mark.asyncio async def test_annotation_result(): """Test AnnotationResult.