Skip to content

Commit 0be1014

Browse files
plutolessclaude
andcommitted
refactor: collapse avatar vendor configs; HeyGen copy + SenseTime populate_by_name
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 289e780 commit 0be1014

4 files changed

Lines changed: 122 additions & 129 deletions

File tree

Lines changed: 80 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import warnings
22
from typing import Any, Dict, Optional
33

4-
from pydantic import BaseModel, ConfigDict, Field, field_validator
4+
from pydantic import ConfigDict, Field, field_validator
55

66
from .base import BaseAvatar
77

@@ -10,7 +10,7 @@
1010
AKOOL_SAMPLE_RATE = 16000
1111

1212

13-
class LiveAvatarAvatarOptions(BaseModel):
13+
class LiveAvatarAvatar(BaseAvatar):
1414
model_config = ConfigDict(extra="forbid")
1515

1616
api_key: str = Field(..., description="LiveAvatar API key")
@@ -31,110 +31,115 @@ def validate_quality(cls, v: str) -> str:
3131
raise ValueError(f"Invalid quality '{v}'. Must be one of: {', '.join(valid)}")
3232
return v
3333

34-
35-
class LiveAvatarAvatar(BaseAvatar):
36-
def __init__(self, **kwargs: Any):
37-
self.options = LiveAvatarAvatarOptions(**kwargs)
38-
3934
@property
4035
def required_sample_rate(self) -> int:
4136
return LIVEAVATAR_SAMPLE_RATE
4237

4338
def to_config(self) -> Dict[str, Any]:
4439
params: Dict[str, Any] = {
45-
"api_key": self.options.api_key,
46-
"quality": self.options.quality,
47-
"agora_uid": self.options.agora_uid,
40+
"api_key": self.api_key,
41+
"quality": self.quality,
42+
"agora_uid": self.agora_uid,
4843
}
4944

50-
if self.options.agora_token is not None:
51-
params["agora_token"] = self.options.agora_token
52-
if self.options.avatar_id is not None:
53-
params["avatar_id"] = self.options.avatar_id
54-
if self.options.disable_idle_timeout is not None:
55-
params["disable_idle_timeout"] = self.options.disable_idle_timeout
56-
if self.options.activity_idle_timeout is not None:
57-
params["activity_idle_timeout"] = self.options.activity_idle_timeout
58-
if self.options.additional_params is not None:
59-
params = {**self.options.additional_params, **params}
60-
61-
enable = self.options.enable if self.options.enable is not None else True
45+
if self.agora_token is not None:
46+
params["agora_token"] = self.agora_token
47+
if self.avatar_id is not None:
48+
params["avatar_id"] = self.avatar_id
49+
if self.disable_idle_timeout is not None:
50+
params["disable_idle_timeout"] = self.disable_idle_timeout
51+
if self.activity_idle_timeout is not None:
52+
params["activity_idle_timeout"] = self.activity_idle_timeout
53+
if self.additional_params is not None:
54+
params = {**self.additional_params, **params}
55+
56+
enable = self.enable if self.enable is not None else True
6257
return {"enable": enable, "vendor": "liveavatar", "params": params}
6358

6459

65-
class HeyGenAvatarOptions(LiveAvatarAvatarOptions):
66-
"""Deprecated: use :class:`LiveAvatarAvatarOptions` instead."""
67-
68-
6960
class HeyGenAvatar(BaseAvatar):
7061
"""Deprecated: HeyGen has been renamed to LiveAvatar. Use LiveAvatarAvatar instead."""
7162

72-
def __init__(self, **kwargs: Any):
63+
model_config = ConfigDict(extra="forbid")
64+
65+
api_key: str = Field(..., description="LiveAvatar API key")
66+
quality: str = Field(..., description="Avatar quality: low, medium, or high")
67+
agora_uid: str = Field(..., description="Agora UID for the avatar stream")
68+
agora_token: Optional[str] = Field(default=None, description="RTC token for avatar authentication")
69+
avatar_id: Optional[str] = Field(default=None, description="Avatar ID")
70+
enable: Optional[bool] = Field(default=None, description="Enable avatar (default: true)")
71+
disable_idle_timeout: Optional[bool] = Field(default=None, description="Whether to disable idle timeout")
72+
activity_idle_timeout: Optional[int] = Field(default=None, description="Idle timeout in seconds")
73+
additional_params: Optional[Dict[str, Any]] = Field(default=None, description="Additional vendor-specific parameters")
74+
75+
@field_validator("quality")
76+
@classmethod
77+
def validate_quality(cls, v: str) -> str:
78+
valid = ("low", "medium", "high")
79+
if v not in valid:
80+
raise ValueError(f"Invalid quality '{v}'. Must be one of: {', '.join(valid)}")
81+
return v
82+
83+
def model_post_init(self, __context: Any) -> None:
7384
warnings.warn(
7485
"HeyGenAvatar is deprecated; use LiveAvatarAvatar instead.",
7586
DeprecationWarning,
7687
stacklevel=2,
7788
)
78-
self.options = HeyGenAvatarOptions(**kwargs)
7989

