From d2d81cc7ea697b8229ee443f25669da841a24a64 Mon Sep 17 00:00:00 2001 From: Octopatch Date: Sat, 17 Oct 2026 20:35:59 +0800 Subject: [PATCH] Add MiniMax voice design support --- sdk/python/agentfield/media_providers.py | 58 ++++++++++++++ sdk/python/tests/test_minimax_voice_design.py | 78 +++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 sdk/python/tests/test_minimax_voice_design.py diff --git a/sdk/python/agentfield/media_providers.py b/sdk/python/agentfield/media_providers.py index 1f12961b8..625831e95 100644 --- a/sdk/python/agentfield/media_providers.py +++ b/sdk/python/agentfield/media_providers.py @@ -1144,6 +1144,64 @@ async def clone_voice( raw_response={"upload": upload_data, "clone": clone_data}, ) + async def design_voice( + self, + prompt: str, + voice_id: str, + model: Optional[str] = None, + **kwargs, + ) -> MultimodalResponse: + """Create a voice from a natural-language description.""" + import os + + import aiohttp + + api_key = self._api_key or os.environ.get("MINIMAX_API_KEY") + if not api_key: + raise ValueError( + "MiniMax API key required. Set MINIMAX_API_KEY or pass api_key " + "to MiniMaxProvider." + ) + if not isinstance(prompt, str) or not prompt.strip(): + raise ValueError("MiniMax voice design requires a prompt") + if not isinstance(voice_id, str) or not voice_id.strip(): + raise ValueError("MiniMax voice design requires a voice_id") + + body: Dict[str, Any] = {"prompt": prompt, "voice_id": voice_id} + if model is not None: + send_model = self._strip_prefix(model).strip() + if not send_model: + raise ValueError("MiniMax voice design requires a model") + body["model"] = send_model + body.update(kwargs) + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + } + timeout = aiohttp.ClientTimeout(total=120.0) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.post( + f"{self._base_url}/voice_design", + headers=headers, + json=body, + ) as response: + data = await self._read_response( + response, "design", service="voice design" + ) + + designed_voice_id = data.get("voice_id") + if not designed_voice_id and isinstance(data.get("data"), dict): + designed_voice_id = data["data"].get("voice_id") + if not isinstance(designed_voice_id, str) or not designed_voice_id: + raise RuntimeError("MiniMax voice design returned no voice_id") + return MultimodalResponse( + text=designed_voice_id, + audio=None, + images=[], + files=[], + raw_response=data, + ) + async def generate_music( self, prompt: str, diff --git a/sdk/python/tests/test_minimax_voice_design.py b/sdk/python/tests/test_minimax_voice_design.py new file mode 100644 index 000000000..c379b53b2 --- /dev/null +++ b/sdk/python/tests/test_minimax_voice_design.py @@ -0,0 +1,78 @@ +import pytest + +from agentfield.media_providers import MINIMAX_CN_BASE_URL, MINIMAX_GLOBAL_BASE_URL, MiniMaxProvider + + +class FakeResponse: + def __init__(self, payload, status=200): + self.payload = payload + self.status = status + + async def json(self): + return self.payload + + async def text(self): + return str(self.payload) + + async def __aenter__(self): + return self + + async def __aexit__(self, *args): + return False + + +class CaptureSession: + def __init__(self, response): + self.response = response + self.calls = [] + + def post(self, url, **kwargs): + self.calls.append((url, kwargs)) + return self.response + + async def __aenter__(self): + return self + + async def __aexit__(self, *args): + return False + + +@pytest.mark.asyncio +@pytest.mark.parametrize("base_url", [MINIMAX_GLOBAL_BASE_URL, MINIMAX_CN_BASE_URL]) +async def test_design_voice_posts_prompt_and_voice_id(monkeypatch, base_url): + session = CaptureSession( + FakeResponse({"voice_id": "designed-voice", "base_resp": {"status_code": 0}}) + ) + monkeypatch.setattr("aiohttp.ClientSession", lambda **kwargs: session) + + result = await MiniMaxProvider(api_key="unit-value", base_url=base_url).design_voice( + prompt="A calm, warm narrator", + voice_id="narrator-1", + ) + + assert session.calls[0][0] == f"{base_url}/voice_design" + assert session.calls[0][1]["headers"]["Authorization"] == "Bearer unit-value" + assert session.calls[0][1]["json"] == { + "prompt": "A calm, warm narrator", + "voice_id": "narrator-1", + } + assert result.text == "designed-voice" + assert result.raw_response["base_resp"]["status_code"] == 0 + + +@pytest.mark.asyncio +async def test_design_voice_validates_inputs_and_status(monkeypatch): + provider = MiniMaxProvider(api_key="unit-value") + with pytest.raises(ValueError, match="prompt"): + await provider.design_voice(" ", "voice") + with pytest.raises(ValueError, match="voice_id"): + await provider.design_voice("A voice", "") + + session = CaptureSession( + FakeResponse( + {"base_resp": {"status_code": 1004, "status_msg": "invalid prompt"}} + ) + ) + monkeypatch.setattr("aiohttp.ClientSession", lambda **kwargs: session) + with pytest.raises(RuntimeError, match="invalid prompt"): + await provider.design_voice("A voice", "voice")