From a065088b4c45e2ca7e1bb91b1a899b8444800121 Mon Sep 17 00:00:00 2001 From: digitallysavvy Date: Wed, 19 Aug 2026 18:54:52 -0400 Subject: [PATCH 1/3] fix(vendors): send Speechmatics STT key instead of api_key # Conflicts: # changelog.md # src/agora_agent/agentkit/vendors/stt.py --- changelog.md | 7 ++++ docs/concepts/vendors.md | 2 +- docs/reference/vendors.md | 5 ++- src/agora_agent/agentkit/vendors/stt.py | 27 +++++++++++++-- .../types/speechmatics_asr_params.py | 33 ++++++++++++++++++- tests/custom/test_request_body.py | 21 ++++++++++-- tests/custom/test_stt_language.py | 2 +- 7 files changed, 89 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 3c0600f..61f8010 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/). +## [Unreleased] + +### Fixed + +- **Speechmatics credential field** — `SpeechmaticsSTT` now emits the REST-compatible `asr.params.key`. The `key` field is preferred; deprecated `api_key` remains supported, warns, and is normalized to `key`. + ## [v2.6.0] — 2026-08-10 ### Added @@ -20,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). - **AssemblyAI STT WebSocket URL** — `AssemblyAISTT.uri` and `AssemblyAiAsrParams.uri` are renamed to `ws_url`, and the field is serialized as `asr.params.ws_url`. This is a breaking rename for callers that set `uri`. - **Generated model aliasing** — Wire-key aliases (`VoiceSelectionParams`, `AudioConfig`, `voiceId`, `modelId`, `appId`, `sceneList`) now use native pydantic field aliases with population by field name, instead of annotation-metadata conversion on every request and response. + ## [v2.4.0] — 2026-06-30 ### Added diff --git a/docs/concepts/vendors.md b/docs/concepts/vendors.md index 34e2970..7f67823 100644 --- a/docs/concepts/vendors.md +++ b/docs/concepts/vendors.md @@ -114,7 +114,7 @@ Use `turn_detection.language` for Agora interaction language; it defaults to `en | Class | Provider | Required Parameters | |---|---|---| -| `SpeechmaticsSTT` | Speechmatics | `api_key`, `language` | +| `SpeechmaticsSTT` | Speechmatics | `key`, `language`; deprecated `api_key` remains supported | | `DeepgramSTT` | Deepgram | `model` for Agora-managed `nova-2`/`nova-3`; `api_key` for BYOK; `language?`, `keyterm?` | | `MicrosoftSTT` | Microsoft Azure | `key`, `region`, `language` | | `OpenAISTT` | OpenAI | `api_key` | diff --git a/docs/reference/vendors.md b/docs/reference/vendors.md index 073c0b6..2110d78 100644 --- a/docs/reference/vendors.md +++ b/docs/reference/vendors.md @@ -488,11 +488,14 @@ Use `turn_detection.language` for Agora interaction language; it defaults to `en | Parameter | Type | Required | Default | Description | |---|---|---|---|---| -| `api_key` | `str` | Yes | — | Speechmatics API key | +| `key` | `str` | Yes | `None` | Speechmatics API key | +| `api_key` | `str` | No | `None` | Deprecated alias for `key`; retained for backward compatibility | | `language` | `str` | Yes | — | Language code (e.g., `en`) | | `uri` | `str` | No | `None` | Speechmatics streaming WebSocket URL | | `additional_params` | `Dict[str, Any]` | No | `None` | Additional parameters | +`SpeechmaticsSTT` always serializes its credential as `asr.params.key`. Passing `api_key` emits a `DeprecationWarning` and is normalized to `key`; when both are provided, `key` takes precedence. + ### `DeepgramSTT` | Parameter | Type | Required | Default | Description | diff --git a/src/agora_agent/agentkit/vendors/stt.py b/src/agora_agent/agentkit/vendors/stt.py index 2dfdd56..2de7384 100644 --- a/src/agora_agent/agentkit/vendors/stt.py +++ b/src/agora_agent/agentkit/vendors/stt.py @@ -1,3 +1,4 @@ +import warnings from typing import Any, Dict, List, Optional from .base import BaseSTT @@ -9,18 +10,40 @@ class SpeechmaticsSTTOptions(BaseModel): model_config = ConfigDict(extra="forbid") - api_key: str = Field(..., description="Speechmatics API key") + key: Optional[str] = Field(default=None, description="Speechmatics API key") + api_key: Optional[str] = Field( + default=None, + description="Deprecated alias for key; normalized to the REST API key field", + deprecated="Use key instead.", + ) language: str = Field(..., description="Language code (e.g., en, es, fr)") model: Optional[str] = Field(default=None, description="Model name") uri: Optional[str] = Field(default=None, description="Speechmatics streaming WebSocket URL") additional_params: Optional[Dict[str, Any]] = Field(default=None) + @model_validator(mode="before") + @classmethod + def _warn_deprecated_api_key(cls, values: Any) -> Any: + if isinstance(values, dict) and "api_key" in values: + warnings.warn( + "SpeechmaticsSTT.api_key is deprecated; use key instead.", + DeprecationWarning, + stacklevel=2, + ) + return values + + @model_validator(mode="after") + def _validate_key(self) -> "SpeechmaticsSTT": + if self.key is None and self.__dict__.get("api_key") is None: + raise ValueError("SpeechmaticsSTT requires key") + return self class SpeechmaticsSTT(SpeechmaticsSTTOptions, BaseSTT): def to_config(self) -> Dict[str, Any]: params: Dict[str, Any] = dict(self.additional_params or {}) + params.pop("api_key", None) params.update({ - "api_key": self.api_key, + "key": self.key if self.key is not None else self.__dict__.get("api_key"), "language": self.language, }) if self.model is not None: diff --git a/src/agora_agent/types/speechmatics_asr_params.py b/src/agora_agent/types/speechmatics_asr_params.py index 4709d22..f2edacf 100644 --- a/src/agora_agent/types/speechmatics_asr_params.py +++ b/src/agora_agent/types/speechmatics_asr_params.py @@ -12,11 +12,19 @@ class SpeechmaticsAsrParams(UncheckedBaseModel): Speechmatics ASR configuration parameters. """ - api_key: str = pydantic.Field() + key: str = pydantic.Field() """ Speechmatics API key """ + api_key: typing.Optional[str] = pydantic.Field( + default=None, + deprecated="Use key instead.", + ) + """ + Deprecated alias for key. The SDK normalizes it to key during validation. + """ + language: str = pydantic.Field() """ Language code to use for transcription @@ -27,6 +35,29 @@ class SpeechmaticsAsrParams(UncheckedBaseModel): WebSocket URL for the Speechmatics streaming API """ + if IS_PYDANTIC_V2: + + @pydantic.model_validator(mode="before") + @classmethod + def _normalize_api_key(cls, values: typing.Any) -> typing.Any: + if not isinstance(values, typing.Mapping): + return values + normalized = dict(values) + legacy_key = normalized.pop("api_key", None) + if legacy_key is not None: + normalized.setdefault("key", legacy_key) + return normalized + + else: + + @pydantic.root_validator(pre=True) + def _normalize_api_key(cls, values: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]: + normalized = dict(values) + legacy_key = normalized.pop("api_key", None) + if legacy_key is not None: + normalized.setdefault("key", legacy_key) + return normalized + if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/tests/custom/test_request_body.py b/tests/custom/test_request_body.py index f71fd5d..879eb94 100644 --- a/tests/custom/test_request_body.py +++ b/tests/custom/test_request_body.py @@ -65,6 +65,7 @@ from agora_agent.agentkit import AgentSession from agora_agent.agentkit.presets import resolve_session_presets from agora_agent.cn import QwenOmni +from agora_agent.types.speechmatics_asr_params import SpeechmaticsAsrParams from test_helpers import test_client @@ -836,13 +837,29 @@ def test_byok_ares_stt_no_params() -> None: def test_byok_speechmatics_stt_params() -> None: - agent = Agent(test_client()).with_stt(SpeechmaticsSTT(api_key="sm-key", language="en")) + with pytest.warns(DeprecationWarning, match="use key instead"): + agent = Agent(test_client()).with_stt(SpeechmaticsSTT(api_key="sm-key", language="en")) props = build_properties(agent, allow_missing={"llm", "tts"}) assert props["asr"]["vendor"] == "speechmatics" - assert props["asr"]["params"]["api_key"] == "sm-key" + assert props["asr"]["params"]["key"] == "sm-key" + assert "api_key" not in props["asr"]["params"] assert props["asr"]["params"]["language"] == "en" +def test_byok_speechmatics_stt_key_takes_precedence() -> None: + assert SpeechmaticsSTT(key="new-key", language="en").to_config()["params"]["key"] == "new-key" + + with pytest.warns(DeprecationWarning, match="use key instead"): + config = SpeechmaticsSTT(key="new-key", api_key="legacy-key", language="en").to_config() + assert config["params"]["key"] == "new-key" + assert "api_key" not in config["params"] + + +def test_generated_speechmatics_params_normalizes_deprecated_api_key() -> None: + params = SpeechmaticsAsrParams(api_key="legacy-key", language="en") + assert dump(params) == {"key": "legacy-key", "language": "en"} + + def test_byok_sarvam_stt_params() -> None: agent = Agent(test_client()).with_stt(SarvamSTT(api_key="sarvam-key", language="en-IN")) props = build_properties(agent, allow_missing={"llm", "tts"}) diff --git a/tests/custom/test_stt_language.py b/tests/custom/test_stt_language.py index 15dfd9d..27ce63f 100644 --- a/tests/custom/test_stt_language.py +++ b/tests/custom/test_stt_language.py @@ -178,7 +178,7 @@ def test_stt_vendor_params_match_documented_shapes() -> None: } assert SpeechmaticsSTT(api_key="sm-key", language="en").to_config()["params"] == { - "api_key": "sm-key", + "key": "sm-key", "language": "en", } From 39a2f896d40966bbdfe33a2465286654386c93d8 Mon Sep 17 00:00:00 2001 From: digitallysavvy Date: Wed, 19 Aug 2026 19:24:18 -0400 Subject: [PATCH 2/3] docs(changelog): mark Speechmatics key fix as v2.6.1 patch release --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 61f8010..a0c599e 100644 --- a/changelog.md +++ b/changelog.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/). -## [Unreleased] +## [v2.6.1] — 2026-08-19 ### Fixed From 6aed08ddc29c0b7d2ed346e904adc20f257c4404 Mon Sep 17 00:00:00 2001 From: digitallysavvy Date: Wed, 19 Aug 2026 19:35:40 -0400 Subject: [PATCH 3/3] fix python CI errors --- src/agora_agent/agentkit/vendors/stt.py | 2 +- src/agora_agent/types/speechmatics_asr_params.py | 6 +++++- tests/custom/test_request_body.py | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/agora_agent/agentkit/vendors/stt.py b/src/agora_agent/agentkit/vendors/stt.py index 2de7384..cd76caf 100644 --- a/src/agora_agent/agentkit/vendors/stt.py +++ b/src/agora_agent/agentkit/vendors/stt.py @@ -33,7 +33,7 @@ def _warn_deprecated_api_key(cls, values: Any) -> Any: return values @model_validator(mode="after") - def _validate_key(self) -> "SpeechmaticsSTT": + def _validate_key(self) -> "SpeechmaticsSTTOptions": if self.key is None and self.__dict__.get("api_key") is None: raise ValueError("SpeechmaticsSTT requires key") return self diff --git a/src/agora_agent/types/speechmatics_asr_params.py b/src/agora_agent/types/speechmatics_asr_params.py index f2edacf..c1b7232 100644 --- a/src/agora_agent/types/speechmatics_asr_params.py +++ b/src/agora_agent/types/speechmatics_asr_params.py @@ -12,7 +12,7 @@ class SpeechmaticsAsrParams(UncheckedBaseModel): Speechmatics ASR configuration parameters. """ - key: str = pydantic.Field() + key: typing.Optional[str] = pydantic.Field(default=None) """ Speechmatics API key """ @@ -46,6 +46,8 @@ def _normalize_api_key(cls, values: typing.Any) -> typing.Any: legacy_key = normalized.pop("api_key", None) if legacy_key is not None: normalized.setdefault("key", legacy_key) + if normalized.get("key") is None: + raise ValueError("SpeechmaticsAsrParams requires key") return normalized else: @@ -56,6 +58,8 @@ def _normalize_api_key(cls, values: typing.Dict[str, typing.Any]) -> typing.Dict legacy_key = normalized.pop("api_key", None) if legacy_key is not None: normalized.setdefault("key", legacy_key) + if normalized.get("key") is None: + raise ValueError("SpeechmaticsAsrParams requires key") return normalized if IS_PYDANTIC_V2: diff --git a/tests/custom/test_request_body.py b/tests/custom/test_request_body.py index 879eb94..d33e541 100644 --- a/tests/custom/test_request_body.py +++ b/tests/custom/test_request_body.py @@ -860,6 +860,11 @@ def test_generated_speechmatics_params_normalizes_deprecated_api_key() -> None: assert dump(params) == {"key": "legacy-key", "language": "en"} +def test_generated_speechmatics_params_requires_a_key() -> None: + with pytest.raises(ValueError, match="requires key"): + SpeechmaticsAsrParams(language="en") + + def test_byok_sarvam_stt_params() -> None: agent = Agent(test_client()).with_stt(SarvamSTT(api_key="sarvam-key", language="en-IN")) props = build_properties(agent, allow_missing={"llm", "tts"})