8090
@property
8191
def required_sample_rate(self) -> int:
8292
return HEYGEN_SAMPLE_RATE
8393

8494
def to_config(self) -> Dict[str, Any]:
8595
params: Dict[str, Any] = {
86-
"api_key": self.options.api_key,
87-
"quality": self.options.quality,
88-
"agora_uid": self.options.agora_uid,
96+
"api_key": self.api_key,
97+
"quality": self.quality,
98+
"agora_uid": self.agora_uid,
8999
}
90100

91-
if self.options.agora_token is not None:
92-
params["agora_token"] = self.options.agora_token
93-
if self.options.avatar_id is not None:
94-
params["avatar_id"] = self.options.avatar_id
95-
if self.options.disable_idle_timeout is not None:
96-
params["disable_idle_timeout"] = self.options.disable_idle_timeout
97-
if self.options.activity_idle_timeout is not None:
98-
params["activity_idle_timeout"] = self.options.activity_idle_timeout
99-
if self.options.additional_params is not None:
100-
params = {**self.options.additional_params, **params}
101-
102-
enable = self.options.enable if self.options.enable is not None else True
101+
if self.agora_token is not None:
102+
params["agora_token"] = self.agora_token
103+
if self.avatar_id is not None:
104+
params["avatar_id"] = self.avatar_id
105+
if self.disable_idle_timeout is not None:
106+
params["disable_idle_timeout"] = self.disable_idle_timeout
107+
if self.activity_idle_timeout is not None:
108+
params["activity_idle_timeout"] = self.activity_idle_timeout
109+
if self.additional_params is not None:
110+
params = {**self.additional_params, **params}
111+
112+
enable = self.enable if self.enable is not None else True
103113
return {"enable": enable, "vendor": "heygen", "params": params}
104114

105115

106-
class AkoolAvatarOptions(BaseModel):
116+
class AkoolAvatar(BaseAvatar):
107117
model_config = ConfigDict(extra="forbid")
108118

109119
api_key: str = Field(..., description="Akool API key")
110120
avatar_id: Optional[str] = Field(default=None, description="Avatar ID")
111121
enable: Optional[bool] = Field(default=None, description="Enable avatar (default: true)")
112122
additional_params: Optional[Dict[str, Any]] = Field(default=None, description="Additional vendor-specific parameters")
113123

114-
115-
class AkoolAvatar(BaseAvatar):
116-
def __init__(self, **kwargs: Any):
117-
self.options = AkoolAvatarOptions(**kwargs)
118-
119124
@property
120125
def required_sample_rate(self) -> int:
121126
return AKOOL_SAMPLE_RATE
122127

123128
def to_config(self) -> Dict[str, Any]:
124129
params: Dict[str, Any] = {
125-
"api_key": self.options.api_key,
130+
"api_key": self.api_key,
126131
}
127132

128-
if self.options.avatar_id is not None:
129-
params["avatar_id"] = self.options.avatar_id
130-
if self.options.additional_params is not None:
131-
params = {**self.options.additional_params, **params}
133+
if self.avatar_id is not None:
134+
params["avatar_id"] = self.avatar_id
135+
if self.additional_params is not None:
136+
params = {**self.additional_params, **params}
132137

133-
enable = self.options.enable if self.options.enable is not None else True
138+
enable = self.enable if self.enable is not None else True
134139
return {"enable": enable, "vendor": "akool", "params": params}
135140

136141

137-
class GenericAvatarOptions(BaseModel):
142+
class GenericAvatar(BaseAvatar):
138143
model_config = ConfigDict(extra="forbid")
139144

140145
api_key: str = Field(..., description="Generic avatar provider API key")
@@ -147,61 +152,51 @@ class GenericAvatarOptions(BaseModel):
147152
enable: Optional[bool] = Field(default=None, description="Enable avatar (default: true)")
148153
additional_params: Optional[Dict[str, Any]] = Field(default=None, description="Additional vendor-specific parameters")
149154

