Skip to content

Commit 87585c5

Browse files
aligned vendor structs with expected params.keys names
1 parent bbed4e2 commit 87585c5

5 files changed

Lines changed: 105 additions & 11 deletions

File tree

src/agora_agent/agentkit/presets.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ def strip_inferred_preset_fields(properties: typing.Dict[str, typing.Any], infer
187187
params["url"] = None
188188
tts = {k: v for k, v in {**tts, "params": _omit_none(params)}.items() if v is not None}
189189
tts.pop("_minimax_preset_model", None)
190+
if tts and "_minimax_preset_model" in tts:
191+
tts = {k: v for k, v in tts.items() if k != "_minimax_preset_model"}
190192

191193
return {**properties, "asr": asr, "llm": llm, "tts": tts}
192194

src/agora_agent/agentkit/vendors/llm.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -382,12 +382,7 @@ def to_config(self) -> Dict[str, Any]:
382382
f"{self.options.project_id}/locations/{self.options.location}/"
383383
f"publishers/google/models/{self.options.model}:streamGenerateContent?alt=sse"
384384
)
385-
config = Gemini(**options).to_config()
386-
params = dict(config.get("params") or {})
387-
params["project_id"] = self.options.project_id
388-
params["location"] = self.options.location
389-
config["params"] = params
390-
return config
385+
return Gemini(**options).to_config()
391386

392387

393388
class AmazonBedrockOptions(BaseModel):

src/agora_agent/agentkit/vendors/tts.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,13 @@ def sample_rate(self) -> Optional[int]:
214214
def to_config(self) -> Dict[str, Any]:
215215
params: Dict[str, Any] = {
216216
"credentials": self.options.key,
217-
"voice_selection_params": {"name": self.options.voice_name},
217+
"VoiceSelectionParams": {"name": self.options.voice_name},
218218
}
219219

220220
if self.options.language_code is not None:
221-
params["voice_selection_params"]["language_code"] = self.options.language_code
221+
params["VoiceSelectionParams"]["language_code"] = self.options.language_code
222222
if self.options.sample_rate_hertz is not None:
223-
params["audio_config"] = {"sample_rate_hertz": self.options.sample_rate_hertz}
223+
params["AudioConfig"] = {"sample_rate_hertz": self.options.sample_rate_hertz}
224224

225225
result: Dict[str, Any] = {"vendor": "google", "params": params}
226226
if self.options.skip_patterns is not None:
@@ -359,7 +359,7 @@ def to_config(self) -> Dict[str, Any]:
359359
params: Dict[str, Any] = {
360360
"api_key": self.options.key,
361361
"speaker": self.options.speaker,
362-
"model_id": self.options.model_id,
362+
"modelId": self.options.model_id,
363363
}
364364
if self.options.base_url is not None:
365365
params["base_url"] = self.options.base_url

tests/custom/test_request_body.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,49 @@ def test_7c_pipeline_id_empty_properties_no_vendors() -> None:
464464
assert "tts" not in properties
465465

466466

467+
def test_7d_pipeline_id_with_byok_tts_only() -> None:
468+
"""7d: pipeline_id present, TTS-only BYOK override — ASR and LLM absent from properties."""
469+
agent = Agent(name="support", pipeline_id="studio-pipeline").with_tts(
470+
ElevenLabsTTS(
471+
key="el-key",
472+
model_id="eleven_flash_v2_5",
473+
voice_id="some-voice",
474+
base_url="wss://api.elevenlabs.io/v1",
475+
)
476+
)
477+
478+
call = start_session(agent)
479+
assert call["pipeline_id"] == "studio-pipeline"
480+
properties = dump(call["properties"])
481+
assert "asr" not in properties
482+
assert "llm" not in properties
483+
assert properties["tts"]["vendor"] == "elevenlabs"
484+
assert properties["tts"]["params"]["key"] == "el-key"
485+
486+
487+
def test_7e_pipeline_id_with_byok_asr_and_tts() -> None:
488+
"""7e: pipeline_id present, ASR+TTS BYOK overrides — LLM absent from properties."""
489+
agent = (
490+
Agent(name="support", pipeline_id="studio-pipeline")
491+
.with_stt(DeepgramSTT(api_key="dg-key", language="en"))
492+
.with_tts(
493+
ElevenLabsTTS(
494+
key="el-key",
495+
model_id="eleven_flash_v2_5",
496+
voice_id="some-voice",
497+
base_url="wss://api.elevenlabs.io/v1",
498+
)
499+
)
500+
)
501+
502+
call = start_session(agent)
503+
assert call["pipeline_id"] == "studio-pipeline"
504+
properties = dump(call["properties"])
505+
assert "llm" not in properties
506+
assert properties["asr"]["vendor"] == "deepgram"
507+
assert properties["tts"]["vendor"] == "elevenlabs"
508+
509+
467510
# ===========================================================================
468511
# Scenario 8 — MLLM mode
469512
# ===========================================================================
@@ -872,10 +915,11 @@ def test_byok_sarvam_tts_params() -> None:
872915

873916

