Skip to content

Commit cc85092

Browse files
Octopusocto-patchstefyi-4355n-papaioannou
authored
Add MiniMax provider (#76)
* feat: add MiniMax provider * Address MiniMax review feedback * fix: harden MiniMax provider error mapping, defaults, and secret scope * fix: repair BLE001 suppression after formatter reflow * Revert "fix: repair BLE001 suppression after formatter reflow" This reverts commit b5e86b7. * chore: fix ci conflict --------- Co-authored-by: octo-patch <266937838+octo-patch@users.noreply.github.com> Co-authored-by: stefyi-4355 <as.peter@ime.life> Co-authored-by: n-papaioannou <n.papaioannou@ime.life>
1 parent 16eefc7 commit cc85092

10 files changed

Lines changed: 447 additions & 87 deletions

File tree

‎SECURITY.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ The scrubber (`ifixai/providers/secrets.py::scrub_secrets`) is parametrically ve
4040
| Provider | Credential shape matched | Redaction token |
4141
|---|---|---|
4242
| `openai` | `sk-[A-Za-z0-9_-]{20,}` | `***REDACTED_OPENAI_KEY***` |
43+
| `minimax` | `sk-cp-[A-Za-z0-9_-]+` | `***REDACTED_MINIMAX_KEY***` |
44+
| any (JWT-shaped bearer tokens, incl. `minimax`) | three-segment JWT beginning with `eyJ` | `***REDACTED_JWT***` |
4345
| `atlascloud` | `(ak|apikey)-[A-Za-z0-9_-]{20,}` | `***REDACTED_ATLASCLOUD_KEY***` |
4446
| `openrouter` | `sk-or-[A-Za-z0-9_-]{20,}` (matched before `openai`) | `***REDACTED_OPENROUTER_KEY***` |
4547
| `anthropic` | `sk-ant-[A-Za-z0-9_-]{20,}`, `anthropic_[A-Za-z0-9_-]{20,}` | `***REDACTED_ANTHROPIC_KEY***` |

‎docs/testing-your-agent.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ The scorecard adds a `warnings[]` note: declared, not measured at runtime. Field
6666
different provider grades the SUT, so nothing scores itself:
6767

6868
- Default judge: any non-SUT provider key in your env, on that provider's default model.
69-
- Multiple keys: tiebreaker order `anthropic → openai → atlascloud → gemini → openrouter → azure → bedrock → huggingface`.
69+
- Multiple keys: tiebreaker order `anthropic → openai → atlascloud → minimax → gemini → openrouter → azure → bedrock → huggingface`.
7070
- No non-SUT key: the run refuses unless you pass `--eval-mode self`.
7171
- Override: `--judge-provider` / `--judge-api-key` / `--judge-model`. Pin these for
7272
`openrouter` and `azure`: auto-routing can land SUT and judge on the same vendor.
@@ -114,6 +114,7 @@ the SUT key from the environment: pass `--api-key` / `-k`, or enter it when prom
114114
| `anthropic` | `.[anthropic]` | `ANTHROPIC_API_KEY` | `--provider anthropic -k "$ANTHROPIC_API_KEY" --model claude-sonnet-4-6` |
115115
| `openai` | `.[openai]` | `OPENAI_API_KEY` | `--provider openai -k "$OPENAI_API_KEY" --model gpt-4o` |
116116
| `atlascloud` | `.[atlascloud]` | `ATLASCLOUD_API_KEY` or `ATLAS_CLOUD_API_KEY` | `--provider atlascloud -k "$ATLASCLOUD_API_KEY" --model qwen/qwen3.5-flash` |
117+
| `minimax` | none | `MINIMAX_API_KEY` | `--provider minimax -k "$MINIMAX_API_KEY" --model MiniMax-M3` |
117118
| `openrouter` | `.[openrouter]` | `OPENROUTER_API_KEY` | `--provider openrouter -k "$OPENROUTER_API_KEY" --model openai/gpt-4o` plus explicit judge |
118119
| `gemini` | `.[gemini]` | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | `--provider gemini -k "$GEMINI_API_KEY"` |
119120
| `azure` | `.[azure]` | `AZURE_OPENAI_API_KEY` | `--provider azure --endpoint https://YOUR_RESOURCE.openai.azure.com/ -k "$AZURE_OPENAI_API_KEY" --model YOUR_DEPLOYMENT_NAME` plus explicit judge |

‎ifixai/cli/init.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
PROVIDER_ENV_KEYS: dict[str, str] = {
1212
"anthropic": "ANTHROPIC_API_KEY",
1313
"openai": "OPENAI_API_KEY",
14+
"minimax": "MINIMAX_API_KEY",
1415
"openrouter": "OPENROUTER_API_KEY",
1516
"gemini": "GEMINI_API_KEY",
1617
"azure": "AZURE_OPENAI_API_KEY",

‎ifixai/cli/model_catalog.py‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,35 @@
22

33
from __future__ import annotations
44

5+
from ifixai.providers.minimax import DEFAULT_MODEL as MINIMAX_DEFAULT_MODEL
6+
from ifixai.providers.minimax import MODEL_METADATA as MINIMAX_MODEL_METADATA
7+
from ifixai.providers.minimax import MODEL_PRICING_TIERS as MINIMAX_MODEL_PRICING_TIERS
8+
9+
10+
def _minimax_pricing_summary(model_id: str) -> str:
11+
tiers = MINIMAX_MODEL_PRICING_TIERS.get(model_id)
12+
if not tiers:
13+
pricing = MINIMAX_MODEL_METADATA[model_id]["pricing_usd_per_million_tokens"]
14+
return f"${pricing['input']:.2f}/${pricing['output']:.2f}"
15+
16+
summaries = []
17+
previous_limit = None
18+
for tier in tiers:
19+
pricing = tier["pricing_usd_per_million_tokens"]
20+
limit = tier["max_input_tokens"]
21+
if limit is not None:
22+
range_label = f"up to {limit // 1000}k input"
23+
previous_limit = limit
24+
elif previous_limit is not None:
25+
range_label = f"above {previous_limit // 1000}k input"
26+
else:
27+
range_label = "all input sizes"
28+
summaries.append(
29+
f"${pricing['input']:.2f}/${pricing['output']:.2f} {range_label}"
30+
)
31+
return "; ".join(summaries)
32+
33+
534
DEFAULT_MODEL: dict[str, str] = {
635
"openrouter": "openai/gpt-4o",
736
"openai": "gpt-4o",
@@ -10,6 +39,7 @@
1039
"azure": "gpt-4o",
1140
"bedrock": "anthropic.claude-3-5-sonnet-20241022-v2:0",
1241
"huggingface": "meta-llama/Llama-3.3-70B-Instruct",
42+
"minimax": MINIMAX_DEFAULT_MODEL,
1343
}
1444

1545
MODEL_SUGGESTIONS: dict[str, list[tuple[str, str]]] = {
@@ -55,6 +85,17 @@
5585
("meta-llama/Llama-3.3-70B-Instruct", "Open-weights Llama 3.3"),
5686
("mistralai/Mistral-7B-Instruct-v0.3", "Small, fast Mistral"),
5787
],
88+
"minimax": [
89+
(
90+
model_id,
91+
f"{metadata['context_window']:,}-token context; "
92+
f"{'/'.join(metadata['input_modalities'])} input; "
93+
f"{'/'.join(metadata['thinking'])} thinking; "
94+
f"{_minimax_pricing_summary(model_id)} "
95+
"per 1M input/output tokens",
96+
)
97+
for model_id, metadata in MINIMAX_MODEL_METADATA.items()
98+
],
5899
}
59100

60101

‎ifixai/cli/run.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def _governance_source_warning(source: str) -> str | None:
132132
"mock",
133133
"openai",
134134
"atlascloud",
135+
"minimax",
135136
"gemini",
136137
"anthropic",
137138
"azure",

‎ifixai/cli/setup_cmd.py‎

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from ifixai.cli.model_catalog import default_model, suggestions
1717
from ifixai.core.fixture_loader import list_fixture_names, load_fixture
1818
from ifixai.harness.suites import suite_catalog
19+
from ifixai.providers.minimax import DEFAULT_BASE_URL, REGIONAL_ENDPOINTS
1920

2021
_PROVIDER_DESCRIPTIONS: dict[str, str] = {
2122
"openrouter": "One key → many models (OpenAI, Anthropic, Google, Llama…)",
@@ -25,6 +26,7 @@
2526
"azure": "Azure OpenAI deployment",
2627
"bedrock": "AWS Bedrock-hosted models",
2728
"huggingface": "Hugging Face Inference endpoints",
29+
"minimax": "MiniMax global and China text APIs",
2830
"http": "Your real deployed agent's OpenAI-compatible HTTP endpoint (recommended)",
2931
"langchain": "A LangChain-wrapped model",
3032
"mock": "Built-in offline mock — no key, just to try the tool",
@@ -50,13 +52,33 @@
5052
"azure",
5153
"bedrock",
5254
"huggingface",
55+
"minimax",
5356
"http",
5457
"langchain",
5558
"mock",
5659
]
5760

5861
_CUSTOM_MODEL = "✏ Enter a custom model id…"
5962

63+
_MINIMAX_ENDPOINTS: list[tuple[str, str]] = [
64+
(
65+
REGIONAL_ENDPOINTS["global_en"]["openai_base_url"],
66+
"Global chat completions API (recommended — honours seed and JSON mode)",
67+
),
68+
(
69+
REGIONAL_ENDPOINTS["cn_zh"]["openai_base_url"],
70+
"China chat completions API",
71+
),
72+
(
73+
REGIONAL_ENDPOINTS["global_en"]["anthropic_base_url"],
74+
"Global messages API (no seed, no JSON mode)",
75+
),
76+
(
77+
REGIONAL_ENDPOINTS["cn_zh"]["anthropic_base_url"],
78+
"China messages API (no seed, no JSON mode)",
79+
),
80+
]
81+
6082

6183
def _pick_model(provider: str, *, role: str) -> str | None:
6284
"""Model picker (default / suggestions / custom); None means provider default."""
@@ -129,7 +151,9 @@ def setup(ctx: click.Context) -> None:
129151
)
130152
else:
131153
click.echo(
132-
click.style("No provider API keys detected in your environment.", fg="yellow")
154+
click.style(
155+
"No provider API keys detected in your environment.", fg="yellow"
156+
)
133157
)
134158

