Skip to content
Open
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
42 changes: 42 additions & 0 deletions sdk/python/agentfield/media_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,47 @@ async def generate_music(
raise NotImplementedError(f"{self.name} does not support music generation")


class MiniMaxProvider(MediaProvider):
"""MiniMax music generation provider for global and China endpoints."""

def __init__(self, api_key: Optional[str] = None, region: str = "global_en"):
self._api_key = api_key
self.region = region

@property
def name(self) -> str:
return "minimax"

@property
def supported_modalities(self) -> List[str]:
return ["music"]

async def generate_image(self, prompt: str, model: Optional[str] = None, size: str = "1024x1024", quality: str = "standard", **kwargs) -> MultimodalResponse:
raise NotImplementedError(f"{self.name} does not support image generation")

async def generate_audio(self, text: str, model: Optional[str] = None, voice: str = "alloy", format: str = "wav", *, system: Optional[str] = None, **kwargs) -> MultimodalResponse:
raise NotImplementedError(f"{self.name} does not support speech generation")

async def generate_music(self, prompt: str, model: Optional[str] = None, duration: Optional[int] = None, **kwargs) -> MultimodalResponse:
import base64, os, aiohttp
key = self._api_key or os.environ.get("MINIMAX_API_KEY", "")
if not key:
raise ValueError("MiniMax API key required. Set MINIMAX_API_KEY or pass api_key.")
endpoint = "https://api.minimaxi.com/v1/music_generation" if self.region == "cn_zh" else "https://api.minimax.io/v1/music_generation"
body = {"model": model or "music-3.0", "prompt": prompt, "output_format": kwargs.pop("output_format", "url"), "stream": kwargs.pop("stream", False)}
if duration is not None:
body["audio_setting"] = {"sample_rate": kwargs.pop("sample_rate", 44100), "bitrate": kwargs.pop("bitrate", 256000), "format": kwargs.pop("format", "mp3"), "duration": duration}
body.update({k: v for k, v in kwargs.items() if k in {"lyrics", "lyrics_optimizer", "is_instrumental", "audio_setting", "aigc_watermark", "cover_feature_id"}})
async with aiohttp.ClientSession() as session:
async with session.post(endpoint, json=body, headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"}) as response:
data = await response.json()
if response.status >= 400 or data.get("base_resp", {}).get("status_code") != 0:
raise RuntimeError(f"MiniMax music generation failed ({response.status}): {data}")
audio = data.get("data", {}).get("audio")
output = AudioOutput(data=audio if audio and body["output_format"] == "hex" else None, format=body.get("audio_setting", {}).get("format", "mp3"), url=audio if body["output_format"] == "url" else None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AudioOutput.data is base64-encoded audio bytes everywhere else in the SDK, but this branch stores MiniMax's raw hex string directly. That means helpers like AudioOutput.get_bytes() and .save() will try to base64-decode hex and produce the wrong bytes or fail.

Can we decode the hex payload to bytes here and then re-encode it as base64 before constructing AudioOutput?

return MultimodalResponse(text=prompt, audio=output, images=[], files=[], raw_response=data)


class FalProvider(MediaProvider):
"""
Fal.ai provider for image, audio, and video generation.
Expand Down Expand Up @@ -1654,6 +1695,7 @@ async def generate_music(
"fal": FalProvider,
"litellm": LiteLLMProvider,
"openrouter": OpenRouterProvider,
"minimax": MiniMaxProvider,
}


Expand Down