Skip to content

Commit 33f9229

Browse files
committed
[fern-replay] Applied customizations
Patches applied (5): - patch-64703bda: test(agentkit): add custom tests for v1.5.0 AgentKit behavior - patch-7c2d9d99: feat(agentkit): align session options and token uid handling - patch-7465fada: fix(agentkit): resolve Python session typing issues - patch-fae1249a: Re-export agora-agents API from legacy PyPI compatibility package The compat distribution delegates to agora_agent via __getattr__ and documents both import paths in its README. - patch-44c21c14: Re-export AgentKit symbols from agora_agent package root Extend __getattr__ and __all__ so vendor classes, presets, and helpers are importable via `from agora_agent import ...`. Add tests and update class docstring examples to use the root import path. Patches with unresolved conflicts (17): - patch-6e30398b: chore(agentkit): bump to v1.5.0 and expose v2.7 type aliases - patch-9df782b4: feat(agentkit): update MLLM and LLM vendor wrappers for v2.7 - patch-26706d73: feat(agentkit): add GenericAvatar and session-aware avatar validation - patch-9f491c63: feat(agentkit): update Agent builder and session lifecycle for v2.7 - patch-6c20f076: docs(agentkit): update v1.5.0 guides, reference, and changelog - patch-eaec58eb: refactor(agentkit): align deprecated vendor aliases with canonical names - patch-20245632: feat(agentkit): export type aliases and avatar token helpers - patch-972dd5bd: updated docs - patch-4323b470: rename python package to agora-agents - patch-d29165c4: make python compat package publishable - patch-fc9d93c3: Document agora-agents PyPI install name and migration notes - patch-87fc4488: Update docs to import from agora_agent package root - patch-923cf954: Prioritize app credentials and builder in Python docs Rewrite getting-started auth and quick-start for app credentials with the builder API. De-emphasize presets and align index, BYOK, and README with the recommended onboarding path. - patch-d475306b: Move package rename guidance to installation docs and protect manual paths in Fern ignore. Consolidate migration notes into the installation guide with next-step links, add a brief README pointer, and exclude README, compat, and workflow files from Fern generation. - patch-c9355576: Streamline Python docs and README for app-credentials-first onboarding. Remove duplicated low-level client examples from the README, de-emphasize legacy auth modes, refocus the low-level API guide on AgentKit with telephony escape hatches, and update Agora-managed model terminology. - patch-98ecb4d3: Add Groq, Vertex AI, Bedrock, Dify, and Custom LLM vendor helpers. Introduce named LLM vendor classes with correct request serialization, export them from the package root, and add tests covering each provider's config shape. - patch-a5097b8d: Document new LLM vendors and tighten onboarding docs. Add Groq, Vertex AI, Bedrock, Dify, and Custom LLM to vendor references, simplify README and index navigation, and align quick-start and terminology with Agora-managed model language. Run `fern-replay resolve` to apply these customizations. Patches absorbed by generator (3): - patch-b7f0c36c: feat(agentkit): release v2.0.0 updates - patch-4d32368c: Add compat-build CI job and harden dual-package PyPI publish Build and verify the compat wheel re-exports, gate publish on compat-build, simplify version checks with poetry version, wait for primary package on PyPI, and retry compat publish on failure. - patch-20109390: Fix PyPI publish auth and explicitly protect release workflow in Fern ignore. Use PYPI_API_TOKEN for primary and compat Poetry publishes, matching the v1.4.1 release flow, and list release.yml explicitly in .fernignore. The generator now produces these customizations natively.
1 parent 403a1a9 commit 33f9229

11 files changed

Lines changed: 13037 additions & 2 deletions

File tree

.fern/replay.lock

Lines changed: 12086 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/agora_agent/agentkit/agent.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
from ..agents.types.start_agents_request_properties_filler_words_content_static_config import StartAgentsRequestPropertiesFillerWordsContentStaticConfig
6868
from ..agents.types.start_agents_request_properties_filler_words_content_static_config_selection_rule import StartAgentsRequestPropertiesFillerWordsContentStaticConfigSelectionRule
6969
from ..types.tts import Tts
70+
from ..agents.types.start_agents_request_properties_filler_words_content_static_config_selection_rule import StartAgentsRequestPropertiesFillerWordsContentStaticConfigSelectionRule
71+
from ..types.tts import Tts
7072
from ..agent_management.types.agent_think_agent_management_request_on_listening_action import (
7173
AgentThinkAgentManagementRequestOnListeningAction,
7274
)

