Skip to content

Commit bda9c94

Browse files
Merge pull request #16 from AgoraIO-Conversational-AI/release/v1.1.0
Release/v1.1.0
2 parents e024ed2 + fb7de81 commit bda9c94

7 files changed

Lines changed: 123 additions & 39 deletions

File tree

changelog.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/).
66

7+
## [v1.1.0] — 2026-03-17
8+
9+
### Added
10+
11+
- `MurfTTS` vendor
12+
13+
14+
### Fixed
15+
16+
- `MiniMaxTTS`: added required `group_id`, `url`, and correctly nested `voice_setting.voice_id` — previously missing, requiring users to bypass the SDK entirely
17+
- `SarvamTTS`: corrected schema to `key` + `speaker` + `target_language_code` (was incorrectly using `api_key`, `voice_id`, `model`)
18+
- All LLM vendors: added `max_history` field for conversation history caching
19+
- `AzureOpenAI` LLM: added `params` escape hatch for passing arbitrary API parameters
20+
- `Anthropic` LLM: added `url` for custom endpoints and `params` escape hatch
21+
- `Gemini` LLM: added `url` for custom endpoints and `params` escape hatch; named model params (`temperature`, `top_p`, `top_k`, `max_output_tokens`) now take precedence over `params` dict
22+
- `SpeechmaticsSTT`, `SarvamSTT`: added optional `model` field
23+
724
## [v1.0.0] — 2026-03-11
825

926
Initial stable release of the Agora Agent Server SDK for Python.

docs/reference/vendors.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -200,30 +200,33 @@ Fixed sample rate: 24000 Hz.
200200
| `reference_id` | `str` | Yes || Reference ID |
201201
| `skip_patterns` | `List[int]` | No | `None` | Skip patterns |
202202

203-
### `GroqTTS`
203+
### `MiniMaxTTS`
204204

205205
| Parameter | Type | Required | Default | Description |
206206
|---|---|---|---|---|
207-
| `key` | `str` | Yes || Groq API key |
208-
| `model` | `str` | No | `None` | Model name |
207+
| `key` | `str` | Yes || MiniMax API key |
208+
| `group_id` | `str` | Yes || MiniMax group ID |
209+
| `model` | `str` | Yes || Model name (e.g., `speech-02-turbo`) |
210+
| `voice_id` | `str` | Yes || Voice style identifier |
211+
| `url` | `str` | Yes || WebSocket endpoint |
209212
| `skip_patterns` | `List[int]` | No | `None` | Skip patterns |
210213

211-
### `MiniMaxTTS`
214+
### `MurfTTS`
212215

213216
| Parameter | Type | Required | Default | Description |
214217
|---|---|---|---|---|
215-
| `key` | `str` | Yes || MiniMax API key |
216-
| `voice_id` | `str` | No | `None` | Voice ID |
217-
| `model` | `str` | No | `None` | Model name |
218+
| `key` | `str` | Yes || Murf API key |
219+
| `voice_id` | `str` | Yes | | Voice ID (e.g., `Ariana`, `Natalie`) |
220+
| `style` | `str` | No | `None` | Voice style (e.g., `Conversational`) |
218221
| `skip_patterns` | `List[int]` | No | `None` | Skip patterns |
219222

220223
### `SarvamTTS`
221224

222225
| Parameter | Type | Required | Default | Description |
223226
|---|---|---|---|---|
224-
| `api_key` | `str` | Yes || Sarvam API key |
225-
| `voice_id` | `str` | No | `None` | Voice ID |
226-
| `model` | `str` | No | `None` | Model name |
227+
| `key` | `str` | Yes || Sarvam API key |
228+
| `speaker` | `str` | Yes | | Speaker name |
229+
| `target_language_code` | `str` | Yes | | Target language code |
227230
| `skip_patterns` | `List[int]` | No | `None` | Skip patterns |
228231

229232
---

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "agora-agent-server-sdk"
33

44
[tool.poetry]
55
name = "agora-agent-server-sdk"
6-
version = "1.0.0"
6+
version = "1.1.0"
77
description = ""
88
readme = "README.md"
99
authors = []

src/agora_agent/agentkit/vendors/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
HumeAITTS,
3535
MicrosoftTTS,
3636
MiniMaxTTS,
37+
MurfTTS,
3738
OpenAITTS,
3839
RimeTTS,
3940
SarvamTTS,
@@ -65,6 +66,7 @@
6566
"RimeTTS",
6667
"FishAudioTTS",
6768
"MiniMaxTTS",
69+
"MurfTTS",
6870
"SarvamTTS",
6971
"SpeechmaticsSTT",
7072
"DeepgramSTT",

