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
54 changes: 19 additions & 35 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,62 +1,46 @@
name: ci

on:
push:
workflow_dispatch:

jobs:
compile:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Set up python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: 3.8
python-version: '3.8'

- name: Bootstrap poetry
run: |
curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1
run: curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1

- name: Install dependencies
run: poetry install

- name: Compile
run: poetry run mypy .

test:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Set up python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: 3.8
python-version: '3.8'

- name: Bootstrap poetry
run: |
curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1
run: curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1

- name: Install dependencies
run: poetry install

- name: Test
run: poetry run pytest -rP .

publish:
needs: [compile, test]
if: (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Set up python
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Bootstrap poetry
run: |
curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1
- name: Install dependencies
run: poetry install
- name: Build package
run: poetry build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
65 changes: 65 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: release

on:
push:
tags:
- 'v*'

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write # create GitHub Release
id-token: write # PyPI trusted publishing (OIDC)
steps:
- name: Checkout repo
uses: actions/checkout@v6

- name: Set up python
uses: actions/setup-python@v6
with:
python-version: '3.8'

- name: Bootstrap poetry
run: curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1

- name: Install dependencies
run: poetry install

- name: Compile
run: poetry run mypy .

- name: Test
run: poetry run pytest -rP .

- name: Build
run: poetry build

- name: Extract changelog notes
id: changelog
run: |
VERSION="${GITHUB_REF_NAME}"
NOTES=$(awk -v ver="## [${VERSION}]" '
index($0, ver) == 1 { found=1; next }
found && /^## / { exit }
found { print }
' changelog.md)
echo "notes<<EOF" >> "$GITHUB_OUTPUT"
echo "$NOTES" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"

- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
NOTES: ${{ steps.changelog.outputs.notes }}
run: |
echo "$NOTES" > release_notes.md
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--notes-file release_notes.md \
dist/*

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
23 changes: 23 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.

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

## [v1.2.0] — 2026-03-27

### Fixed

- **`AresSTT`** — Removed redundant `language` key from the `params` dict. Language is now emitted only at the top level. `params` is only included when `additional_params` is provided.
- **`OpenAIRealtime` / `VertexAI` (MLLM)** — Agent-level `greeting`, `failure_message`, and `max_history` overrides are now correctly applied when the agent is in MLLM mode. Previously these values were silently dropped.
- **`VertexAI` (MLLM)** — `messages` is now correctly placed inside `params` (required by the Gemini Live API). Previously it was emitted at the top level and silently ignored.

### Changed

- **`OpenAITTS`** — Renamed constructor parameter `key` → `api_key` to match the Agora server API expectation. ⚠️ **Breaking change.**
- **`CartesiaTTS`** — Renamed constructor parameter `key` → `api_key`. Voice is now serialized as `{"mode": "id", "id": "<voice_id>"}` instead of a flat `voice_id` string. ⚠️ **Breaking change.**
- **`HeyGenAvatar`** — Removed legacy fields `avatar_name`, `voice_id`, `language`, `version`. Added `agora_token`, `avatar_id`, `enable`, `disable_idle_timeout`, `activity_idle_timeout`. The config now includes a top-level `enable` field (defaults `true`). ⚠️ **Breaking change.**

### Added

- **`OpenAITTS`** — New optional parameters: `response_format` (str, e.g. `"pcm"`) and `speed` (float).
- **`CartesiaTTS`** — `voice_id` user-facing field is preserved; voice is serialized to the required nested object format automatically.
- **`RimeTTS`** — New optional parameters: `lang` (str), `sampling_rate` (int, serialized as `samplingRate`), `speed_alpha` (float, serialized as `speedAlpha`).
- **`OpenAIRealtime`** — New optional parameters: `predefined_tools` (List[str]), `failure_message` (str), `max_history` (int).
- **`VertexAI` (MLLM)** — New optional parameters: `predefined_tools` (List[str]), `failure_message` (str), `max_history` (int).
- **`HeyGenAvatar`** — New fields: `agora_token` (str, optional), `avatar_id` (str, optional), `enable` (bool, optional, default `True`), `disable_idle_timeout` (bool, optional), `activity_idle_timeout` (int, optional).

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

### Added
Expand Down
26 changes: 19 additions & 7 deletions docs/reference/vendors.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,11 @@ llm = Gemini(api_key='your-google-key', model='gemini-2.0-flash-exp')

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `key` | `str` | Yes | — | OpenAI API key |
| `api_key` | `str` | Yes | — | OpenAI API key |
| `voice` | `str` | Yes | — | Voice: `alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer` |
| `model` | `str` | No | `None` | Model: `tts-1` or `tts-1-hd` |
| `response_format` | `str` | No | `None` | Audio format (e.g., `pcm`) |
| `speed` | `float` | No | `None` | Speech speed multiplier |
| `skip_patterns` | `List[int]` | No | `None` | Skip patterns |

Fixed sample rate: 24000 Hz.
Expand All @@ -150,8 +152,8 @@ Fixed sample rate: 24000 Hz.

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `key` | `str` | Yes | — | Cartesia API key |
| `voice_id` | `str` | Yes | — | Voice ID |
| `api_key` | `str` | Yes | — | Cartesia API key |
| `voice_id` | `str` | Yes | — | Voice ID (serialized as `{"mode": "id", "id": "..."}`) |
| `model_id` | `str` | No | `None` | Model ID |
| `sample_rate` | `int` | No | `None` | Sample rate: 8000–48000 Hz |
| `skip_patterns` | `List[int]` | No | `None` | Skip patterns |
Expand Down Expand Up @@ -190,6 +192,9 @@ Fixed sample rate: 24000 Hz.
| `key` | `str` | Yes | — | Rime API key |
| `speaker` | `str` | Yes | — | Speaker ID |
| `model_id` | `str` | No | `None` | Model ID |
| `lang` | `str` | No | `None` | Language code |
| `sampling_rate` | `int` | No | `None` | Sampling rate in Hz (serialized as `samplingRate`) |
| `speed_alpha` | `float` | No | `None` | Speed multiplier (serialized as `speedAlpha`) |
| `skip_patterns` | `List[int]` | No | `None` | Skip patterns |

### `FishAudioTTS`
Expand Down Expand Up @@ -331,6 +336,9 @@ Fixed sample rate: 24000 Hz.
| `model` | `str` | No | `None` | Model (e.g., `gpt-4o-realtime-preview`) |
| `url` | `str` | No | `None` | Custom WebSocket URL |
| `greeting_message` | `str` | No | `None` | Greeting message |
| `failure_message` | `str` | No | `None` | Message played when the model call fails |
| `max_history` | `int` | No | `None` | Maximum conversation history length |
| `predefined_tools` | `List[str]` | No | `None` | Predefined tools (e.g., `["_publish_message"]`) |
| `input_modalities` | `List[str]` | No | `None` | Input modalities |
| `output_modalities` | `List[str]` | No | `None` | Output modalities |
| `messages` | `List[Dict]` | No | `None` | Conversation messages |
Expand All @@ -347,6 +355,9 @@ Fixed sample rate: 24000 Hz.
| `instructions` | `str` | No | `None` | System instructions |
| `voice` | `str` | No | `None` | Voice name (e.g., `Aoede`, `Charon`) |
| `greeting_message` | `str` | No | `None` | Greeting message |
| `failure_message` | `str` | No | `None` | Message played when the model call fails |
| `max_history` | `int` | No | `None` | Maximum conversation history length |
| `predefined_tools` | `List[str]` | No | `None` | Predefined tools (e.g., `["_publish_message"]`) |
| `input_modalities` | `List[str]` | No | `None` | Input modalities |
| `output_modalities` | `List[str]` | No | `None` | Output modalities |
| `messages` | `List[Dict]` | No | `None` | Conversation messages |
Expand All @@ -365,10 +376,11 @@ Required TTS sample rate: **24000 Hz**
| `api_key` | `str` | Yes | — | HeyGen API key |
| `quality` | `str` | Yes | — | Avatar quality: `low`, `medium`, or `high` |
| `agora_uid` | `str` | Yes | — | Agora UID for avatar video stream |
| `avatar_name` | `str` | No | `None` | Avatar name |
| `voice_id` | `str` | No | `None` | Voice ID |
| `language` | `str` | No | `None` | Language code |
| `version` | `str` | No | `None` | API version (`v1` or `v2`) |
| `agora_token` | `str` | No | `None` | RTC token for avatar authentication |
| `avatar_id` | `str` | No | `None` | HeyGen avatar ID |
| `enable` | `bool` | No | `True` | Enable or disable the avatar |
| `disable_idle_timeout` | `bool` | No | `None` | Disable the idle timeout |
| `activity_idle_timeout` | `int` | No | `None` | Idle timeout in seconds (default: 120) |

### `AkoolAvatar`

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.1.0"
version = "1.2.0"
description = ""
readme = "README.md"
authors = []
Expand Down
9 changes: 9 additions & 0 deletions src/agora_agent/agentkit/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,15 @@ def to_properties(
base_kwargs["filler_words"] = self._filler_words

if is_mllm_mode:
if self._mllm is not None:
mllm_config = dict(self._mllm)
if self._greeting:
mllm_config.setdefault("greeting_message", self._greeting)
if self._failure_message:
mllm_config.setdefault("failure_message", self._failure_message)
if self._max_history is not None:
mllm_config.setdefault("max_history", self._max_history)
base_kwargs["mllm"] = mllm_config
return StartAgentsRequestProperties(**base_kwargs)

if self._tts is None:
Expand Down
28 changes: 15 additions & 13 deletions src/agora_agent/agentkit/vendors/avatar.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ class HeyGenAvatarOptions(BaseModel):
api_key: str = Field(..., description="HeyGen API key")
quality: str = Field(..., description="Avatar quality: low, medium, or high")
agora_uid: str = Field(..., description="Agora UID for the avatar stream")
avatar_name: Optional[str] = Field(default=None, description="Avatar name")
voice_id: Optional[str] = Field(default=None, description="Voice ID")
language: Optional[str] = Field(default=None, description="Language code")
version: Optional[str] = Field(default=None, description="API version (v1 or v2)")
agora_token: Optional[str] = Field(default=None, description="RTC token for avatar authentication")
avatar_id: Optional[str] = Field(default=None, description="HeyGen avatar ID")
enable: Optional[bool] = Field(default=None, description="Enable avatar (default: true)")
disable_idle_timeout: Optional[bool] = Field(default=None, description="Whether to disable idle timeout")
activity_idle_timeout: Optional[int] = Field(default=None, description="Idle timeout in seconds")

@field_validator("quality")
@classmethod
Expand Down Expand Up @@ -44,16 +45,17 @@ def to_config(self) -> Dict[str, Any]:
"agora_uid": self.options.agora_uid,
}

if self.options.avatar_name is not None:
params["avatar_name"] = self.options.avatar_name
if self.options.voice_id is not None:
params["voice_id"] = self.options.voice_id
if self.options.language is not None:
params["language"] = self.options.language
if self.options.version is not None:
params["version"] = self.options.version
if self.options.agora_token is not None:
params["agora_token"] = self.options.agora_token
if self.options.avatar_id is not None:
params["avatar_id"] = self.options.avatar_id
if self.options.disable_idle_timeout is not None:
params["disable_idle_timeout"] = self.options.disable_idle_timeout
if self.options.activity_idle_timeout is not None:
params["activity_idle_timeout"] = self.options.activity_idle_timeout

return {"vendor": "heygen", "params": params}
enable = self.options.enable if self.options.enable is not None else True
return {"enable": enable, "vendor": "heygen", "params": params}


class AkoolAvatarOptions(BaseModel):
Expand Down
22 changes: 20 additions & 2 deletions src/agora_agent/agentkit/vendors/mllm.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class OpenAIRealtimeOptions(BaseModel):
output_modalities: Optional[List[str]] = Field(default=None, description="Output modalities")
messages: Optional[List[Dict[str, Any]]] = Field(default=None, description="Conversation messages")
params: Optional[Dict[str, Any]] = Field(default=None, description="Additional parameters")
predefined_tools: Optional[List[str]] = Field(default=None, description="List of predefined tools")
failure_message: Optional[str] = Field(default=None, description="Message played on failure")
max_history: Optional[int] = Field(default=None, description="Maximum conversation history length")

class Config:
extra = "forbid"
Expand Down Expand Up @@ -47,6 +50,12 @@ def to_config(self) -> Dict[str, Any]:
config["output_modalities"] = self.options.output_modalities
if self.options.messages is not None:
config["messages"] = self.options.messages
if self.options.predefined_tools is not None:
config["predefined_tools"] = self.options.predefined_tools
if self.options.failure_message is not None:
config["failure_message"] = self.options.failure_message
if self.options.max_history is not None:
config["max_history"] = self.options.max_history

return config

Expand All @@ -63,6 +72,9 @@ class VertexAIOptions(BaseModel):
output_modalities: Optional[List[str]] = Field(default=None, description="Output modalities")
messages: Optional[List[Dict[str, Any]]] = Field(default=None, description="Conversation messages")
additional_params: Optional[Dict[str, Any]] = Field(default=None, description="Additional parameters")
predefined_tools: Optional[List[str]] = Field(default=None, description="List of predefined tools")
failure_message: Optional[str] = Field(default=None, description="Message played on failure")
max_history: Optional[int] = Field(default=None, description="Maximum conversation history length")

class Config:
extra = "forbid"
Expand All @@ -84,6 +96,8 @@ def to_config(self) -> Dict[str, Any]:
params["instructions"] = self.options.instructions
if self.options.voice is not None:
params["voice"] = self.options.voice
if self.options.messages is not None:
params["messages"] = self.options.messages
if self.options.additional_params is not None:
params.update(self.options.additional_params)

Expand All @@ -99,7 +113,11 @@ def to_config(self) -> Dict[str, Any]:
config["input_modalities"] = self.options.input_modalities
if self.options.output_modalities is not None:
config["output_modalities"] = self.options.output_modalities
if self.options.messages is not None:
config["messages"] = self.options.messages
if self.options.predefined_tools is not None:
config["predefined_tools"] = self.options.predefined_tools
if self.options.failure_message is not None:
config["failure_message"] = self.options.failure_message
if self.options.max_history is not None:
config["max_history"] = self.options.max_history

return config
15 changes: 5 additions & 10 deletions src/agora_agent/agentkit/vendors/stt.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,17 +234,12 @@ def __init__(self, **kwargs: Any):
self.options = AresSTTOptions(**kwargs)

def to_config(self) -> Dict[str, Any]:
params: Dict[str, Any] = {}
config: Dict[str, Any] = {"vendor": "ares"}
if self.options.language is not None:
params["language"] = self.options.language
if self.options.additional_params is not None:
params.update(self.options.additional_params)

return {
"vendor": "ares",
"language": self.options.language,
"params": params,
}
config["language"] = self.options.language
if self.options.additional_params:
config["params"] = self.options.additional_params
return config


class SarvamSTTOptions(BaseModel):
Expand Down
Loading