diff --git a/.fern/metadata.json b/.fern/metadata.json index 62289a3..764f419 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -14,5 +14,5 @@ }, "exclude_types_from_init_exports": true }, - "originGitCommit": "26314a9d3199f5e135302c3c3645b6bbb8f556d4" + "originGitCommit": "9b9b2fa1161d85c0e7da5c783a8bc4e2b4dae7c0" } \ No newline at end of file diff --git a/.fern/replay.lock b/.fern/replay.lock index 699a092..7de023c 100644 --- a/.fern/replay.lock +++ b/.fern/replay.lock @@ -72,14 +72,20 @@ generations: cli_version: unknown generator_versions: fernapi/fern-python-sdk: 4.37.0 -current_generation: f7e9b5d993665a16b43eb1d7d442b118fbeffa4b + - commit_sha: c947c8e18fb5d4ba1e156caf3703f84d630c033c + tree_hash: fef739c337b8fcbcfe43233813e255c51551bad6 + timestamp: 2026-09-22T07:54:15.761Z + cli_version: unknown + generator_versions: + fernapi/fern-python-sdk: 4.37.0 +current_generation: c947c8e18fb5d4ba1e156caf3703f84d630c033c patches: - id: patch-13d8e068 content_hash: sha256:97d879ab169016b9abaf1866427a8ebc45af608f69e063a07e5126c9613c329b original_commit: 13d8e0683ffa08bcfc9a381eb9604adf5abdbe0b original_message: "chore: ignore local venv and planning docs" original_author: plutoless - base_generation: f7e9b5d993665a16b43eb1d7d442b118fbeffa4b + base_generation: c947c8e18fb5d4ba1e156caf3703f84d630c033c files: - .gitignore patch_content: | @@ -108,7 +114,7 @@ patches: original_commit: a065088b4c45e2ca7e1bb91b1a899b8444800121 original_message: "fix(vendors): send Speechmatics STT key instead of api_key" original_author: digitallysavvy - base_generation: f7e9b5d993665a16b43eb1d7d442b118fbeffa4b + base_generation: c947c8e18fb5d4ba1e156caf3703f84d630c033c files: - src/agora_agent/types/speechmatics_asr_params.py patch_content: | @@ -251,7 +257,7 @@ patches: original_commit: 33a436b3bdb6f2a6fe4d8987b6536c2f0ac2862e original_message: "fix: address v2.8.0 release review findings" original_author: digitallysavvy - base_generation: f7e9b5d993665a16b43eb1d7d442b118fbeffa4b + base_generation: c947c8e18fb5d4ba1e156caf3703f84d630c033c files: - src/agora_agent/agents/types/start_agents_request_properties_advanced_features.py patch_content: | @@ -312,3 +318,4 @@ patches: frozen = True smart_union = True extra = pydantic.Extra.allow + user_owned: true diff --git a/changelog.md b/changelog.md index acb87ae..d67033d 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/). +## [Unreleased] + +### Added + +- **RTZR global STT** — Added the global-only `RtzrSTT` AgentKit vendor with typed credentials, recognition options, vendor catalog registration, and generated request serialization. +- **Speak parameters** — Exposed the generated `SpeakConfig`/`ParametersSpeak` types through `SessionParams` so `parameters.speak.batch` can be configured with `Agent.with_parameters()`. + +### Changed + +- **Sarvam TTS v2.14 parameters** — Added `speech_sample_rate`, `enable_preprocessing`, `model`, and flattened `additional_params` support. The legacy `sample_rate` input remains supported as a deprecated alias. +- **Generated SDK alignment** — Updated AgentKit exports, global vendor routing, request validation, avatar sample-rate handling, documentation, and feature-focused regression tests for the latest `engine-v2.14` generated API. + ## [v2.10.0] — 2026-09-18 ### Added diff --git a/docs/concepts/vendors.md b/docs/concepts/vendors.md index 13f6cd5..53c8fe5 100644 --- a/docs/concepts/vendors.md +++ b/docs/concepts/vendors.md @@ -125,6 +125,7 @@ top-level `asr.keywords`. Both vendors also accept `additional_params`, serializ | `AssemblyAISTT` | AssemblyAI | `api_key`, `language` | | `AresSTT` | Ares | — (all optional) | | `SarvamSTT` | Sarvam | `api_key`, `language` | +| `RtzrSTT` | RTZR | `client_id`, `client_secret` | | `XaiSTT` | xAI | `api_key` | ### CN STT Vendors diff --git a/docs/guides/regional-routing.md b/docs/guides/regional-routing.md index ff6e4cc..b045409 100644 --- a/docs/guides/regional-routing.md +++ b/docs/guides/regional-routing.md @@ -40,7 +40,7 @@ If you omit `with_stt()`, AgentKit uses `FengmingSTT` by default for `Area.CN` c | Client area | STT classes | LLM classes | MLLM classes | TTS classes | Avatar classes | |---|---|---|---|---|---| -| `Area.US`, `Area.EU`, `Area.AP` | `DeepgramSTT`, `SpeechmaticsSTT`, `MicrosoftSTT`, `OpenAISTT`, `GeminiSTT`, `GoogleSTT`, `AmazonSTT`, `AssemblyAISTT`, `AresSTT`, `SarvamSTT`, `XaiSTT` | `OpenAI`, `AzureOpenAI`, `Anthropic`, `Gemini`, `Groq`, `VertexAILLM`, `AmazonBedrock`, `Dify`, `CustomLLM` | `OpenAIRealtime`, `AzureOpenAIRealtime`, `GeminiLive`, `VertexAI`, `XaiGrok` | `ElevenLabsTTS`, `MicrosoftTTS`, `OpenAITTS`, `CartesiaTTS`, `GoogleTTS`, `AmazonTTS`, `DeepgramTTS`, `GradiumTTS`, `MistralTTS`, `TypecastTTS`, `HumeAITTS`, `RimeTTS`, `FishAudioTTS`, `MiniMaxTTS`, `MurfTTS`, `SarvamTTS`, `GenericTTS`, `XaiTTS` | `LiveAvatarAvatar`, `HeyGenAvatar`, `AkoolAvatar`, `AnamAvatar`, `GenericAvatar` | +| `Area.US`, `Area.EU`, `Area.AP` | `DeepgramSTT`, `SpeechmaticsSTT`, `MicrosoftSTT`, `OpenAISTT`, `GeminiSTT`, `GoogleSTT`, `AmazonSTT`, `AssemblyAISTT`, `AresSTT`, `SarvamSTT`, `RtzrSTT`, `XaiSTT` | `OpenAI`, `AzureOpenAI`, `Anthropic`, `Gemini`, `Groq`, `VertexAILLM`, `AmazonBedrock`, `Dify`, `CustomLLM` | `OpenAIRealtime`, `AzureOpenAIRealtime`, `GeminiLive`, `VertexAI`, `XaiGrok` | `ElevenLabsTTS`, `MicrosoftTTS`, `OpenAITTS`, `CartesiaTTS`, `GoogleTTS`, `AmazonTTS`, `DeepgramTTS`, `GradiumTTS`, `MistralTTS`, `TypecastTTS`, `HumeAITTS`, `RimeTTS`, `FishAudioTTS`, `MiniMaxTTS`, `MurfTTS`, `SarvamTTS`, `GenericTTS`, `XaiTTS` | `LiveAvatarAvatar`, `HeyGenAvatar`, `AkoolAvatar`, `AnamAvatar`, `GenericAvatar` | | `Area.CN` | `FengmingSTT`, `TencentSTT`, `MicrosoftCNSTT`, `XfyunSTT`, `XfyunBigModelSTT`, `XfyunDialectSTT` | `AliyunLLM`, `BytedanceLLM`, `DeepSeekLLM`, `TencentLLM` | `QwenOmni` | `MiniMaxCNTTS`, `TencentTTS`, `BytedanceTTS`, `MicrosoftCNTTS`, `CosyVoiceTTS`, `BytedanceDuplexTTS`, `StepFunTTS`, `GenericTTS` | `SenseTimeAvatar`, `SpatiusAvatar` | Global client example: diff --git a/docs/reference/agent.md b/docs/reference/agent.md index 68e46f1..f4e8aed 100644 --- a/docs/reference/agent.md +++ b/docs/reference/agent.md @@ -178,6 +178,7 @@ Enable or disable MCP and inline REST tool invocation by setting `advanced_featu ### `with_parameters(parameters: SessionParams) -> Agent` Set session parameters (silence config, farewell config, data channel, audio scenario, etc.). +Use `SpeakConfig(batch=False)` in the `speak` field to control sentence segmentation for speak requests. ### `with_audio_scenario(audio_scenario: ParametersAudioScenario) -> Agent` diff --git a/docs/reference/vendors.md b/docs/reference/vendors.md index 959690b..1835394 100644 --- a/docs/reference/vendors.md +++ b/docs/reference/vendors.md @@ -20,7 +20,7 @@ Construct vendors directly from `agora_agent`, then bind a client with `Agent(cl | Area | STT classes | LLM classes | MLLM classes | TTS classes | Avatar classes | |---|---|---|---|---|---| -| `Area.US`, `Area.EU`, `Area.AP` | `DeepgramSTT`, `SpeechmaticsSTT`, `MicrosoftSTT`, `OpenAISTT`, `GeminiSTT`, `GoogleSTT`, `AmazonSTT`, `AssemblyAISTT`, `AresSTT`, `SarvamSTT`, `XaiSTT` | `OpenAI`, `AzureOpenAI`, `Anthropic`, `Gemini`, `Groq`, `VertexAILLM`, `AmazonBedrock`, `Dify`, `CustomLLM` | `OpenAIRealtime`, `AzureOpenAIRealtime`, `GeminiLive`, `VertexAI`, `XaiGrok` | `ElevenLabsTTS`, `MicrosoftTTS`, `OpenAITTS`, `CartesiaTTS`, `GoogleTTS`, `AmazonTTS`, `DeepgramTTS`, `GradiumTTS`, `MistralTTS`, `TypecastTTS`, `HumeAITTS`, `RimeTTS`, `FishAudioTTS`, `MiniMaxTTS`, `MurfTTS`, `SarvamTTS`, `GenericTTS`, `XaiTTS` | `LiveAvatarAvatar`, `HeyGenAvatar`, `AkoolAvatar`, `AnamAvatar`, `GenericAvatar` | +| `Area.US`, `Area.EU`, `Area.AP` | `DeepgramSTT`, `SpeechmaticsSTT`, `MicrosoftSTT`, `OpenAISTT`, `GeminiSTT`, `GoogleSTT`, `AmazonSTT`, `AssemblyAISTT`, `AresSTT`, `SarvamSTT`, `RtzrSTT`, `XaiSTT` | `OpenAI`, `AzureOpenAI`, `Anthropic`, `Gemini`, `Groq`, `VertexAILLM`, `AmazonBedrock`, `Dify`, `CustomLLM` | `OpenAIRealtime`, `AzureOpenAIRealtime`, `GeminiLive`, `VertexAI`, `XaiGrok` | `ElevenLabsTTS`, `MicrosoftTTS`, `OpenAITTS`, `CartesiaTTS`, `GoogleTTS`, `AmazonTTS`, `DeepgramTTS`, `GradiumTTS`, `MistralTTS`, `TypecastTTS`, `HumeAITTS`, `RimeTTS`, `FishAudioTTS`, `MiniMaxTTS`, `MurfTTS`, `SarvamTTS`, `GenericTTS`, `XaiTTS` | `LiveAvatarAvatar`, `HeyGenAvatar`, `AkoolAvatar`, `AnamAvatar`, `GenericAvatar` | | `Area.CN` | `FengmingSTT`, `TencentSTT`, `MicrosoftCNSTT`, `XfyunSTT`, `XfyunBigModelSTT`, `XfyunDialectSTT` | `AliyunLLM`, `BytedanceLLM`, `DeepSeekLLM`, `TencentLLM` | `QwenOmni` | `MiniMaxCNTTS`, `TencentTTS`, `BytedanceTTS`, `MicrosoftCNTTS`, `CosyVoiceTTS`, `BytedanceDuplexTTS`, `StepFunTTS`, `GenericTTS` | `SenseTimeAvatar`, `SpatiusAvatar` | Global example: @@ -446,7 +446,11 @@ AgentKit serializes `credential_mode` at the top level of the Rime TTS configura | `pitch` | `float` | No | `None` | Pitch adjustment | | `pace` | `float` | No | `None` | Speed of speech | | `loudness` | `float` | No | `None` | Volume level | -| `sample_rate` | `int` | No | `None` | Audio sample rate | +| `speech_sample_rate` | `int` | No | `None` | Output speech sample rate in Hz | +| `enable_preprocessing` | `bool` | No | `None` | Normalize English words and numeric entities before synthesis | +| `model` | `str` | No | `None` | Sarvam TTS model; defaults to `bulbul:v3` | +| `sample_rate` | `int` | No | `None` | Deprecated alias for `speech_sample_rate` | +| `additional_params` | `Dict[str, Any]` | No | `None` | Additional Sarvam TTS parameters, flattened into `params` | | `skip_patterns` | `List[int]` | No | `None` | Skip patterns | ### `SmallestAITTS` @@ -636,6 +640,26 @@ stt = GeminiSTT( | `language` | `str` | No | `None` | Language code for speech recognition | | `additional_params` | `Dict[str, Any]` | No | `None` | Additional xAI STT parameters | +### `RtzrSTT` + +Global-only RTZR speech-to-text provider (`asr.vendor: "rtzr"`). + +| Parameter | Type | Required | Default | Description | +|---|---|---|---|---| +| `client_id` | `str` | Yes | — | RTZR client ID | +| `client_secret` | `str` | Yes | — | RTZR client secret | +| `api_base` | `str` | No | `None` | RTZR API base URL | +| `model_name` | `str` | No | `None` | RTZR recognition model name | +| `language` | `str` | No | `None` | RTZR recognition language code | +| `sample_rate` | `int` | No | `None` | Input audio sample rate in Hz | +| `encoding` | `str` | No | `None` | Input audio encoding | +| `use_itn` | `bool` | No | `None` | Enable inverse text normalization | +| `use_disfluency_filter` | `bool` | No | `None` | Filter disfluencies | +| `use_profanity_filter` | `bool` | No | `None` | Filter profanity | +| `use_punctuation` | `bool` | No | `None` | Add punctuation to recognized text | +| `keywords` | `List[str]` | No | `None` | Keywords that improve recognition accuracy | +| `additional_params` | `Dict[str, Any]` | No | `None` | Additional RTZR parameters | + ### `SmallestAISTT` Global-only Smallest AI speech-to-text provider (`asr.vendor: "smallestai"`). diff --git a/src/agora_agent/__init__.py b/src/agora_agent/__init__.py index 9c97381..84bc4a8 100644 --- a/src/agora_agent/__init__.py +++ b/src/agora_agent/__init__.py @@ -67,6 +67,9 @@ OpenAIRealtime, OpenAIGPTLive, OpenAISTT, + RtzrSTT, + SpeakConfig, + ParametersSpeak, OpenAITTS, RimeTTS, SarvamSTT, diff --git a/src/agora_agent/agentkit/__init__.py b/src/agora_agent/agentkit/__init__.py index c45c514..f7cd8b5 100644 --- a/src/agora_agent/agentkit/__init__.py +++ b/src/agora_agent/agentkit/__init__.py @@ -55,6 +55,8 @@ FarewellConfig, ParametersDataChannel, ParametersAudioScenario, + SpeakConfig, + ParametersSpeak, InterruptionConfig, InterruptionMode, MllmTurnDetectionConfig, @@ -207,6 +209,7 @@ OpenAIGPTLive, OpenAISampleRate, OpenAISTT, + RtzrSTT, OpenAITTS, GenericTTS, RimeTTS, @@ -303,6 +306,8 @@ "FarewellConfig", "ParametersDataChannel", "ParametersAudioScenario", + "SpeakConfig", + "ParametersSpeak", "InterruptionConfig", "InterruptionMode", "MllmTurnDetectionConfig", @@ -449,6 +454,7 @@ "MicrosoftSTT", "MicrosoftCNSTT", "OpenAISTT", + "RtzrSTT", "GeminiSTT", "GeminiSTTModels", "GoogleSTT", diff --git a/src/agora_agent/agentkit/agent.py b/src/agora_agent/agentkit/agent.py index 5eb5659..34be68c 100644 --- a/src/agora_agent/agentkit/agent.py +++ b/src/agora_agent/agentkit/agent.py @@ -47,6 +47,7 @@ from ..agents.types.start_agents_request_properties_parameters_farewell_config import StartAgentsRequestPropertiesParametersFarewellConfig from ..agents.types.start_agents_request_properties_parameters_data_channel import StartAgentsRequestPropertiesParametersDataChannel from ..agents.types.start_agents_request_properties_parameters_audio_scenario import StartAgentsRequestPropertiesParametersAudioScenario +from ..agents.types.start_agents_request_properties_parameters_speak import StartAgentsRequestPropertiesParametersSpeak from ..agents.types.start_agents_request_properties_interruption import StartAgentsRequestPropertiesInterruption from ..agents.types.start_agents_request_properties_interruption_mode import StartAgentsRequestPropertiesInterruptionMode from ..agents.types.start_agents_request_properties_geofence import StartAgentsRequestPropertiesGeofence @@ -140,6 +141,8 @@ FarewellConfig = StartAgentsRequestPropertiesParametersFarewellConfig ParametersDataChannel = StartAgentsRequestPropertiesParametersDataChannel ParametersAudioScenario = StartAgentsRequestPropertiesParametersAudioScenario +SpeakConfig = StartAgentsRequestPropertiesParametersSpeak +ParametersSpeak = SpeakConfig InterruptionConfig = StartAgentsRequestPropertiesInterruption InterruptionMode = StartAgentsRequestPropertiesInterruptionMode MllmTurnDetectionConfig = MllmTurnDetection @@ -160,6 +163,7 @@ class SessionParamsInput(typing_extensions.TypedDict, total=False): + speak: StartAgentsRequestPropertiesParametersSpeak silence_config: StartAgentsRequestPropertiesParametersSilenceConfig farewell_config: StartAgentsRequestPropertiesParametersFarewellConfig data_channel: StartAgentsRequestPropertiesParametersDataChannel diff --git a/src/agora_agent/agentkit/agent_session.py b/src/agora_agent/agentkit/agent_session.py index 18a2bac..b1171db 100644 --- a/src/agora_agent/agentkit/agent_session.py +++ b/src/agora_agent/agentkit/agent_session.py @@ -260,6 +260,7 @@ def _validate_avatar_config(self) -> None: sample_rate = ( tts_params.get("sample_rate") or tts_params.get("sample_rate_hertz") + or tts_params.get("speech_sample_rate") or tts_params.get("samplingRate") ) if isinstance(sample_rate, int): diff --git a/src/agora_agent/agentkit/presets.py b/src/agora_agent/agentkit/presets.py index f160cee..ce76a2c 100644 --- a/src/agora_agent/agentkit/presets.py +++ b/src/agora_agent/agentkit/presets.py @@ -108,7 +108,7 @@ def infer_asr_preset(asr: typing.Optional[typing.Dict[str, typing.Any]]) -> typi if not asr or asr.get("vendor") != "deepgram": return None params = asr.get("params") or {} - if params.get("key"): + if params.get("api_key"): return None return _DEEPGRAM_MODEL_TO_PRESET.get(_normalize_model_name(params.get("model")) or "") diff --git a/src/agora_agent/agentkit/regional_agent.py b/src/agora_agent/agentkit/regional_agent.py index 8658154..9688574 100644 --- a/src/agora_agent/agentkit/regional_agent.py +++ b/src/agora_agent/agentkit/regional_agent.py @@ -34,6 +34,7 @@ GoogleSTT, MicrosoftSTT, OpenAISTT, + RtzrSTT, SarvamSTT, SmallestAISTT, SpeechmaticsSTT, @@ -91,6 +92,7 @@ AssemblyAISTT, SpeechmaticsSTT, SarvamSTT, + RtzrSTT, SmallestAISTT, XaiSTT, ] diff --git a/src/agora_agent/agentkit/vendors/__init__.py b/src/agora_agent/agentkit/vendors/__init__.py index 4d6e7c4..1181e8f 100644 --- a/src/agora_agent/agentkit/vendors/__init__.py +++ b/src/agora_agent/agentkit/vendors/__init__.py @@ -58,6 +58,7 @@ GoogleSTT, MicrosoftSTT, OpenAISTT, + RtzrSTT, SarvamSTT, SmallestAISTT, SpeechmaticsSTT, @@ -137,6 +138,7 @@ "MicrosoftSTT", "MicrosoftCNSTT", "OpenAISTT", + "RtzrSTT", "GeminiSTT", "GeminiSTTModels", "GoogleSTT", diff --git a/src/agora_agent/agentkit/vendors/catalog.py b/src/agora_agent/agentkit/vendors/catalog.py index ebfb918..fd73eac 100644 --- a/src/agora_agent/agentkit/vendors/catalog.py +++ b/src/agora_agent/agentkit/vendors/catalog.py @@ -26,6 +26,7 @@ GoogleSTT, MicrosoftSTT, OpenAISTT, + RtzrSTT, SarvamSTT, SmallestAISTT, SpeechmaticsSTT, @@ -83,6 +84,7 @@ def __init__( "assemblyai": AssemblyAISTT, "speechmatics": SpeechmaticsSTT, "sarvam": SarvamSTT, + "rtzr": RtzrSTT, "smallestai": SmallestAISTT, "xai": XaiSTT, }, diff --git a/src/agora_agent/agentkit/vendors/namespaces.py b/src/agora_agent/agentkit/vendors/namespaces.py index 782c5aa..c447a4f 100644 --- a/src/agora_agent/agentkit/vendors/namespaces.py +++ b/src/agora_agent/agentkit/vendors/namespaces.py @@ -23,6 +23,7 @@ GoogleSTT, MicrosoftSTT, OpenAISTT, + RtzrSTT, SarvamSTT, SmallestAISTT, SpeechmaticsSTT, @@ -62,6 +63,7 @@ class GlobalSTTVendors: assemblyai = AssemblyAISTT speechmatics = SpeechmaticsSTT sarvam = SarvamSTT + rtzr = RtzrSTT smallestai = SmallestAISTT xai = XaiSTT diff --git a/src/agora_agent/agentkit/vendors/region.py b/src/agora_agent/agentkit/vendors/region.py index 495c8bd..6c37b5b 100644 --- a/src/agora_agent/agentkit/vendors/region.py +++ b/src/agora_agent/agentkit/vendors/region.py @@ -28,6 +28,7 @@ "assemblyai", "speechmatics", "sarvam", + "rtzr", "smallestai", "xai", ) diff --git a/src/agora_agent/agentkit/vendors/stt.py b/src/agora_agent/agentkit/vendors/stt.py index f05fe79..ffeb9d1 100644 --- a/src/agora_agent/agentkit/vendors/stt.py +++ b/src/agora_agent/agentkit/vendors/stt.py @@ -97,7 +97,7 @@ def to_config(self) -> Dict[str, Any]: params: Dict[str, Any] = dict(self.additional_params or {}) if self.api_key is not None: - params["key"] = self.api_key + params["api_key"] = self.api_key if self.model is not None: params["model"] = self.model if self.language is not None: @@ -115,6 +115,50 @@ def to_config(self) -> Dict[str, Any]: return config +class RtzrSTTOptions(BaseModel): + model_config = ConfigDict(extra="forbid") + + client_id: str = Field(..., min_length=1, description="RTZR client ID") + client_secret: str = Field(..., min_length=1, description="RTZR client secret") + api_base: Optional[str] = Field(default=None, description="RTZR API base URL") + model_name: Optional[str] = Field(default=None, description="RTZR recognition model name") + language: Optional[str] = Field(default=None, description="RTZR recognition language code") + sample_rate: Optional[int] = Field(default=None, gt=0, description="Input audio sample rate in Hz") + encoding: Optional[str] = Field(default=None, description="Input audio encoding") + use_itn: Optional[bool] = Field(default=None, description="Enable inverse text normalization") + use_disfluency_filter: Optional[bool] = Field(default=None, description="Filter disfluencies such as stuttering") + use_profanity_filter: Optional[bool] = Field(default=None, description="Filter profanity") + use_punctuation: Optional[bool] = Field(default=None, description="Add punctuation to recognized text") + keywords: Optional[List[str]] = Field(default=None, description="Keywords that improve recognition accuracy") + additional_params: Optional[Dict[str, Any]] = Field(default=None) + + +class RtzrSTT(RtzrSTTOptions, BaseSTT): + """RTZR streaming speech-to-text provider.""" + + def to_config(self) -> Dict[str, Any]: + params: Dict[str, Any] = dict(self.additional_params or {}) + params.update({"client_id": self.client_id, "client_secret": self.client_secret}) + + for name in ( + "api_base", + "model_name", + "language", + "sample_rate", + "encoding", + "use_itn", + "use_disfluency_filter", + "use_profanity_filter", + "use_punctuation", + "keywords", + ): + value = getattr(self, name) + if value is not None: + params[name] = value + + return {"vendor": "rtzr", "params": params} + + class MicrosoftSTTOptions(BaseModel): model_config = ConfigDict(extra="forbid") diff --git a/src/agora_agent/agentkit/vendors/tts.py b/src/agora_agent/agentkit/vendors/tts.py index 0d8d3ff..c89a6df 100644 --- a/src/agora_agent/agentkit/vendors/tts.py +++ b/src/agora_agent/agentkit/vendors/tts.py @@ -1,3 +1,4 @@ +import warnings from typing import Any, Dict, List, Literal, Optional from urllib.parse import urlsplit @@ -568,9 +569,38 @@ class SarvamTTSOptions(BaseModel): pitch: Optional[float] = Field(default=None, description="Pitch adjustment") pace: Optional[float] = Field(default=None, description="Speed of speech") loudness: Optional[float] = Field(default=None, description="Volume level") - sample_rate: Optional[int] = Field(default=None, description="Audio sample rate in Hz") + speech_sample_rate: Optional[int] = Field(default=None, gt=0, description="Output speech sample rate in Hz") + enable_preprocessing: Optional[bool] = Field( + default=None, + description="Normalize English words and numeric entities before synthesis", + ) + model: Optional[str] = Field(default=None, description="Sarvam TTS model") + sample_rate: Optional[int] = Field( + default=None, + gt=0, + description="Deprecated alias for speech_sample_rate", + ) + additional_params: Optional[Dict[str, Any]] = Field(default=None, description="Additional Sarvam TTS parameters") skip_patterns: Optional[List[int]] = Field(default=None) + @model_validator(mode="before") + @classmethod + def _warn_deprecated_sample_rate(cls, values: Any) -> Any: + if isinstance(values, dict) and "sample_rate" in values: + warnings.warn( + "SarvamTTS.sample_rate is deprecated; use speech_sample_rate instead.", + DeprecationWarning, + stacklevel=2, + ) + return values + + @model_validator(mode="after") + def _validate_sample_rate_alias(self) -> "SarvamTTSOptions": + legacy_sample_rate = self.__dict__.get("sample_rate") + if self.speech_sample_rate is not None and legacy_sample_rate is not None: + raise ValueError("SarvamTTS accepts only one of speech_sample_rate or sample_rate") + return self + class SarvamTTS(SarvamTTSOptions, BaseTTS): @property @@ -578,19 +608,26 @@ def resolved_sample_rate(self) -> Optional[int]: return None def to_config(self) -> Dict[str, Any]: - params: Dict[str, Any] = { + params: Dict[str, Any] = dict(self.additional_params or {}) + params.update({ "api_subscription_key": self.key, "speaker": self.speaker, "target_language_code": self.target_language_code, - } + }) if self.pitch is not None: params["pitch"] = self.pitch if self.pace is not None: params["pace"] = self.pace if self.loudness is not None: params["loudness"] = self.loudness - if self.sample_rate is not None: - params["sample_rate"] = self.sample_rate + legacy_sample_rate = self.__dict__.get("sample_rate") + speech_sample_rate = self.speech_sample_rate if self.speech_sample_rate is not None else legacy_sample_rate + if speech_sample_rate is not None: + params["speech_sample_rate"] = speech_sample_rate + if self.enable_preprocessing is not None: + params["enable_preprocessing"] = self.enable_preprocessing + if self.model is not None: + params["model"] = self.model result: Dict[str, Any] = {"vendor": "sarvam", "params": params} if self.skip_patterns is not None: diff --git a/src/agora_agent/agents/types/start_agents_request_properties_parameters.py b/src/agora_agent/agents/types/start_agents_request_properties_parameters.py index 33bb237..d3153e9 100644 --- a/src/agora_agent/agents/types/start_agents_request_properties_parameters.py +++ b/src/agora_agent/agents/types/start_agents_request_properties_parameters.py @@ -15,6 +15,7 @@ from .start_agents_request_properties_parameters_silence_config import ( StartAgentsRequestPropertiesParametersSilenceConfig, ) +from .start_agents_request_properties_parameters_speak import StartAgentsRequestPropertiesParametersSpeak class StartAgentsRequestPropertiesParameters(UncheckedBaseModel): @@ -22,6 +23,11 @@ class StartAgentsRequestPropertiesParameters(UncheckedBaseModel): Agent configuration parameters. """ + speak: typing.Optional[StartAgentsRequestPropertiesParametersSpeak] = pydantic.Field(default=None) + """ + Settings for the agent's speak behavior. + """ + silence_config: typing.Optional[StartAgentsRequestPropertiesParametersSilenceConfig] = pydantic.Field(default=None) """ Settings related to agent silence behavior. Does not apply when you integrate a `mllm`. diff --git a/src/agora_agent/agents/types/start_agents_request_properties_parameters_speak.py b/src/agora_agent/agents/types/start_agents_request_properties_parameters_speak.py new file mode 100644 index 0000000..95003bc --- /dev/null +++ b/src/agora_agent/agents/types/start_agents_request_properties_parameters_speak.py @@ -0,0 +1,29 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2 +from ...core.unchecked_base_model import UncheckedBaseModel + + +class StartAgentsRequestPropertiesParametersSpeak(UncheckedBaseModel): + """ + Settings for the agent's speak behavior. + """ + + batch: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether to skip sentence segmentation for speak requests: + - `false`: Skip sentence segmentation. + - Omitted or `true`: Preserve sentence segmentation. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/agora_agent/core/client_wrapper.py b/src/agora_agent/core/client_wrapper.py index ac8bacd..8ce1e55 100644 --- a/src/agora_agent/core/client_wrapper.py +++ b/src/agora_agent/core/client_wrapper.py @@ -26,10 +26,10 @@ def __init__( def get_headers(self) -> typing.Dict[str, str]: headers: typing.Dict[str, str] = { - "User-Agent": "agora-agents/v2.10.0", + "User-Agent": "agora-agents/v2.11.0", "X-Fern-Language": "Python", "X-Fern-SDK-Name": "agora-agents", - "X-Fern-SDK-Version": "v2.10.0", + "X-Fern-SDK-Version": "v2.11.0", **(self.get_custom_headers() or {}), } headers["Authorization"] = httpx.BasicAuth(self._get_username(), self._get_password())._auth_header diff --git a/src/agora_agent/types/asr.py b/src/agora_agent/types/asr.py index 838ea54..dc5461d 100644 --- a/src/agora_agent/types/asr.py +++ b/src/agora_agent/types/asr.py @@ -18,6 +18,7 @@ from .google_asr_params import GoogleAsrParams from .microsoft_asr_params import MicrosoftAsrParams from .open_ai_asr_params import OpenAiAsrParams +from .rtzr_asr_params import RtzrAsrParams from .sarvam_asr_params import SarvamAsrParams from .smallest_ai_asr_params import SmallestAiAsrParams from .speechmatics_asr_params import SpeechmaticsAsrParams @@ -210,6 +211,21 @@ class Config: extra = pydantic.Extra.allow +class Asr_Rtzr(UncheckedBaseModel): + vendor: typing.Literal["rtzr"] = "rtzr" + language: typing.Optional[AsrLanguage] = None + params: RtzrAsrParams + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow + + class Asr_Xai(UncheckedBaseModel): vendor: typing.Literal["xai"] = "xai" language: typing.Optional[AsrLanguage] = None @@ -299,6 +315,7 @@ class Config: Asr_Assemblyai, Asr_Speechmatics, Asr_Sarvam, + Asr_Rtzr, Asr_Xai, Asr_Xfyun, Asr_XfyunBigmodel, diff --git a/src/agora_agent/types/deepgram_asr_params.py b/src/agora_agent/types/deepgram_asr_params.py index 6688333..e1f3656 100644 --- a/src/agora_agent/types/deepgram_asr_params.py +++ b/src/agora_agent/types/deepgram_asr_params.py @@ -17,7 +17,7 @@ class DeepgramAsrParams(UncheckedBaseModel): WebSocket URL for Deepgram's streaming API """ - key: str = pydantic.Field() + api_key: str = pydantic.Field() """ Deepgram API key """ diff --git a/src/agora_agent/types/rtzr_asr.py b/src/agora_agent/types/rtzr_asr.py new file mode 100644 index 0000000..e49023d --- /dev/null +++ b/src/agora_agent/types/rtzr_asr.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2 +from ..core.unchecked_base_model import UncheckedBaseModel +from .asr_language import AsrLanguage +from .rtzr_asr_params import RtzrAsrParams + + +class RtzrAsr(UncheckedBaseModel): + """ + RTZR ASR configuration. + """ + + language: typing.Optional[AsrLanguage] = None + params: RtzrAsrParams + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/agora_agent/types/rtzr_asr_params.py b/src/agora_agent/types/rtzr_asr_params.py new file mode 100644 index 0000000..40f2baa --- /dev/null +++ b/src/agora_agent/types/rtzr_asr_params.py @@ -0,0 +1,82 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2 +from ..core.unchecked_base_model import UncheckedBaseModel + + +class RtzrAsrParams(UncheckedBaseModel): + """ + RTZR ASR configuration parameters. + """ + + client_id: str = pydantic.Field() + """ + RTZR client ID. + """ + + client_secret: str = pydantic.Field() + """ + RTZR client secret. + """ + + api_base: typing.Optional[str] = pydantic.Field(default=None) + """ + RTZR API base URL. + """ + + model_name: typing.Optional[str] = pydantic.Field(default=None) + """ + RTZR recognition model name. + """ + + language: typing.Optional[str] = pydantic.Field(default=None) + """ + RTZR recognition language code. Defaults to Korean (`ko`). + """ + + sample_rate: typing.Optional[int] = pydantic.Field(default=None) + """ + Input audio sample rate in Hz. + """ + + encoding: typing.Optional[str] = pydantic.Field(default=None) + """ + Input audio encoding. + """ + + use_itn: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether to enable inverse text normalization. + """ + + use_disfluency_filter: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether to filter disfluencies such as stuttering. + """ + + use_profanity_filter: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether to filter profanity. + """ + + use_punctuation: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether to add punctuation to the recognized text. + """ + + keywords: typing.Optional[typing.List[str]] = pydantic.Field(default=None) + """ + Keywords to improve recognition accuracy. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/agora_agent/types/sarvam_tts_params.py b/src/agora_agent/types/sarvam_tts_params.py index 855299f..d544dea 100644 --- a/src/agora_agent/types/sarvam_tts_params.py +++ b/src/agora_agent/types/sarvam_tts_params.py @@ -20,32 +20,42 @@ class SarvamTtsParams(UncheckedBaseModel): speaker: str = pydantic.Field() """ - Voice ID (e.g., anushka, abhilash, karun, hitesh, manisha, vidya, arya) + Speaker voice to use. """ target_language_code: SarvamTtsParamsTargetLanguageCode = pydantic.Field() """ - Target language code (e.g., en-IN) + Target language code in BCP-47 format (e.g., `hi-IN`, `bn-IN`, `en-IN`). """ pitch: typing.Optional[float] = pydantic.Field(default=None) """ - Pitch adjustment for the voice + Pitch control for the `bulbul:v2` model. """ pace: typing.Optional[float] = pydantic.Field(default=None) """ - Speed of speech + Speech speed. Defaults to `1.0`. """ loudness: typing.Optional[float] = pydantic.Field(default=None) """ - Volume level of the speech + Audio loudness control for the `bulbul:v2` model. """ - sample_rate: typing.Optional[float] = pydantic.Field(default=None) + speech_sample_rate: typing.Optional[int] = pydantic.Field(default=None) """ - Audio sample rate in Hz + Output speech sample rate in Hz. Defaults to `24000`. + """ + + enable_preprocessing: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether to normalize English words and numeric entities. Defaults to `false`. + """ + + model: typing.Optional[str] = pydantic.Field(default=None) + """ + TTS model to use. Defaults to `bulbul:v3`. """ if IS_PYDANTIC_V2: diff --git a/tests/custom/test_agentkit_agent.py b/tests/custom/test_agentkit_agent.py index d7cf47e..6d9cd5d 100644 --- a/tests/custom/test_agentkit_agent.py +++ b/tests/custom/test_agentkit_agent.py @@ -6,6 +6,7 @@ LlmStyle, MllmConfig, MllmVendor, + SpeakConfig, SttConfig, SttVendor, TtsConfig, @@ -85,6 +86,21 @@ def test_with_audio_scenario_preserves_existing_parameters(): assert _parameter(agent.config, "audio_scenario") == "chorus" +def test_with_parameters_serializes_speak_settings(): + agent = Agent(test_client()).with_parameters({"speak": SpeakConfig(batch=False)}) + + properties = agent.to_properties( + channel="room", + agent_uid="1", + remote_uids=["100"], + token="token", + skip_vendor_validation=True, + ) + + assert properties.parameters.speak is not None + assert properties.parameters.speak.batch is False + + def test_enable_rtm_defaults_data_channel_to_rtm(): properties = Agent(test_client(), advanced_features={"enable_rtm": True}).to_properties( channel="room", diff --git a/tests/custom/test_regional_vendors.py b/tests/custom/test_regional_vendors.py index 92ff328..10516a4 100644 --- a/tests/custom/test_regional_vendors.py +++ b/tests/custom/test_regional_vendors.py @@ -13,6 +13,7 @@ MiniMaxTTS, MistralTTS, OpenAI, + RtzrSTT, SmallestAISTT, SmallestAITTS, SpatiusAvatar, @@ -229,6 +230,14 @@ def test_xai_grok_remains_mllm_vendor() -> None: assert agent.mllm is not None and agent.mllm["vendor"] == "xai" +def test_rtzr_is_registered_as_a_global_stt_vendor() -> None: + assert "rtzr" in GLOBAL_ASR_VENDORS + assert "rtzr" not in CN_ASR_VENDORS + assert GLOBAL_VENDOR_NAMESPACE.asr["rtzr"] is RtzrSTT + assert GlobalSTTVendors.rtzr is RtzrSTT + assert RtzrSTT in get_args(GlobalSTT) + + def test_smallest_ai_is_registered_as_global_only() -> None: assert "smallestai" in GLOBAL_ASR_VENDORS assert "smallestai" in GLOBAL_TTS_VENDORS diff --git a/tests/custom/test_request_body.py b/tests/custom/test_request_body.py index f5bab70..3106870 100644 --- a/tests/custom/test_request_body.py +++ b/tests/custom/test_request_body.py @@ -323,7 +323,7 @@ def test_byok_pipeline_full_properties_shape() -> None: # ASR asr = props["asr"] assert asr["vendor"] == "deepgram" - assert asr["params"]["key"] == "dg-key" + assert asr["params"]["api_key"] == "dg-key" assert asr["params"]["model"] == "nova-2" assert asr["params"]["language"] == "en" @@ -630,7 +630,7 @@ def test_6b_tts_preset_with_byok_llm_and_asr() -> None: properties = dump(call["properties"]) # BYOK ASR: key and model both retained (nothing stripped for BYOK path) - assert properties["asr"]["params"]["key"] == "dg-key" + assert properties["asr"]["params"]["api_key"] == "dg-key" assert properties["asr"]["params"]["model"] == "nova-2" # BYOK LLM key retained assert properties["llm"]["api_key"] == "openai-key" @@ -779,7 +779,7 @@ def test_byok_deepgram_stt_params() -> None: ) props = build_properties(agent, allow_missing={"llm", "tts"}) assert props["asr"]["vendor"] == "deepgram" - assert props["asr"]["params"]["key"] == "dg-key" + assert props["asr"]["params"]["api_key"] == "dg-key" assert props["asr"]["params"]["model"] == "nova-2" assert props["asr"]["params"]["language"] == "en" @@ -1163,7 +1163,7 @@ def test_byok_sarvam_tts_params() -> None: key="sarvam-key", speaker="anushka", target_language_code="en-IN", - sample_rate=24000, + speech_sample_rate=24000, ) ) props = build_properties(agent, allow_missing={"asr", "llm"}) diff --git a/tests/custom/test_stt_language.py b/tests/custom/test_stt_language.py index 27ce63f..9e2b8be 100644 --- a/tests/custom/test_stt_language.py +++ b/tests/custom/test_stt_language.py @@ -11,6 +11,7 @@ MicrosoftSTT, OpenAI, OpenAISTT, + RtzrSTT, SarvamSTT, SpeechmaticsSTT, TurnDetectionConfig, @@ -110,13 +111,13 @@ def test_stt_vendor_params_match_documented_shapes() -> None: DeepgramSTT(model="enhanced") assert DeepgramSTT(api_key="dg-key", language="en").to_config()["params"] == { - "key": "dg-key", + "api_key": "dg-key", "language": "en", } - # api_key → wire key "key"; keyterm passes through unchanged + # api_key uses the generated Deepgram ASR field name; keyterm passes through unchanged assert DeepgramSTT(api_key="dg-key", model="nova-3", language="en", keyterm="term").to_config()["params"] == { - "key": "dg-key", + "api_key": "dg-key", "model": "nova-3", "language": "en", "keyterm": "term", @@ -188,6 +189,55 @@ def test_stt_vendor_params_match_documented_shapes() -> None: } +def test_rtzr_stt_serializes_generated_params() -> None: + assert RtzrSTT( + client_id="client-id", + client_secret="client-secret", + api_base="https://rtzr.example.com", + model_name="general", + language="ko", + sample_rate=16000, + encoding="pcm_s16le", + use_itn=True, + use_disfluency_filter=False, + use_profanity_filter=True, + use_punctuation=True, + keywords=["Agora"], + ).to_config() == { + "vendor": "rtzr", + "params": { + "client_id": "client-id", + "client_secret": "client-secret", + "api_base": "https://rtzr.example.com", + "model_name": "general", + "language": "ko", + "sample_rate": 16000, + "encoding": "pcm_s16le", + "use_itn": True, + "use_disfluency_filter": False, + "use_profanity_filter": True, + "use_punctuation": True, + "keywords": ["Agora"], + }, + } + + +def test_rtzr_stt_reaches_generated_request_properties() -> None: + props = properties( + base_agent().with_stt(RtzrSTT(client_id="client-id", client_secret="client-secret", language="ko")) + ) + + assert props["asr"] == { + "vendor": "rtzr", + "language": "en-US", + "params": { + "client_id": "client-id", + "client_secret": "client-secret", + "language": "ko", + }, + } + + def test_assemblyai_params_stay_nested_and_asr_language_comes_from_turn_detection() -> None: props = properties( Agent(test_client(), turn_detection=TurnDetectionConfig(language="fr-FR")) diff --git a/tests/custom/test_tts_vendors.py b/tests/custom/test_tts_vendors.py index 43388bb..6abf094 100644 --- a/tests/custom/test_tts_vendors.py +++ b/tests/custom/test_tts_vendors.py @@ -198,11 +198,22 @@ def test_tts_vendor_params_match_generated_core_shapes() -> None: "language_boost": "auto", } - assert SarvamTTS(key="sarvam-key", speaker="anushka", target_language_code="en-IN", sample_rate=24000).to_config()["params"] == { + assert SarvamTTS( + key="sarvam-key", + speaker="anushka", + target_language_code="en-IN", + speech_sample_rate=24000, + enable_preprocessing=True, + model="bulbul:v3", + additional_params={"custom_option": "value", "model": "overridden"}, + ).to_config()["params"] == { "api_subscription_key": "sarvam-key", "speaker": "anushka", "target_language_code": "en-IN", - "sample_rate": 24000, + "speech_sample_rate": 24000, + "enable_preprocessing": True, + "model": "bulbul:v3", + "custom_option": "value", } assert MurfTTS(