Skip to content
Open
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
1 change: 1 addition & 0 deletions backend/app/services/agent_runtime/tool_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"generate_image_siliconflow",
"generate_image_openai",
"generate_image_google",
"generate_image_minimax",
"generate_image_custom",
}
)
Expand Down
123 changes: 122 additions & 1 deletion backend/app/services/agent_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ async def _get_scoped_agentbay_client(
"generate_image_siliconflow",
"generate_image_openai",
"generate_image_google",
"generate_image_minimax",
"generate_image_custom",
}
)
Expand All @@ -456,6 +457,7 @@ async def _get_scoped_agentbay_client(
"generate_image_siliconflow": "siliconflow",
"generate_image_openai": "openai",
"generate_image_google": "google",
"generate_image_minimax": "minimax",
"generate_image_custom": "custom",
}

Expand Down Expand Up @@ -1246,6 +1248,7 @@ async def get_runtime_agent_tools_for_llm(agent_id: uuid.UUID) -> list[dict]:
"generate_image_siliconflow",
"generate_image_openai",
"generate_image_google",
"generate_image_minimax",
} and str(config.get("api_key") or "").strip():
ready.append(tool)
elif name == "generate_image_custom" and all(
Expand Down Expand Up @@ -3272,6 +3275,13 @@ async def execute_tool(
lambda temp_ws: _generate_image(agent_id, temp_ws, arguments, "google"),
sync_back=True,
)
elif tool_name == "generate_image_minimax":
result = await _run_with_temp_workspace(
agent_id,
_agent_tenant_id,
lambda temp_ws: _generate_image(agent_id, temp_ws, arguments, "minimax"),
sync_back=True,
)
elif tool_name == "generate_image_custom":
result = await _run_with_temp_workspace(
agent_id,
Expand Down Expand Up @@ -11351,6 +11361,23 @@ async def _generate_image_outcome(
"invalid_tool_arguments",
)

minimax_seed = arguments.get("seed")
minimax_prompt_optimizer = arguments.get("prompt_optimizer", False)
if provider == "minimax" and (
(
minimax_seed is not None
and (
not isinstance(minimax_seed, int)
or isinstance(minimax_seed, bool)
)
)
or not isinstance(minimax_prompt_optimizer, bool)
):
return _typed_failure(
"MiniMax seed must be an integer and prompt_optimizer must be a boolean.",
"invalid_tool_arguments",
)

save_path_value = arguments.get("save_path", "")
if save_path_value is not None and not isinstance(save_path_value, str):
return _typed_failure(
Expand All @@ -11375,7 +11402,7 @@ async def _generate_image_outcome(
"workspace_path_invalid",
)

if provider not in {"siliconflow", "openai", "google", "custom"}:
if provider not in {"siliconflow", "openai", "google", "minimax", "custom"}:
return _typed_failure(
"Unknown image generation provider.",
"invalid_tool_arguments",
Expand Down Expand Up @@ -11423,6 +11450,16 @@ async def _generate_image_outcome(
prompt,
size,
)
elif provider == "minimax":
image_bytes = await _generate_image_minimax(
api_key,
model or "image-01",
base_url or "https://api.minimax.io/v1/image_generation",
prompt,
size,
seed=minimax_seed,
prompt_optimizer=minimax_prompt_optimizer,
)
else:
image_bytes = await _generate_image_custom_api(
api_key=api_key,
Expand Down Expand Up @@ -11678,6 +11715,90 @@ async def _generate_image_openai(
)


_MINIMAX_ASPECT_RATIO_BY_SIZE = {
"1024x1024": "1:1",
"1024x768": "4:3",
"768x1024": "3:4",
"1366x768": "16:9",
"768x1366": "9:16",
"1536x1024": "3:2",
"1024x1536": "2:3",
}


async def _generate_image_minimax(
api_key: str,
model: str,
endpoint: str,
prompt: str,
size: str,
*,
seed: object = None,
prompt_optimizer: object = False,
) -> bytes:
"""Generate one image with the native MiniMax image API."""
import httpx

payload: dict[str, object] = {
"model": model,
"prompt": prompt,
"aspect_ratio": _MINIMAX_ASPECT_RATIO_BY_SIZE[size],
"response_format": "base64",
"n": 1,
"prompt_optimizer": prompt_optimizer is True,
}
if isinstance(seed, int) and not isinstance(seed, bool):
payload["seed"] = seed

headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
async with httpx.AsyncClient(timeout=120) as client:
resp = await client.post(endpoint, json=payload, headers=headers)
_settle_image_provider_status("MiniMax", resp.status_code)
try:
data = resp.json()
except Exception:
_image_generation_unknown(
"image_provider_response_invalid",
"MiniMax returned an unreadable success response.",
)
if not isinstance(data, Mapping):
_image_generation_unknown(
"image_provider_response_invalid",
"MiniMax returned an invalid success response.",
)

base_resp = data.get("base_resp")
if not isinstance(base_resp, Mapping):
_image_generation_unknown(
"image_provider_response_invalid",
"MiniMax success response omitted its status receipt.",
)
if base_resp.get("status_code") != 0:
_image_generation_failure(
"image_provider_rejected",
"MiniMax rejected the image generation request.",
)
result = data.get("data")
if not isinstance(result, Mapping):
_image_generation_unknown(
"image_provider_response_invalid",
"MiniMax success response omitted the image receipt.",
)
encoded_images = result.get("image_base64")
if isinstance(encoded_images, list) and encoded_images:
return _decode_generated_image_base64(encoded_images[0])
image_urls = result.get("image_urls")
if isinstance(image_urls, list) and image_urls:
return await _download_generated_image(image_urls[0], client)
_image_generation_unknown(
"image_provider_response_invalid",
"MiniMax success response omitted the image receipt.",
)


def _json_path_get(data: Any, path: str) -> Any:
"""Read a simple dotted JSON path, with numeric list indexes."""
if not path:
Expand Down
81 changes: 81 additions & 0 deletions backend/app/services/builtin_tool_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,86 @@
]
},
},
{
"name": "generate_image_minimax",
"display_name": "Generate Image (MiniMax)",
"description": "Generate an image with the native MiniMax image API.",
"category": "media",
"icon": "🎨",
"is_default": False,
"parameters_schema": {
"type": "object",
"properties": {
"prompt": {
"type": "string",
"minLength": 1,
"maxLength": 1500,
"description": "Detailed image description.",
},
"size": {
"type": "string",
"enum": ["1024x1024", "1024x768", "768x1024", "1366x768", "768x1366", "1536x1024", "1024x1536"],
"description": "Image size. Default 1024x1024.",
},
"seed": {
"type": "integer",
"description": "Optional random seed for reproducible generation.",
},
"prompt_optimizer": {
"type": "boolean",
"description": "Enable automatic prompt optimization. Default false.",
},
"save_path": {
"type": "string",
"pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+\\.(?:png|jpg|jpeg|webp)$",
"description": "Workspace-relative image path. Default: auto.",
},
},
"required": ["prompt"],
},
"config": {
"model": "",
"api_key": "",
"base_url": "",
},
"config_schema": {
"fields": [
{
"key": "model",
"label": "Model",
"type": "select",
"default": "image-01",
"options": [
{"value": "image-01", "label": "image-01"},
{"value": "image-01-live", "label": "image-01-live"},
],
},
{
"key": "api_key",
"label": "API Key",
"type": "password",
"default": "",
"placeholder": "MiniMax API Key",
},
{
"key": "base_url",
"label": "Regional Endpoint",
"type": "select",
"default": "https://api.minimax.io/v1/image_generation",
"options": [
{
"value": "https://api.minimax.io/v1/image_generation",
"label": "Global",
},
{
"value": "https://api.minimaxi.com/v1/image_generation",
"label": "China",
},
],
},
]
},
},
{
"name": "generate_image_custom",
"display_name": "Generate Image (Custom API)",
Expand Down Expand Up @@ -3810,6 +3890,7 @@
"generate_image_siliconflow": 120,
"generate_image_openai": 120,
"generate_image_google": 120,
"generate_image_minimax": 120,
"generate_image_custom": 120,
}

Expand Down
1 change: 1 addition & 0 deletions backend/tests/test_agent_tools_deploy_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"generate_image_siliconflow",
"generate_image_openai",
"generate_image_google",
"generate_image_minimax",
"generate_image_custom",
)
class FakeResponse:
Expand Down
Loading