src/agora_agent/agentkit/vendors/llm.py

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class OpenAIOptions(BaseModel):
3232
template_variables: Optional[Dict[str, str]] = Field(default=None)
3333
vendor: Optional[str] = Field(default=None)
3434
mcp_servers: Optional[List[Dict[str, Any]]] = Field(default=None)
35+
max_history: Optional[int] = Field(default=None, gt=0, description="Maximum number of conversation history messages to cache")
3536

3637
class Config:
3738
extra = "forbid"
@@ -78,6 +79,8 @@ def to_config(self) -> Dict[str, Any]:
7879
config["vendor"] = self.options.vendor
7980
if self.options.mcp_servers is not None:
8081
config["mcp_servers"] = _ensure_mcp_transport(self.options.mcp_servers)
82+
if self.options.max_history is not None:
83+
config["max_history"] = self.options.max_history
8184

8285
return config
8386

@@ -94,11 +97,13 @@ class AzureOpenAIOptions(BaseModel):
9497
greeting_message: Optional[str] = Field(default=None)
9598
failure_message: Optional[str] = Field(default=None)
9699
input_modalities: Optional[List[str]] = Field(default=None)
100+
params: Optional[Dict[str, Any]] = Field(default=None)
97101
output_modalities: Optional[List[str]] = Field(default=None)
98102
greeting_configs: Optional[Dict[str, Any]] = Field(default=None)
99103
template_variables: Optional[Dict[str, str]] = Field(default=None)
100104
vendor: Optional[str] = Field(default=None)
101105
mcp_servers: Optional[List[Dict[str, Any]]] = Field(default=None)
106+
max_history: Optional[int] = Field(default=None, gt=0, description="Maximum number of conversation history messages to cache")
102107

103108
class Config:
104109
extra = "forbid"
@@ -122,7 +127,8 @@ def to_config(self) -> Dict[str, Any]:
122127
"input_modalities": self.options.input_modalities or ["text"],
123128
}
124129

125-
params: Dict[str, Any] = {}
130+
# Named fields take precedence over anything in the generic params dict.
131+
params: Dict[str, Any] = dict(self.options.params or {})
126132
if self.options.temperature is not None:
127133
params["temperature"] = self.options.temperature
128134
if self.options.top_p is not None:
@@ -146,25 +152,30 @@ def to_config(self) -> Dict[str, Any]:
146152
config["template_variables"] = self.options.template_variables
147153
if self.options.mcp_servers is not None:
148154
config["mcp_servers"] = _ensure_mcp_transport(self.options.mcp_servers)
155+
if self.options.max_history is not None:
156+
config["max_history"] = self.options.max_history
149157

150158
return config
151159

152160

153161
class AnthropicOptions(BaseModel):
154162
api_key: str = Field(..., description="Anthropic API key")
155163
model: str = Field(default="claude-3-5-sonnet-20241022", description="Model name")
164+
url: Optional[str] = Field(default=None, description="Custom API endpoint URL")
156165
max_tokens: Optional[int] = Field(default=None, gt=0)
157166
temperature: Optional[float] = Field(default=None, ge=0.0, le=1.0)
158167
top_p: Optional[float] = Field(default=None, ge=0.0, le=1.0)
159168
system_messages: Optional[List[Dict[str, Any]]] = Field(default=None)
160169
greeting_message: Optional[str] = Field(default=None)
161170
failure_message: Optional[str] = Field(default=None)
162171
input_modalities: Optional[List[str]] = Field(default=None)
172+
params: Optional[Dict[str, Any]] = Field(default=None)
163173
output_modalities: Optional[List[str]] = Field(default=None)
164174
greeting_configs: Optional[Dict[str, Any]] = Field(default=None)
165175
template_variables: Optional[Dict[str, str]] = Field(default=None)
166176
vendor: Optional[str] = Field(default=None)
167177
mcp_servers: Optional[List[Dict[str, Any]]] = Field(default=None)
178+
max_history: Optional[int] = Field(default=None, gt=0, description="Maximum number of conversation history messages to cache")
168179

169180
class Config:
170181
extra = "forbid"
@@ -175,20 +186,23 @@ def __init__(self, **kwargs: Any):
175186
self.options = AnthropicOptions(**kwargs)
176187

177188
def to_config(self) -> Dict[str, Any]:
189+
# Named fields take precedence over anything in the generic params dict.
190+
params: Dict[str, Any] = {"model": self.options.model, **(self.options.params or {})}
191+
if self.options.max_tokens is not None:
192+
params["max_tokens"] = self.options.max_tokens
193+
if self.options.temperature is not None:
194+
params["temperature"] = self.options.temperature
195+
if self.options.top_p is not None:
196+
params["top_p"] = self.options.top_p
197+
178198
config: Dict[str, Any] = {
179-
"url": "https://api.anthropic.com/v1/messages",
199+
"url": self.options.url or "https://api.anthropic.com/v1/messages",
180200
"api_key": self.options.api_key,
181-
"params": {"model": self.options.model},
201+
"params": params,
182202
"style": "anthropic",
183203
"input_modalities": self.options.input_modalities or ["text"],
184204
}
185205