150-
151-
class GenericAvatar(BaseAvatar):
152-
def __init__(self, **kwargs: Any):
153-
self.options = GenericAvatarOptions(**kwargs)
154-
155155
@property
156156
def required_sample_rate(self) -> int:
157157
return 0
158158

159159
def to_config(self) -> Dict[str, Any]:
160160
params: Dict[str, Any] = {
161-
"api_key": self.options.api_key,
162-
"api_base_url": self.options.api_base_url,
163-
"avatar_id": self.options.avatar_id,
164-
"agora_uid": self.options.agora_uid,
161+
"api_key": self.api_key,
162+
"api_base_url": self.api_base_url,
163+
"avatar_id": self.avatar_id,
164+
"agora_uid": self.agora_uid,
165165
}
166166

167-
if self.options.agora_appid is not None:
168-
params["agora_appid"] = self.options.agora_appid
169-
if self.options.agora_token is not None:
170-
params["agora_token"] = self.options.agora_token
171-
if self.options.agora_channel is not None:
172-
params["agora_channel"] = self.options.agora_channel
173-
if self.options.additional_params is not None:
174-
params = {**self.options.additional_params, **params}
167+
if self.agora_appid is not None:
168+
params["agora_appid"] = self.agora_appid
169+
if self.agora_token is not None:
170+
params["agora_token"] = self.agora_token
171+
if self.agora_channel is not None:
172+
params["agora_channel"] = self.agora_channel
173+
if self.additional_params is not None:
174+
params = {**self.additional_params, **params}
175175

176-
enable = self.options.enable if self.options.enable is not None else True
176+
enable = self.enable if self.enable is not None else True
177177
return {"enable": enable, "vendor": "generic", "params": params}
178178

179179

180-
class AnamAvatarOptions(BaseModel):
180+
class AnamAvatar(BaseAvatar):
181181
model_config = ConfigDict(extra="forbid")
182182

183183
api_key: str = Field(..., description="Anam API key")
184184
avatar_id: str = Field(..., description="Anam avatar ID")
185185
enable: Optional[bool] = Field(default=None, description="Enable avatar (default: true)")
186186
additional_params: Optional[Dict[str, Any]] = Field(default=None, description="Additional vendor-specific parameters")
187187

188-
189-
class AnamAvatar(BaseAvatar):
190-
def __init__(self, **kwargs: Any):
191-
self.options = AnamAvatarOptions(**kwargs)
192-
193188
@property
194189
def required_sample_rate(self) -> int:
195190
return 0
196191

197192
def to_config(self) -> Dict[str, Any]:
198193
params: Dict[str, Any] = {
199-
"api_key": self.options.api_key,
200-
"avatar_id": self.options.avatar_id,
194+
"api_key": self.api_key,
195+
"avatar_id": self.avatar_id,
201196
}
202197

203-
if self.options.additional_params is not None:
204-
params = {**self.options.additional_params, **params}
198+
if self.additional_params is not None:
199+
params = {**self.additional_params, **params}
205200

206-
enable = self.options.enable if self.options.enable is not None else True
201+
enable = self.enable if self.enable is not None else True
207202
return {"enable": enable, "vendor": "anam", "params": params}

src/agora_agent/agentkit/vendors/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def to_config(self) -> Dict[str, Any]:
7676
"""Serialize the MLLM configuration to a dict for the REST API."""
7777

7878