874917
def test_byok_murf_tts_params() -> None:
875-
agent = Agent(name="t").with_tts(MurfTTS(key="murf-key"))
918+
agent = Agent(name="t").with_tts(MurfTTS(key="murf-key", voice_id="Ariana"))
876919
props = build_properties(agent, allow_missing={"asr", "llm"})
877920
assert props["tts"]["vendor"] == "murf"
878921
assert props["tts"]["params"]["api_key"] == "murf-key"
922+
assert props["tts"]["params"]["voiceId"] == "Ariana"
879923

880924

881925
# ---------------------------------------------------------------------------
@@ -965,3 +1009,15 @@ def test_preset_minimax_speech_2_8_turbo_inferred() -> None:
9651009
def test_preset_minimax_speech_2_6_turbo_inferred() -> None:
9661010
preset, properties = resolve_session_presets(None, {"tts": MiniMaxTTS(model="speech-2.6-turbo", voice_id="voice").to_config()})
9671011
assert preset == "minimax_speech_2_6_turbo"
1012+
1013+
1014+
def test_explicit_minimax_preset_strips_internal_hint() -> None:
1015+
"""Explicit MiniMax TTS preset must not leak _minimax_preset_model to the wire."""
1016+
# When the caller supplies the preset explicitly, inference is skipped but the
1017+
# internal _minimax_preset_model hint set by MiniMaxTTS.to_config() must still
1018+
# be removed before the POST body is sent.
1019+
tts_config = MiniMaxTTS(model="speech_2_8_turbo", voice_id="voice").to_config()
1020+
assert "_minimax_preset_model" in tts_config # confirm the hint is set pre-strip
1021+
1022+
_, properties = resolve_session_presets("minimax_speech_2_8_turbo", {"tts": tts_config})
1023+
assert "_minimax_preset_model" not in properties["tts"]

tests/custom/test_tts_vendors.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from agora_agent import AmazonTTS, CartesiaTTS, DeepgramTTS, ElevenLabsTTS, FishAudioTTS, GoogleTTS, HumeAITTS, MiniMaxTTS, MurfTTS, OpenAITTS, RimeTTS, SarvamTTS
4+
from agora_agent.agents.types.start_agents_request_properties import StartAgentsRequestProperties
45

56

67
def test_tts_vendor_params_match_generated_core_shapes() -> None:
@@ -116,3 +117,43 @@ def test_tts_managed_mode_validation_matches_core_shapes() -> None:
116117

117118
with pytest.raises(Exception, match="MiniMaxTTS requires key unless using a supported Agora-managed model"):
118119
MiniMaxTTS(model="unsupported-model")
120+
121+
122+
def test_tts_wire_serialization_applies_fern_aliases() -> None:
123+
"""Verify alias-sensitive TTS params reach the wire with the correct Fern aliases.
124+
125+
The intermediate to_config() / build_properties() helpers return snake_case
126+
field names. The real POST body goes through StartAgentsRequestProperties →
127+
.dict(by_alias=True) → convert_and_respect_annotation_metadata(direction='write'),
128+
which is what jsonable_encoder calls in the live HTTP client. These tests
129+
exercise that full chain so a Fern alias regression would be caught.
130+
"""
131+
_BASE = dict(channel="ch", token="tok", agent_rtc_uid="1", remote_rtc_uids=["100"])
132+
133+
# Google TTS: voice_selection_params and audio_config must arrive as PascalCase aliases
134+
google_config = GoogleTTS(
135+
key="{}", voice_name="en-US-JennyNeural", language_code="en-US", sample_rate_hertz=24000
136+
).to_config()
137+
assert "voice_selection_params" in google_config["params"] # pre-condition: to_config emits snake_case
138+
google_wire = StartAgentsRequestProperties(**_BASE, tts=google_config).dict(by_alias=True)
139+
google_params = google_wire["tts"]["params"]
140+
assert "VoiceSelectionParams" in google_params, f"wire missing VoiceSelectionParams, got: {list(google_params)}"
141+
assert "voice_selection_params" not in google_params
142+
assert "AudioConfig" in google_params
143+
assert "audio_config" not in google_params
144+
145+
# Rime TTS: model_id must arrive as modelId alias
146+
rime_config = RimeTTS(key="rime-key", speaker="speaker", model_id="mist").to_config()
147+
assert "model_id" in rime_config["params"] # pre-condition: to_config emits snake_case
148+
rime_wire = StartAgentsRequestProperties(**_BASE, tts=rime_config).dict(by_alias=True)
149+
rime_params = rime_wire["tts"]["params"]
150+
assert "modelId" in rime_params, f"wire missing modelId, got: {list(rime_params)}"
151+
assert "model_id" not in rime_params
152+
153+
# Murf TTS: voiceId (emitted by to_config as alias) must survive through wire serialization
154+
murf_config = MurfTTS(key="murf-key", voice_id="Ariana").to_config()
155+
assert "voiceId" in murf_config["params"] # to_config currently emits alias directly
156+
murf_wire = StartAgentsRequestProperties(**_BASE, tts=murf_config).dict(by_alias=True)
157+
murf_params = murf_wire["tts"]["params"]
158+
assert "voiceId" in murf_params, f"wire missing voiceId, got: {list(murf_params)}"
159+
assert murf_params["voiceId"] == "Ariana"

0 commit comments

Comments
 (0)