Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.

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

## [v1.1.0] — 2026-03-17

### Added

- `MurfTTS` vendor


### Fixed

- `MiniMaxTTS`: added required `group_id`, `url`, and correctly nested `voice_setting.voice_id` — previously missing, requiring users to bypass the SDK entirely
- `SarvamTTS`: corrected schema to `key` + `speaker` + `target_language_code` (was incorrectly using `api_key`, `voice_id`, `model`)
- All LLM vendors: added `max_history` field for conversation history caching
- `AzureOpenAI` LLM: added `params` escape hatch for passing arbitrary API parameters
- `Anthropic` LLM: added `url` for custom endpoints and `params` escape hatch
- `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
- `SpeechmaticsSTT`, `SarvamSTT`: added optional `model` field

## [v1.0.0] — 2026-03-11

Initial stable release of the Agora Agent Server SDK for Python.
Expand Down
23 changes: 13 additions & 10 deletions docs/reference/vendors.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,30 +200,33 @@ Fixed sample rate: 24000 Hz.
| `reference_id` | `str` | Yes | — | Reference ID |
| `skip_patterns` | `List[int]` | No | `None` | Skip patterns |

### `GroqTTS`
### `MiniMaxTTS`

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

### `MiniMaxTTS`
### `MurfTTS`

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `key` | `str` | Yes | — | MiniMax API key |
| `voice_id` | `str` | No | `None` | Voice ID |
| `model` | `str` | No | `None` | Model name |
| `key` | `str` | Yes | — | Murf API key |
| `voice_id` | `str` | Yes | | Voice ID (e.g., `Ariana`, `Natalie`) |
| `style` | `str` | No | `None` | Voice style (e.g., `Conversational`) |
| `skip_patterns` | `List[int]` | No | `None` | Skip patterns |

### `SarvamTTS`

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `api_key` | `str` | Yes | — | Sarvam API key |
| `voice_id` | `str` | No | `None` | Voice ID |
| `model` | `str` | No | `None` | Model name |
| `key` | `str` | Yes | — | Sarvam API key |
| `speaker` | `str` | Yes | | Speaker name |
| `target_language_code` | `str` | Yes | | Target language code |
| `skip_patterns` | `List[int]` | No | `None` | Skip patterns |

---
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "agora-agent-server-sdk"

[tool.poetry]
name = "agora-agent-server-sdk"
version = "1.0.0"
version = "1.1.0"
description = ""
readme = "README.md"
authors = []
Expand Down
2 changes: 2 additions & 0 deletions src/agora_agent/agentkit/vendors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
HumeAITTS,
MicrosoftTTS,
MiniMaxTTS,
MurfTTS,
OpenAITTS,
RimeTTS,
SarvamTTS,
Expand Down Expand Up @@ -65,6 +66,7 @@
"RimeTTS",
"FishAudioTTS",
"MiniMaxTTS",
"MurfTTS",
"SarvamTTS",
"SpeechmaticsSTT",
"DeepgramSTT",
Expand Down
62 changes: 43 additions & 19 deletions src/agora_agent/agentkit/vendors/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class OpenAIOptions(BaseModel):
template_variables: Optional[Dict[str, str]] = Field(default=None)
vendor: Optional[str] = Field(default=None)
mcp_servers: Optional[List[Dict[str, Any]]] = Field(default=None)
max_history: Optional[int] = Field(default=None, gt=0, description="Maximum number of conversation history messages to cache")

class Config:
extra = "forbid"
Expand Down Expand Up @@ -78,6 +79,8 @@ def to_config(self) -> Dict[str, Any]:
config["vendor"] = self.options.vendor
if self.options.mcp_servers is not None:
config["mcp_servers"] = _ensure_mcp_transport(self.options.mcp_servers)
if self.options.max_history is not None:
config["max_history"] = self.options.max_history

return config

Expand All @@ -94,11 +97,13 @@ class AzureOpenAIOptions(BaseModel):
greeting_message: Optional[str] = Field(default=None)
failure_message: Optional[str] = Field(default=None)
input_modalities: Optional[List[str]] = Field(default=None)
params: Optional[Dict[str, Any]] = Field(default=None)
output_modalities: Optional[List[str]] = Field(default=None)
greeting_configs: Optional[Dict[str, Any]] = Field(default=None)
template_variables: Optional[Dict[str, str]] = Field(default=None)
vendor: Optional[str] = Field(default=None)
mcp_servers: Optional[List[Dict[str, Any]]] = Field(default=None)
max_history: Optional[int] = Field(default=None, gt=0, description="Maximum number of conversation history messages to cache")

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

params: Dict[str, Any] = {}
# Named fields take precedence over anything in the generic params dict.
params: Dict[str, Any] = dict(self.options.params or {})
if self.options.temperature is not None:
params["temperature"] = self.options.temperature
if self.options.top_p is not None:
Expand All @@ -146,25 +152,30 @@ def to_config(self) -> Dict[str, Any]:
config["template_variables"] = self.options.template_variables
if self.options.mcp_servers is not None:
config["mcp_servers"] = _ensure_mcp_transport(self.options.mcp_servers)
if self.options.max_history is not None:
config["max_history"] = self.options.max_history

return config


class AnthropicOptions(BaseModel):
api_key: str = Field(..., description="Anthropic API key")
model: str = Field(default="claude-3-5-sonnet-20241022", description="Model name")
url: Optional[str] = Field(default=None, description="Custom API endpoint URL")
max_tokens: Optional[int] = Field(default=None, gt=0)
temperature: Optional[float] = Field(default=None, ge=0.0, le=1.0)
top_p: Optional[float] = Field(default=None, ge=0.0, le=1.0)
system_messages: Optional[List[Dict[str, Any]]] = Field(default=None)
greeting_message: Optional[str] = Field(default=None)
failure_message: Optional[str] = Field(default=None)
input_modalities: Optional[List[str]] = Field(default=None)
params: Optional[Dict[str, Any]] = Field(default=None)
output_modalities: Optional[List[str]] = Field(default=None)
greeting_configs: Optional[Dict[str, Any]] = Field(default=None)
template_variables: Optional[Dict[str, str]] = Field(default=None)
vendor: Optional[str] = Field(default=None)
mcp_servers: Optional[List[Dict[str, Any]]] = Field(default=None)
max_history: Optional[int] = Field(default=None, gt=0, description="Maximum number of conversation history messages to cache")

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

def to_config(self) -> Dict[str, Any]:
# Named fields take precedence over anything in the generic params dict.
params: Dict[str, Any] = {"model": self.options.model, **(self.options.params or {})}
if self.options.max_tokens is not None:
params["max_tokens"] = self.options.max_tokens
if self.options.temperature is not None:
params["temperature"] = self.options.temperature
if self.options.top_p is not None:
params["top_p"] = self.options.top_p

config: Dict[str, Any] = {
"url": "https://api.anthropic.com/v1/messages",
"url": self.options.url or "https://api.anthropic.com/v1/messages",
"api_key": self.options.api_key,
"params": {"model": self.options.model},
"params": params,
"style": "anthropic",
"input_modalities": self.options.input_modalities or ["text"],
}

if self.options.max_tokens is not None:
config["params"]["max_tokens"] = self.options.max_tokens
if self.options.temperature is not None:
config["params"]["temperature"] = self.options.temperature
if self.options.top_p is not None:
config["params"]["top_p"] = self.options.top_p
if self.options.system_messages is not None:
config["system_messages"] = self.options.system_messages
if self.options.greeting_message is not None:
Expand All @@ -205,13 +219,16 @@ def to_config(self) -> Dict[str, Any]:
config["vendor"] = self.options.vendor
if self.options.mcp_servers is not None:
config["mcp_servers"] = _ensure_mcp_transport(self.options.mcp_servers)
if self.options.max_history is not None:
config["max_history"] = self.options.max_history

return config


class GeminiOptions(BaseModel):
api_key: str = Field(..., description="Google AI API key")
model: str = Field(default="gemini-2.0-flash-exp", description="Model name")
url: Optional[str] = Field(default=None, description="Custom API endpoint URL")
temperature: Optional[float] = Field(default=None, ge=0.0, le=2.0)
top_p: Optional[float] = Field(default=None, ge=0.0, le=1.0)
top_k: Optional[int] = Field(default=None, gt=0)
Expand All @@ -220,11 +237,13 @@ class GeminiOptions(BaseModel):
greeting_message: Optional[str] = Field(default=None)
failure_message: Optional[str] = Field(default=None)
input_modalities: Optional[List[str]] = Field(default=None)
params: Optional[Dict[str, Any]] = Field(default=None)
output_modalities: Optional[List[str]] = Field(default=None)
greeting_configs: Optional[Dict[str, Any]] = Field(default=None)
template_variables: Optional[Dict[str, str]] = Field(default=None)
vendor: Optional[str] = Field(default=None)
mcp_servers: Optional[List[Dict[str, Any]]] = Field(default=None)
max_history: Optional[int] = Field(default=None, gt=0, description="Maximum number of conversation history messages to cache")

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

def to_config(self) -> Dict[str, Any]:
# Named fields take precedence over anything in the generic params dict.
params: Dict[str, Any] = {"model": self.options.model, **(self.options.params or {})}
if self.options.temperature is not None:
params["temperature"] = self.options.temperature
if self.options.top_p is not None:
params["top_p"] = self.options.top_p
if self.options.top_k is not None:
params["top_k"] = self.options.top_k
if self.options.max_output_tokens is not None:
params["max_output_tokens"] = self.options.max_output_tokens

config: Dict[str, Any] = {
"url": "https://generativelanguage.googleapis.com/v1beta/models",
"url": self.options.url or "https://generativelanguage.googleapis.com/v1beta/models",
"api_key": self.options.api_key,
"params": {"model": self.options.model},
"params": params,
"style": "gemini",
"input_modalities": self.options.input_modalities or ["text"],
}

if self.options.temperature is not None:
config["params"]["temperature"] = self.options.temperature
if self.options.top_p is not None:
config["params"]["top_p"] = self.options.top_p
if self.options.top_k is not None:
config["params"]["top_k"] = self.options.top_k
if self.options.max_output_tokens is not None:
config["params"]["max_output_tokens"] = self.options.max_output_tokens
if self.options.system_messages is not None:
config["system_messages"] = self.options.system_messages
if self.options.greeting_message is not None:
Expand All @@ -267,5 +289,7 @@ def to_config(self) -> Dict[str, Any]:
config["vendor"] = self.options.vendor
if self.options.mcp_servers is not None:
config["mcp_servers"] = _ensure_mcp_transport(self.options.mcp_servers)
if self.options.max_history is not None:
config["max_history"] = self.options.max_history

return config
6 changes: 6 additions & 0 deletions src/agora_agent/agentkit/vendors/stt.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
class SpeechmaticsSTTOptions(BaseModel):
api_key: str = Field(..., description="Speechmatics API key")
language: str = Field(..., description="Language code (e.g., en, es, fr)")
model: Optional[str] = Field(default=None, description="Model name")
additional_params: Optional[Dict[str, Any]] = Field(default=None)

class Config:
Expand All @@ -23,6 +24,8 @@ def to_config(self) -> Dict[str, Any]:
"api_key": self.options.api_key,
"language": self.options.language,
}
if self.options.model is not None:
params["model"] = self.options.model
if self.options.additional_params is not None:
params.update(self.options.additional_params)

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

class Config:
Expand All @@ -262,6 +266,8 @@ def to_config(self) -> Dict[str, Any]:
"api_key": self.options.api_key,
"language": self.options.language,
}
if self.options.model is not None:
params["model"] = self.options.model
if self.options.additional_params is not None:
params.update(self.options.additional_params)

Expand Down
50 changes: 41 additions & 9 deletions src/agora_agent/agentkit/vendors/tts.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,9 @@ def to_config(self) -> Dict[str, Any]:


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

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

def to_config(self) -> Dict[str, Any]:
params: Dict[str, Any] = {"api_key": self.options.api_key}

if self.options.voice_id is not None:
params["voice_id"] = self.options.voice_id
if self.options.model is not None:
params["model"] = self.options.model
params: Dict[str, Any] = {
"key": self.options.key,
"speaker": self.options.speaker,
"target_language_code": self.options.target_language_code,
}

result: Dict[str, Any] = {"vendor": "sarvam", "params": params}
if self.options.skip_patterns is not None:
result["skip_patterns"] = self.options.skip_patterns
return result


class MurfTTSOptions(BaseModel):
key: str = Field(..., description="Murf API key")
voice_id: str = Field(..., description="Voice ID (e.g., 'Ariana', 'Natalie', 'Ken')")
style: Optional[str] = Field(default=None, description="Voice style (e.g., 'Angry', 'Sad', 'Conversational', 'Newscast')")
skip_patterns: Optional[List[int]] = Field(default=None)

class Config:
extra = "forbid"


class MurfTTS(BaseTTS):
def __init__(self, **kwargs: Any):
self.options = MurfTTSOptions(**kwargs)

@property
def sample_rate(self) -> Optional[int]:
return None

def to_config(self) -> Dict[str, Any]:
params: Dict[str, Any] = {
"key": self.options.key,
"voice_id": self.options.voice_id,
}

if self.options.style is not None:
params["style"] = self.options.style

result: Dict[str, Any] = {"vendor": "murf", "params": params}
if self.options.skip_patterns is not None:
result["skip_patterns"] = self.options.skip_patterns
return result
Loading