79-
class BaseAvatar(ABC):
79+
class BaseAvatar(BaseModel, ABC):
8080
"""Abstract base class for all avatar vendor implementations.
8181
8282
Avatars render a visual representation of the agent and impose a specific TTS

src/agora_agent/agentkit/vendors/cn.py

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Any, Dict, List, Optional
44

5-
from pydantic import BaseModel, ConfigDict, Field, model_validator
5+
from pydantic import ConfigDict, Field, model_validator
66

77
from .avatar import BaseAvatar
88
from .base import BaseLLM
@@ -788,8 +788,8 @@ def to_config(self) -> Dict[str, Any]:
788788
return config
789789

790790

791-
class SenseTimeAvatarOptions(BaseModel):
792-
model_config = ConfigDict(extra="forbid")
791+
class SenseTimeAvatar(BaseAvatar):
792+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
793793

794794
agora_token: Optional[str] = Field(default=None, description="RTC token for avatar publisher; generated by AgentSession when omitted")
795795
agora_uid: str = Field(..., description="Avatar RTC publisher uid")
@@ -799,34 +799,29 @@ class SenseTimeAvatarOptions(BaseModel):
799799
enable: Optional[bool] = Field(default=None)
800800
additional_params: Optional[Dict[str, Any]] = Field(default=None)
801801

802-
803-
class SenseTimeAvatar(BaseAvatar):
804-
def __init__(self, **kwargs: Any):
805-
self.options = SenseTimeAvatarOptions(**kwargs)
806-
807802
@property
808803
def required_sample_rate(self) -> int:
809804
return 0
810805

811806
def to_config(self) -> Dict[str, Any]:
812807
params: Dict[str, Any] = {
813-
"agora_uid": self.options.agora_uid,
814-
"app_key": self.options.app_key,
808+
"agora_uid": self.agora_uid,
809+
"app_key": self.app_key,
815810
}
816-
if self.options.agora_token is not None:
817-
params["agora_token"] = self.options.agora_token
818-
if self.options.app_id is not None:
819-
params["appId"] = self.options.app_id
820-
if self.options.scene_list is not None:
821-
params["sceneList"] = self.options.scene_list
822-
if self.options.additional_params is not None:
823-
params = {**self.options.additional_params, **params}
824-
825-
enable = self.options.enable if self.options.enable is not None else True
811+
if self.agora_token is not None:
812+
params["agora_token"] = self.agora_token
813+
if self.app_id is not None:
814+
params["appId"] = self.app_id
815+
if self.scene_list is not None:
816+
params["sceneList"] = self.scene_list
817+
if self.additional_params is not None:
818+
params = {**self.additional_params, **params}
819+
820+
enable = self.enable if self.enable is not None else True
826821
return {"enable": enable, "vendor": "sensetime", "params": params}
827822

828823

829-
class SpatiusAvatarOptions(BaseModel):
824+
class SpatiusAvatar(BaseAvatar):
830825
model_config = ConfigDict(extra="forbid")
831826

832827
spatius_api_key: str = Field(..., description="Spatius API key")
@@ -840,32 +835,27 @@ class SpatiusAvatarOptions(BaseModel):
840835
enable: Optional[bool] = Field(default=None)
841836
additional_params: Optional[Dict[str, Any]] = Field(default=None)
842837

843-
844-
class SpatiusAvatar(BaseAvatar):
845-
def __init__(self, **kwargs: Any):
846-
self.options = SpatiusAvatarOptions(**kwargs)
847-
848838
@property
849839
def required_sample_rate(self) -> int:
850-
return self.options.sample_rate or 0
840+
return self.sample_rate or 0
851841

852842
def to_config(self) -> Dict[str, Any]:
853843
params: Dict[str, Any] = {
854-
"spatius_api_key": self.options.spatius_api_key,
855-
"spatius_app_id": self.options.spatius_app_id,
856-
"spatius_avatar_id": self.options.spatius_avatar_id,
857-
"agora_uid": self.options.agora_uid,
844+
"spatius_api_key": self.spatius_api_key,
845+
"spatius_app_id": self.spatius_app_id,
846+
"spatius_avatar_id": self.spatius_avatar_id,
847+
"agora_uid": self.agora_uid,
858848
}
859-
if self.options.agora_token is not None:
860-
params["agora_token"] = self.options.agora_token
861-
if self.options.region is not None:
862-
params["region"] = self.options.region
863-
if self.options.sample_rate is not None:
864-
params["sample_rate"] = self.options.sample_rate
865-
if self.options.session_expire_minutes is not None:
866-
params["session_expire_minutes"] = self.options.session_expire_minutes
867-
if self.options.additional_params is not None:
868-
params = {**self.options.additional_params, **params}
869-
870-
enable = self.options.enable if self.options.enable is not None else True
849+
if self.agora_token is not None:
850+
params["agora_token"] = self.agora_token
851+
if self.region is not None:
852+
params["region"] = self.region
853+
if self.sample_rate is not None:
854+
params["sample_rate"] = self.sample_rate
855+
if self.session_expire_minutes is not None:
856+
params["session_expire_minutes"] = self.session_expire_minutes
857+
if self.additional_params is not None:
858+
params = {**self.additional_params, **params}
859+
860+
enable = self.enable if self.enable is not None else True
871861
return {"enable": enable, "vendor": "spatius", "params": params}

0 commit comments

Comments
 (0)