Skip to content
Merged
Show file tree
Hide file tree
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
59 changes: 51 additions & 8 deletions sdk/python/agentfield/agent_ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ def _openrouter_provider(self):

@property
def _minimax_provider(self):
"""Lazy-initialized MiniMax provider for video generation."""
"""Lazy-initialized MiniMax media provider."""
if self._minimax_provider_instance is None:
from agentfield.media_providers import MiniMaxProvider

Expand Down Expand Up @@ -1443,30 +1443,55 @@ async def ai_with_audio(
"""
AI method optimized for audio output generation.

Automatically detects the model type and uses the appropriate LiteLLM function:
Automatically detects the model type and uses the appropriate provider:
- For MiniMax TTS models (minimax/speech-2.8-hd, etc.): Uses MiniMax T2A
- For TTS models (tts-1, tts-1-hd, gpt-4o-mini-tts): Uses litellm.speech()
- For audio-capable chat models (gpt-4o-audio-preview): Uses litellm.completion() with audio modalities

Args:
*args: Input arguments (text prompts, etc.)
voice: Voice to use for audio generation (alloy, echo, fable, onyx, nova, shimmer)
format: Audio format (wav, mp3, etc.)
voice: Voice identifier. OpenAI voices include alloy, echo, fable,
onyx, nova, and shimmer. MiniMax models require a MiniMax voice ID.
format: Audio format. MiniMax supports mp3, wav, flac, and pcm.
model: Model to use (defaults to tts-1)
**kwargs: Additional parameters
**kwargs: Additional parameters. MiniMax supports output_format
("hex" or "url"), language_boost, voice_setting,
pronunciation_dict, audio_setting, voice_modify, and
subtitle_enable. Streaming is not supported.

Returns:
MultimodalResponse with audio content

Example:
audio_result = await agent.ai_with_audio("Say hello warmly", voice="alloy")
audio_result.audio.save("greeting.wav")

minimax_result = await agent.ai_with_audio(
"Say hello warmly",
model="minimax/speech-2.8-hd",
voice="English_Graceful_Lady",
format="mp3",
)
"""
# Use TTS model as default (more reliable than gpt-4o-audio-preview)
if model is None:
model = (
self.agent.ai_config.audio_model
) # Use configured audio model (defaults to tts-1)

if model.startswith("minimax/"):
provider = self._media_router.resolve(model, "audio")
text_input = " ".join(str(arg) for arg in args if isinstance(arg, str))
if not text_input:
text_input = "Hello, this is a test audio message."
return await provider.generate_audio(
text=text_input,
model=model,
voice=voice,
format=format,
**kwargs,
)

# Try media router for fal models
try:
provider = self._media_router.resolve(model, "audio")
Expand Down Expand Up @@ -1919,14 +1944,22 @@ async def ai_generate_audio(
Supported Providers:
- LiteLLM: OpenAI TTS models like "tts-1", "tts-1-hd", "gpt-4o-mini-tts"
- Fal.ai: TTS models like "fal-ai/kokoro/..." (custom deployments)
- MiniMax: TTS models prefixed with "minimax/", such as
"minimax/speech-2.8-hd"

Args:
text: Text to convert to speech
model: TTS model to use (defaults to AIConfig.audio_model, typically "tts-1")
voice: Voice to use ("alloy", "echo", "fable", "onyx", "nova", "shimmer")
format: Audio format ("wav", "mp3", "opus", "aac", "flac", "pcm")
voice: Voice identifier. OpenAI voices include "alloy", "echo",
"fable", "onyx", "nova", and "shimmer". MiniMax models
require a MiniMax voice ID instead of the default "alloy".
format: Audio format. MiniMax supports "mp3", "wav", "flac", and
"pcm".
speed: Speech speed multiplier (0.25 to 4.0)
**kwargs: Provider-specific parameters
**kwargs: Provider-specific parameters. MiniMax supports
output_format ("hex" or "url"), language_boost,
voice_setting, pronunciation_dict, audio_setting,
voice_modify, and subtitle_enable. Streaming is not supported.

Returns:
MultimodalResponse: Response object with .audio containing AudioOutput.
Expand All @@ -1949,6 +1982,16 @@ async def ai_generate_audio(
format="mp3"
)

# MiniMax TTS with a MiniMax voice ID
result = await app.ai_generate_audio(
"Welcome to the presentation.",
model="minimax/speech-2.8-hd",
voice="English_Graceful_Lady",
format="mp3",
language_boost="English",
output_format="hex",
)

# Adjust speech speed
result = await app.ai_generate_audio(
"This is spoken slowly.",
Expand Down
135 changes: 131 additions & 4 deletions sdk/python/agentfield/media_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Provides a unified interface for different media generation backends:
- Fal.ai (Flux, SDXL, Whisper, TTS, Video models)
- MiniMax (Video generation)
- MiniMax (Media generation)
- OpenRouter (via LiteLLM)
- OpenAI DALL-E (via LiteLLM)
- Future: ElevenLabs, Replicate, etc.
Expand Down Expand Up @@ -650,7 +650,7 @@ async def transcribe_audio(


class MiniMaxProvider(MediaProvider):
"""MiniMax provider for asynchronous video generation."""
"""MiniMax media generation provider."""

def __init__(
self,
Expand All @@ -671,7 +671,7 @@ def name(self) -> str:

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

async def generate_image(
self,
Expand All @@ -691,9 +691,136 @@ async def generate_audio(
format: str = "wav",
*,
system: Optional[str] = None,
stream: bool = False,
language_boost: Optional[str] = None,
output_format: str = "hex",
voice_setting: Optional[Dict[str, Any]] = None,
pronunciation_dict: Optional[Dict[str, Any]] = None,
audio_setting: Optional[Dict[str, Any]] = None,
voice_modify: Optional[Dict[str, Any]] = None,
subtitle_enable: Optional[bool] = None,
**kwargs,
) -> MultimodalResponse:
raise NotImplementedError("minimax does not support audio generation")
"""Generate speech via the MiniMax t2a_v2 endpoint."""
import base64
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 stream:
raise ValueError("MiniMax streaming TTS is not supported by generate_audio")
if output_format not in {"hex", "url"}:
raise ValueError("MiniMax audio output_format must be hex or url")

audio_options = dict(audio_setting or {})
audio_format = audio_options.get("format", format)
if audio_format not in {"mp3", "wav", "flac", "pcm"}:
raise ValueError("MiniMax audio format must be mp3, wav, flac, or pcm")
audio_options["format"] = audio_format

voice_options = dict(voice_setting or {})
speed = kwargs.pop("speed", None)
if voice != "alloy":
voice_options.setdefault("voice_id", voice)
if speed is not None and voice_options:
voice_options.setdefault("speed", speed)
elif speed not in (None, 1.0):
raise ValueError(
"MiniMax speed requires a voice or voice_setting with voice_id"
)
if voice_options and not voice_options.get("voice_id"):
raise ValueError("MiniMax voice_setting requires voice_id")

send_model = self._strip_prefix(model or "speech-2.8-hd")
if not send_model:
raise ValueError("MiniMax audio generation requires a model")

body: Dict[str, Any] = {
"model": send_model,
"text": text,
"stream": False,
"output_format": output_format,
"audio_setting": audio_options,
}
optional_fields = {
"language_boost": language_boost,
"voice_setting": voice_options or None,
"pronunciation_dict": pronunciation_dict,
"voice_modify": voice_modify,
"subtitle_enable": subtitle_enable,
}
body.update(
{key: value for key, value in optional_fields.items() if value is not None}
)

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}/t2a_v2",
headers=headers,
json=body,
) as response:
if response.status >= 400:
detail = await response.text()
raise RuntimeError(
f"MiniMax audio generation failed ({response.status}): "
f"{detail[:500]}"
)
data = await response.json()

if not isinstance(data, dict):
raise RuntimeError("MiniMax audio generation returned an invalid response")
base_resp = data.get("base_resp") or {}
if not isinstance(base_resp, dict):
raise RuntimeError("MiniMax audio generation returned an invalid response")
status_code = base_resp.get("status_code")
if status_code not in (None, 0):
status_msg = base_resp.get("status_msg") or "unknown error"
raise RuntimeError(
f"MiniMax audio generation failed ({status_code}): {status_msg}"
)

response_data = data.get("data") or {}
if not isinstance(response_data, dict):
raise RuntimeError("MiniMax audio generation returned no audio")
status = response_data.get("status")
if status not in (None, 2):
raise RuntimeError("MiniMax audio generation did not complete")
audio_value = response_data.get("audio")
if not isinstance(audio_value, str) or not audio_value:
raise RuntimeError("MiniMax audio generation returned no audio")

if output_format == "url":
output = AudioOutput(data=None, format=audio_format, url=audio_value)
else:
try:
audio_bytes = bytes.fromhex(audio_value)
except ValueError as exc:
raise RuntimeError(
"MiniMax audio generation returned invalid hex audio"
) from exc
output = AudioOutput(
data=base64.b64encode(audio_bytes).decode("ascii"),
format=audio_format,
url=None,
)
return MultimodalResponse(
text=text,
audio=output,
images=[],
files=[],
raw_response=data,
)

async def generate_music(
self,
Expand Down
Loading
Loading