src/agora_agent/agentkit/agent_session.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
is_generic_avatar,
2525
is_heygen_avatar,
2626
is_live_avatar_avatar,
27+
is_rtc_avatar,
2728
validate_avatar_config,
2829
validate_tts_sample_rate,
2930
)

src/agora_agent/agentkit/vendors/avatar.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,49 @@ def to_config(self) -> Dict[str, Any]:
177177
return {"enable": enable, "vendor": "generic", "params": params}
178178

179179

180+
class GenericAvatarOptions(BaseModel):
181+
model_config = ConfigDict(extra="forbid")
182+
183+
api_key: str = Field(..., description="Generic avatar provider API key")
184+
api_base_url: str = Field(..., description="Avatar provider API base URL")
185+
avatar_id: str = Field(..., description="Avatar ID")
186+
agora_uid: str = Field(..., description="Agora UID for the avatar video stream")
187+
agora_appid: Optional[str] = Field(default=None, description="Agora App ID; filled by AgentSession when omitted")
188+
agora_token: Optional[str] = Field(default=None, description="RTC token; generated by AgentSession when omitted")
189+
agora_channel: Optional[str] = Field(default=None, description="Agora channel; filled by AgentSession when omitted")
190+
enable: Optional[bool] = Field(default=None, description="Enable avatar (default: true)")
191+
additional_params: Optional[Dict[str, Any]] = Field(default=None, description="Additional vendor-specific parameters")
192+
193+
194+
class GenericAvatar(BaseAvatar):
195+
def __init__(self, **kwargs: Any):
196+
self.options = GenericAvatarOptions(**kwargs)
197+
198+
@property
199+
def required_sample_rate(self) -> int:
200+
return 0
201+
202+
def to_config(self) -> Dict[str, Any]:
203+
params: Dict[str, Any] = {
204+
"api_key": self.options.api_key,
205+
"api_base_url": self.options.api_base_url,
206+
"avatar_id": self.options.avatar_id,
207+
"agora_uid": self.options.agora_uid,
208+
}
209+
210+
if self.options.agora_appid is not None:
211+
params["agora_appid"] = self.options.agora_appid
212+
if self.options.agora_token is not None:
213+
params["agora_token"] = self.options.agora_token
214+
if self.options.agora_channel is not None:
215+
params["agora_channel"] = self.options.agora_channel
216+
if self.options.additional_params is not None:
217+
params = {**self.options.additional_params, **params}
218+
219+
enable = self.options.enable if self.options.enable is not None else True
220+
return {"enable": enable, "vendor": "generic", "params": params}
221+
222+
180223
class AnamAvatarOptions(BaseModel):
181224
model_config = ConfigDict(extra="forbid")
182225

src/agora_agent/agentkit/vendors/mllm.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import warnings
12
from typing import Any, Dict, List, Optional
23