186-
if self.options.max_tokens is not None:
187-
config["params"]["max_tokens"] = self.options.max_tokens
188-
if self.options.temperature is not None:
189-
config["params"]["temperature"] = self.options.temperature
190-
if self.options.top_p is not None:
191-
config["params"]["top_p"] = self.options.top_p
192206
if self.options.system_messages is not None:
193207
config["system_messages"] = self.options.system_messages
194208
if self.options.greeting_message is not None:
@@ -205,13 +219,16 @@ def to_config(self) -> Dict[str, Any]:
205219
config["vendor"] = self.options.vendor
206220
if self.options.mcp_servers is not None:
207221
config["mcp_servers"] = _ensure_mcp_transport(self.options.mcp_servers)
222+
if self.options.max_history is not None:
223+
config["max_history"] = self.options.max_history
208224

209225
return config
210226

211227

212228
class GeminiOptions(BaseModel):
213229
api_key: str = Field(..., description="Google AI API key")
214230
model: str = Field(default="gemini-2.0-flash-exp", description="Model name")
231+
url: Optional[str] = Field(default=None, description="Custom API endpoint URL")
215232
temperature: Optional[float] = Field(default=None, ge=0.0, le=2.0)
216233
top_p: Optional[float] = Field(default=None, ge=0.0, le=1.0)
217234
top_k: Optional[int] = Field(default=None, gt=0)
@@ -220,11 +237,13 @@ class GeminiOptions(BaseModel):
220237
greeting_message: Optional[str] = Field(default=None)
221238
failure_message: Optional[str] = Field(default=None)
222239
input_modalities: Optional[List[str]] = Field(default=None)
240+
params: Optional[Dict[str, Any]] = Field(default=None)
223241
output_modalities: Optional[List[str]] = Field(default=None)
224242
greeting_configs: Optional[Dict[str, Any]] = Field(default=None)
225243
template_variables: Optional[Dict[str, str]] = Field(default=None)
226244
vendor: Optional[str] = Field(default=None)
227245
mcp_servers: Optional[List[Dict[str, Any]]] = Field(default=None)
246+
max_history: Optional[int] = Field(default=None, gt=0, description="Maximum number of conversation history messages to cache")
228247

229248
class Config:
230249
extra = "forbid"
@@ -235,22 +254,25 @@ def __init__(self, **kwargs: Any):
235254
self.options = GeminiOptions(**kwargs)
236255

237256
def to_config(self) -> Dict[str, Any]:
257+
# Named fields take precedence over anything in the generic params dict.
258+
params: Dict[str, Any] = {"model": self.options.model, **(self.options.params or {})}
259+
if self.options.temperature is not None:
260+
params["temperature"] = self.options.temperature
261+
if self.options.top_p is not None:
262+
params["top_p"] = self.options.top_p
263+
if self.options.top_k is not None:
264+
params["top_k"] = self.options.top_k
265+
if self.options.max_output_tokens is not None:
266+
params["max_output_tokens"] = self.options.max_output_tokens
267+
238268
config: Dict[str, Any] = {
239-
"url": "https://generativelanguage.googleapis.com/v1beta/models",
269+
"url": self.options.url or "https://generativelanguage.googleapis.com/v1beta/models",
240270
"api_key": self.options.api_key,
241-
"params": {"model": self.options.model},
271+
"params": params,
242272
"style": "gemini",
243273
"input_modalities": self.options.input_modalities or ["text"],
244274
}
245275

246-
if self.options.temperature is not None:
247-
config["params"]["temperature"] = self.options.temperature
248-
if self.options.top_p is not None:
249-
config["params"]["top_p"] = self.options.top_p
250-
if self.options.top_k is not None:
251-
config["params"]["top_k"] = self.options.top_k
252-
if self.options.max_output_tokens is not None:
253-
config["params"]["max_output_tokens"] = self.options.max_output_tokens
254276
if self.options.system_messages is not None:
255277
config["system_messages"] = self.options.system_messages
256278
if self.options.greeting_message is not None:
@@ -267,5 +289,7 @@ def to_config(self) -> Dict[str, Any]:
267289
config["vendor"] = self.options.vendor
268290
if self.options.mcp_servers is not None:
269291
config["mcp_servers"] = _ensure_mcp_transport(self.options.mcp_servers)
292+
if self.options.max_history is not None:
293+
config["max_history"] = self.options.max_history
270294

