mirror of
https://github.com/calesthio/OpenMontage.git
synced 2026-08-18 14:29:54 +08:00
fix: complete MiniMax image provider contracts
This commit is contained in:
@@ -8,6 +8,13 @@ FAL_KEY=
|
||||
# Alias for FAL_KEY (some SDKs/docs use this name); either one is read.
|
||||
FAL_AI_API_KEY=
|
||||
|
||||
# --- MiniMax official direct API ---
|
||||
# First-party image generation (image-01 / image-01-live).
|
||||
# Get one at https://platform.minimax.io/user-center/basic-information/interface-key
|
||||
MINIMAX_API_KEY=
|
||||
# Optional: global (default) or cn.
|
||||
MINIMAX_REGION=global
|
||||
|
||||
# --- Replicate ---
|
||||
# Replicate-hosted video gen (seedance_replicate). Needed to make the
|
||||
# Replicate-backed Seedance path selectable alongside the fal.ai one.
|
||||
|
||||
@@ -50,6 +50,7 @@ AZURE_SPEECH_REGION= # Speech resource region, e.g. eastus
|
||||
|
||||
# MULTI-MODEL GATEWAY (one key, 6+ tools)
|
||||
FAL_KEY= # FLUX, Recraft, Kling, Veo, MiniMax video
|
||||
MINIMAX_API_KEY= # MiniMax first-party image generation
|
||||
|
||||
# KLING OFFICIAL DIRECT API
|
||||
KLING_API_KEY= # Official Kling video, image, TTS, avatar, lip sync
|
||||
@@ -227,6 +228,40 @@ No subscription — pure pay-as-you-go, no minimum spend.
|
||||
|
||||
---
|
||||
|
||||
### MiniMax — Official Direct Image API
|
||||
|
||||
> **Low-cost first-party image generation.** The direct MiniMax API supports
|
||||
> seeded text-to-image, character subject references, custom dimensions, and
|
||||
> global or mainland-China routing without a gateway.
|
||||
|
||||
**Tool unlocked:** `minimax_image`
|
||||
|
||||
**Env var:** `MINIMAX_API_KEY`
|
||||
|
||||
**Optional region:** `MINIMAX_REGION=global` (default) or `cn`
|
||||
|
||||
#### Setup
|
||||
|
||||
1. Create a MiniMax Open Platform account.
|
||||
2. Generate an API key in the account's API-key page.
|
||||
3. Add `MINIMAX_API_KEY=...` to `.env`.
|
||||
4. For a mainland-China account, also set `MINIMAX_REGION=cn`.
|
||||
|
||||
#### Pricing
|
||||
|
||||
| Models | Global pay-as-you-go price |
|
||||
|--------|----------------------------|
|
||||
| `image-01`, `image-01-live` | $0.0035 per generated image |
|
||||
|
||||
MiniMax also offers subscription token plans with included daily image quota.
|
||||
OpenMontage conservatively reports the standard pay-as-you-go amount in cost
|
||||
estimates and generation results.
|
||||
|
||||
The tool is automatically discoverable through `image_selector`; choose it
|
||||
with `preferred_provider: "minimax"`.
|
||||
|
||||
---
|
||||
|
||||
### Kling Official — Direct API
|
||||
|
||||
> **Official Kling path.** This is separate from `kling_video` via fal.ai: it uses Kling's official `Authorization: Bearer <KLING_API_KEY>` API, provider name `kling_official`, and direct Classic/Turbo/Omni task protocols.
|
||||
|
||||
@@ -54,6 +54,75 @@ def test_status_requires_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
assert tool.get_status() == ToolStatus.AVAILABLE
|
||||
|
||||
|
||||
def test_cost_estimate_and_result_report_paid_images(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path
|
||||
) -> None:
|
||||
monkeypatch.setenv("MINIMAX_API_KEY", "test-key")
|
||||
monkeypatch.setattr(
|
||||
requests,
|
||||
"post",
|
||||
lambda *args, **kwargs: FakeResponse(
|
||||
json_data={
|
||||
"data": {
|
||||
"image_base64": [
|
||||
base64.b64encode(b"one").decode("ascii"),
|
||||
base64.b64encode(b"two").decode("ascii"),
|
||||
]
|
||||
},
|
||||
"base_resp": {"status_code": 0},
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
tool = MiniMaxImage()
|
||||
inputs = {
|
||||
"prompt": "A lighthouse at dusk",
|
||||
"response_format": "base64",
|
||||
"n": 2,
|
||||
"output_path": str(tmp_path / "image.png"),
|
||||
}
|
||||
assert tool.estimate_cost(inputs) == pytest.approx(0.007)
|
||||
result = tool.execute(inputs)
|
||||
assert result.success, result.error
|
||||
assert result.cost_usd == pytest.approx(0.007)
|
||||
|
||||
|
||||
def test_image_selector_can_route_to_minimax(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path
|
||||
) -> None:
|
||||
from tools.graphics.image_selector import ImageSelector
|
||||
|
||||
monkeypatch.setenv("MINIMAX_API_KEY", "test-key")
|
||||
monkeypatch.setattr(
|
||||
requests,
|
||||
"post",
|
||||
lambda *args, **kwargs: FakeResponse(
|
||||
json_data={
|
||||
"data": {
|
||||
"image_base64": [base64.b64encode(b"image").decode("ascii")]
|
||||
},
|
||||
"base_resp": {"status_code": 0},
|
||||
}
|
||||
),
|
||||
)
|
||||
tool = MiniMaxImage()
|
||||
selector = ImageSelector()
|
||||
monkeypatch.setattr(selector, "_providers", lambda: [tool])
|
||||
|
||||
result = selector.execute(
|
||||
{
|
||||
"prompt": "A lighthouse at dusk",
|
||||
"preferred_provider": "minimax",
|
||||
"response_format": "base64",
|
||||
"output_path": str(tmp_path / "selected.png"),
|
||||
}
|
||||
)
|
||||
|
||||
assert result.success, result.error
|
||||
assert result.data["selected_provider"] == "minimax"
|
||||
assert result.data["selected_tool"] == "minimax_image"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("region", "expected_base_url"),
|
||||
[
|
||||
|
||||
@@ -24,6 +24,8 @@ from tools.base_tool import (
|
||||
MODELS = ["image-01", "image-01-live"]
|
||||
DEFAULT_MODEL = "image-01"
|
||||
DEFAULT_REGION = "global"
|
||||
# Official global pay-as-you-go rate for image-01/image-01-live.
|
||||
PRICE_PER_IMAGE_USD = 0.0035
|
||||
REGION_BASE_URLS = {
|
||||
"global": "https://api.minimax.io",
|
||||
"global_en": "https://api.minimax.io",
|
||||
@@ -48,7 +50,9 @@ class MiniMaxImage(BaseTool):
|
||||
"Set MINIMAX_API_KEY to your MiniMax API key. "
|
||||
"Optionally set MINIMAX_REGION to global or cn."
|
||||
)
|
||||
agent_skills = ["flux-best-practices"]
|
||||
# MiniMax is not a FLUX model. Use the provider-neutral visual direction
|
||||
# skill until a dedicated MiniMax prompting skill is available.
|
||||
agent_skills = ["visual-style"]
|
||||
|
||||
capabilities = ["generate_image", "text_to_image"]
|
||||
supports = {
|
||||
@@ -209,6 +213,9 @@ class MiniMaxImage(BaseTool):
|
||||
def _safe_error(exc: Exception, api_key: str) -> str:
|
||||
return str(exc).replace(api_key, "[redacted]") if api_key else str(exc)
|
||||
|
||||
def estimate_cost(self, inputs: dict[str, Any]) -> float:
|
||||
return PRICE_PER_IMAGE_USD * int(inputs.get("n", 1))
|
||||
|
||||
def execute(self, inputs: dict[str, Any]) -> ToolResult:
|
||||
api_key = os.environ.get("MINIMAX_API_KEY", "")
|
||||
if not api_key:
|
||||
@@ -285,6 +292,7 @@ class MiniMaxImage(BaseTool):
|
||||
"request_id": data.get("id"),
|
||||
},
|
||||
artifacts=outputs,
|
||||
cost_usd=self.estimate_cost(inputs),
|
||||
duration_seconds=round(time.time() - start, 2),
|
||||
model=payload["model"],
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user