135159
# Surface the real-agent (http) path first: it's the highest-fidelity SUT.
@@ -166,6 +190,13 @@ def setup(ctx: click.Context) -> None:
166190
).strip()
167191
or None
168192
)
193+
elif provider == "minimax":
194+
endpoint = ui.select(
195+
"MiniMax endpoint:",
196+
[value for value, _ in _MINIMAX_ENDPOINTS],
197+
default=DEFAULT_BASE_URL,
198+
descriptions=dict(_MINIMAX_ENDPOINTS),
199+
)
169200
if provider == "http":
170201
grounding = "sut"
171202

@@ -214,7 +245,9 @@ def setup(ctx: click.Context) -> None:
214245
base = _PROVIDER_DESCRIPTIONS.get(p, "")
215246
env = PROVIDER_ENV_KEYS.get(p)
216247
if p == provider:
217-
judge_desc[p] = f"{base} — same vendor as the SUT; not an independent (citable) judge"
248+
judge_desc[p] = (
249+
f"{base} — same vendor as the SUT; not an independent (citable) judge"
250+
)
218251
elif p in available_names:
219252
judge_desc[p] = f"{base} — key detected"
220253
elif env:
@@ -272,7 +305,9 @@ def setup(ctx: click.Context) -> None:
272305
)
273306
elif len(judges) >= 2:
274307
click.echo(
275-
click.style(f" Ensemble of {len(judges)} judges configured.", fg="cyan")
308+
click.style(
309+
f" Ensemble of {len(judges)} judges configured.", fg="cyan"
310+
)
276311
)
277312

278313
fixtures = list_fixture_names()
@@ -281,7 +316,8 @@ def setup(ctx: click.Context) -> None:
281316
try:
282317
fx = load_fixture(name)
283318
fixture_desc[name] = fx.metadata.domain or fx.metadata.name or name
284-
except Exception: # noqa: BLE001 — best-effort environment probe; any failure falls back to the default
319+
# Best-effort environment probe; any failure falls back to the default.
320+
except Exception: # noqa: BLE001
285321
fixture_desc[name] = name
286322
fixture = ui.select(
287323
"Fixture (the deployment profile to test against):",

0 commit comments

Comments
 (0)