271295
return config

src/agora_agent/agentkit/vendors/stt.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
class SpeechmaticsSTTOptions(BaseModel):
99
api_key: str = Field(..., description="Speechmatics API key")
1010
language: str = Field(..., description="Language code (e.g., en, es, fr)")
11+
model: Optional[str] = Field(default=None, description="Model name")
1112
additional_params: Optional[Dict[str, Any]] = Field(default=None)
1213

1314
class Config:
@@ -23,6 +24,8 @@ def to_config(self) -> Dict[str, Any]:
2324
"api_key": self.options.api_key,
2425
"language": self.options.language,
2526
}
27+
if self.options.model is not None:
28+
params["model"] = self.options.model
2629
if self.options.additional_params is not None:
2730
params.update(self.options.additional_params)
2831

@@ -247,6 +250,7 @@ def to_config(self) -> Dict[str, Any]:
247250
class SarvamSTTOptions(BaseModel):
248251
api_key: str = Field(..., description="Sarvam API key")
249252
language: str = Field(..., description="Language code (e.g., en, hi, ta)")
253+
model: Optional[str] = Field(default=None, description="Model name")
250254
additional_params: Optional[Dict[str, Any]] = Field(default=None)
251255

252256
class Config:
@@ -262,6 +266,8 @@ def to_config(self) -> Dict[str, Any]:
262266
"api_key": self.options.api_key,
263267
"language": self.options.language,
264268
}
269+
if self.options.model is not None:
270+
params["model"] = self.options.model
265271
if self.options.additional_params is not None:
266272
params.update(self.options.additional_params)
267273

src/agora_agent/agentkit/vendors/tts.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,9 @@ def to_config(self) -> Dict[str, Any]:
358358

359359

360360
class SarvamTTSOptions(BaseModel):
361-
api_key: str = Field(..., description="Sarvam API key")
362-
voice_id: Optional[str] = Field(default=None, description="Voice ID")
363-
model: Optional[str] = Field(default=None, description="Model name")
361+
key: str = Field(..., description="Sarvam API subscription key")
362+
speaker: str = Field(..., description="Speaker/voice ID (e.g., 'anushka', 'abhilash', 'karun', 'hitesh', 'manisha', 'vidya', 'arya')")
363+
target_language_code: str = Field(..., description="Target language code (e.g., 'en-IN', 'hi-IN', 'ta-IN')")
364364
skip_patterns: Optional[List[int]] = Field(default=None)
365365

366366
class Config:
@@ -376,14 +376,46 @@ def sample_rate(self) -> Optional[int]:
376376
return None
377377

378378
def to_config(self) -> Dict[str, Any]:
379-
params: Dict[str, Any] = {"api_key": self.options.api_key}
380-
381-
if self.options.voice_id is not None:
382-
params["voice_id"] = self.options.voice_id
383-
if self.options.model is not None:
384-
params["model"] = self.options.model
379+
params: Dict[str, Any] = {
380+
"key": self.options.key,
381+
"speaker": self.options.speaker,
382+
"target_language_code": self.options.target_language_code,
383+
}
385384

386385
result: Dict[str, Any] = {"vendor": "sarvam", "params": params}
387386
if self.options.skip_patterns is not None:
388387
result["skip_patterns"] = self.options.skip_patterns
389388
return result
389+
390+
391+
class MurfTTSOptions(BaseModel):
392+
key: str = Field(..., description="Murf API key")
393+
voice_id: str = Field(..., description="Voice ID (e.g., 'Ariana', 'Natalie', 'Ken')")
394+
style: Optional[str] = Field(default=None, description="Voice style (e.g., 'Angry', 'Sad', 'Conversational', 'Newscast')")
395+
skip_patterns: Optional[List[int]] = Field(default=None)
396+
397+
class Config:
398+
extra = "forbid"
399+
400+
401+
class MurfTTS(BaseTTS):
402+
def __init__(self, **kwargs: Any):
403+
self.options = MurfTTSOptions(**kwargs)
404+
405+
@property
406+
def sample_rate(self) -> Optional[int]:
407+
return None
408+
409+
def to_config(self) -> Dict[str, Any]:
410+
params: Dict[str, Any] = {
411+
"key": self.options.key,
412+
"voice_id": self.options.voice_id,
413+
}
414+
415+
if self.options.style is not None:
416+
params["style"] = self.options.style
417+
418+
result: Dict[str, Any] = {"vendor": "murf", "params": params}
419+
if self.options.skip_patterns is not None:
420+
result["skip_patterns"] = self.options.skip_patterns
421+
return result

0 commit comments

Comments
 (0)