34
from pydantic import BaseModel, ConfigDict, Field
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
from agora_agent.agentkit import (
2+
Agent,
3+
AvatarConfig,
4+
AvatarVendor,
5+
LlmConfig,
6+
LlmStyle,
7+
MllmConfig,
8+
MllmVendor,
9+
SttConfig,
10+
SttVendor,
11+
TtsConfig,
12+
)
13+
import pytest
14+
15+
from agora_agent.agentkit.vendors import (
16+
AkoolAvatar,
17+
ElevenLabsTTS,
18+
LiveAvatarAvatar,
19+
OpenAI,
20+
OpenAIRealtime,
21+
)
22+
23+
24+
def _parameter(config, key):
25+
parameters = config["parameters"]
26+
if isinstance(parameters, dict):
27+
return parameters[key]
28+
return getattr(parameters, key)
29+
30+
31+
class _CopyOnlyModel:
32+
def __init__(self, **values):
33+
self.values = values
34+
35+
def copy(self, update=None):
36+
return _CopyOnlyModel(**{**self.values, **(update or {})})
37+
38+
39+
def test_generated_core_aliases_are_public():
40+
assert LlmConfig is not None
41+
assert LlmStyle is not None
42+
assert SttConfig is not None
43+
assert SttVendor is not None
44+
assert TtsConfig is not None
45+
assert MllmConfig is not None
46+
assert MllmVendor is not None
47+
assert AvatarConfig is not None
48+
assert AvatarVendor is not None
49+
50+
51+
def test_model_copy_helper_supports_pydantic_v1_copy_api():
52+
copied = Agent._copy_model_update(_CopyOnlyModel(enable_rtm=True), {"data_channel": "rtm"}) # noqa: SLF001
53+
54+
assert copied.values == {"enable_rtm": True, "data_channel": "rtm"}
55+
56+
57+
def test_with_audio_scenario_sets_session_parameter():
58+
agent = Agent(name="test").with_audio_scenario("chorus")
59+
60+
assert _parameter(agent.config, "audio_scenario") == "chorus"
61+
62+
63+
def test_with_audio_scenario_preserves_existing_parameters():
64+
agent = Agent(name="test", parameters={"enable_metrics": True}).with_audio_scenario(
65+
"chorus"
66+
)
67+
68+
assert _parameter(agent.config, "enable_metrics") is True
69+
assert _parameter(agent.config, "audio_scenario") == "chorus"
70+
71+
72+
def test_enable_rtm_defaults_data_channel_to_rtm():
73+
properties = Agent(name="test", advanced_features={"enable_rtm": True}).to_properties(
74+
channel="room",
75+
agent_uid="1",
76+
remote_uids=["100"],
77+
token="token",
78+
skip_vendor_validation=True,
79+
)
80+
81+
assert properties.parameters.data_channel == "rtm"
82+
83+
84+
def test_enable_rtm_preserves_explicit_data_channel():
85+
properties = Agent(
86+
name="test",
87+
advanced_features={"enable_rtm": True},
88+
parameters={"data_channel": "datastream"},
89+
).to_properties(
90+
channel="room",
91+
agent_uid="1",
92+
remote_uids=["100"],
93+
token="token",
94+
skip_vendor_validation=True,
95+
)
96+
97+
assert properties.parameters.data_channel == "datastream"
98+
99+
100+
def test_agent_level_llm_fields_override_vendor_defaults():
101+
agent = (
102+
Agent(name="test")
103+
.with_llm(
104+
OpenAI(
105+
api_key="llm-key",
106+
greeting_message="vendor greeting",
107+
failure_message="vendor failure",
108+
max_history=1,
109+
)
110+
)
111+
.with_tts(ElevenLabsTTS(key="tts-key", model_id="model", voice_id="voice"))
112+
.with_greeting("agent greeting")
113+
.with_failure_message("agent failure")
114+
.with_max_history(2)
115+
)
116+
117+
properties = agent.to_properties(
118+
channel="room",
119+
agent_uid="1",
120+
remote_uids=["100"],
121+
token="token",
122+
)
123+
124+
assert properties.llm.greeting_message == "agent greeting"
125+
assert properties.llm.failure_message == "agent failure"
126+
assert properties.llm.max_history == 2
127+
128+
129+
def test_avatar_sample_rate_validation_works_when_tts_added_after_avatar():
130+
agent = Agent(name="test").with_avatar(
131+
LiveAvatarAvatar(api_key="avatar-key", quality="medium", agora_uid="2")
132+
)
133+
134+
with pytest.raises(ValueError, match="24000"):
135+
agent.with_tts(
136+
ElevenLabsTTS(key="tts-key", model_id="model", voice_id="voice", sample_rate=16000)
137+
)
138+
139+
140+
def test_avatar_sample_rate_validation_uses_wrapper_sample_rate():
141+
agent = (
142+
Agent(name="test")
143+
.with_avatar(AkoolAvatar(api_key="avatar-key"))
144+
.with_tts(
145+
ElevenLabsTTS(key="tts-key", model_id="model", voice_id="voice", sample_rate=16000)
146+
)
147+
)
148+
149+
assert agent.tts_sample_rate == 16000
150+
151+
152+
def test_with_mllm_removes_deprecated_advanced_features_enable_mllm():
153+
properties = (
154+
Agent(
155+
name="test",
156+
advanced_features={"enable_mllm": True, "enable_rtm": True},
157+
greeting="hello from agent",
158+
failure_message="try again",
159+
max_history=5,
160+
)
161+
.with_mllm(OpenAIRealtime(api_key="openai-key"))
162+
.to_properties(
163+
channel="room",
164+
agent_uid="1",
165+
remote_uids=["100"],
166+
token="rtc-token",
167+
)
168+
)
169+
170+
assert properties.mllm is not None
171+
assert properties.mllm.enable is True
172+
assert properties.mllm.greeting_message == "hello from agent"
173+
assert properties.mllm.failure_message == "try again"
174+
mllm_dump = properties.mllm.model_dump(exclude_none=True)
175+
assert "max_history" not in mllm_dump
176+
assert properties.advanced_features is not None
177+
af_dump = properties.advanced_features.model_dump(exclude_none=True)
178+
assert "enable_mllm" not in af_dump
179+
assert af_dump.get("enable_rtm") is True
180+
181+
182+
def test_to_properties_rejects_mllm_with_enabled_avatar():
183+
agent = (
184+
Agent(name="test")
185+
.with_mllm(OpenAIRealtime(api_key="mllm-key"))
186+
.with_avatar(
187+
LiveAvatarAvatar(
188+
api_key="avatar-key",
189+
quality="medium",
190+
agora_uid="2",
191+
agora_token="avatar-token",
192+
)
193+
)
194+
)
195+
196+
with pytest.raises(ValueError, match="cascading"):
197+
agent.to_properties(
198+
channel="room",
199+
agent_uid="1",
200+
remote_uids=["100"],
201+
token="rtc-token",
202+
)
203+
204+
205+
def test_to_properties_mllm_with_avatar_fires_before_token_generation():
206+
"""The guard must fire before the token-generation step so callers get a
207+
clear, actionable error even when app_id/app_certificate are empty.
208+
"""
209+
agent = (
210+
Agent(name="test")
211+
.with_mllm(OpenAIRealtime(api_key="mllm-key"))
212+
.with_avatar(
213+
LiveAvatarAvatar(
214+
api_key="avatar-key",
215+
quality="medium",
216+
agora_uid="2",
217+
agora_token="avatar-token",
218+
)
219+
)
220+
)
221+
222+
with pytest.raises(ValueError, match="cascading"):
223+
agent.to_properties(
224+
channel="room",
225+
agent_uid="1",
226+
remote_uids=["100"],
227+
app_id="",
228+
app_certificate="",
229+
)
230+
231+
232+
def test_to_properties_rejects_mllm_with_default_enabled_avatar():
233+
"""Avatar with no `enable` field should be treated as enabled."""
234+
agent = Agent(name="test").with_mllm(OpenAIRealtime(api_key="mllm-key"))
235+
agent._avatar = { # noqa: SLF001
236+
"vendor": "liveavatar",
237+
"params": {
238+
"api_key": "avatar-key",
239+
"quality": "high",
240+
"agora_uid": "200",
241+
"agora_token": "avatar-token",
242+
},
243+
}
244+
245+
with pytest.raises(ValueError, match="cascading"):
246+
agent.to_properties(
247+
channel="room",
248+
agent_uid="1",
249+
remote_uids=["100"],
250+
token="rtc-token",
251+
)
252+
253+
254+
def test_to_properties_allows_mllm_with_disabled_avatar_and_no_tts():
255+
properties = (
256+
Agent(name="test")
257+
.with_mllm(OpenAIRealtime(api_key="mllm-key"))
258+
.with_avatar(
259+
LiveAvatarAvatar(
260+
api_key="avatar-key",
261+
quality="medium",
262+
agora_uid="2",
263+
agora_token="avatar-token",
264+
enable=False,
265+
)
266+
)
267+
.to_properties(
268+
channel="room",
269+
agent_uid="1",
270+
remote_uids=["100"],
271+
token="rtc-token",
272+
)
273+
)
274+
275+
assert properties.mllm is not None and properties.mllm.enable is True
276+
assert properties.tts is None
277+
assert properties.llm is None
278+
assert properties.asr is None
279+
assert properties.avatar is not None and properties.avatar.enable is False
280+
281+
282+
def test_to_properties_mllm_without_tts_or_llm_succeeds():
283+
properties = (
284+
Agent(name="test")
285+
.with_mllm(OpenAIRealtime(api_key="mllm-key"))
286+
.to_properties(
287+
channel="room",
288+
agent_uid="1",
289+
remote_uids=["100"],
290+
token="rtc-token",
291+
)
292+
)
293+
294+
assert properties.mllm is not None and properties.mllm.enable is True
295+
assert properties.tts is None
296+
assert properties.llm is None
297+
assert properties.asr is None
298+
assert properties.avatar is None

0 commit comments

Comments
 (0)