From e1afa01f7c238b8c431cec42584418b6e0281648 Mon Sep 17 00:00:00 2001 From: octo-patch <266937838+octo-patch@users.noreply.github.com> Date: Wed, 29 Jul 2026 06:51:24 +0000 Subject: [PATCH] feat(harness): add MiniMax provider to standalone LLM helper Add a MiniMax provider branch to .agent/harness/llm.py so the portable harness can call MiniMax directly via its OpenAI- and Anthropic-compatible endpoints instead of relying on undocumented external SDK behavior. - Configure global (api.minimax.io) and CN (api.minimaxi.com) regional endpoints with both OpenAI and Anthropic base URLs. - Register current MiniMax-M3 (1,000,000-token context) and MiniMax-M2.7 (204,800-token context) text models; MiniMax-M3 is the default. - Select region via AGENT_MINIMAX_REGION and wire via AGENT_MINIMAX_WIRE. - Document the new env vars in .env.example and the standalone-python adapter README. - Add tests/test_llm_provider.py covering region/model config, provider availability, and both OpenAI/Anthropic wires for each region. --- .agent/harness/llm.py | 72 +++++++++++++ .env.example | 7 ++ adapters/standalone-python/README.md | 7 ++ tests/test_llm_provider.py | 152 +++++++++++++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 tests/test_llm_provider.py diff --git a/.agent/harness/llm.py b/.agent/harness/llm.py index 082969a..d2aecde 100644 --- a/.agent/harness/llm.py +++ b/.agent/harness/llm.py @@ -2,6 +2,37 @@ import os +# MiniMax regional endpoints. Each region exposes OpenAI- and Anthropic-compatible +# base URLs so the portable harness can call MiniMax directly instead of relying +# on undocumented external SDK environment behavior. +MINIMAX_REGIONS = { + "global_en": { + "openai_base_url": "https://api.minimax.io/v1", + "anthropic_base_url": "https://api.minimax.io/anthropic", + }, + "cn_zh": { + "openai_base_url": "https://api.minimaxi.com/v1", + "anthropic_base_url": "https://api.minimaxi.com/anthropic", + }, +} + +# Current MiniMax text models. ``MiniMax-M3`` is the default; ``MiniMax-M2.7`` is +# also supported via AGENT_MODEL. Context windows are in tokens. +MINIMAX_MODELS = { + "MiniMax-M3": {"context_window": 1000000}, + "MiniMax-M2.7": {"context_window": 204800}, +} +MINIMAX_DEFAULT_MODEL = "MiniMax-M3" + + +def _minimax_region(): + """Resolve the configured MiniMax region and return its base URLs.""" + region = os.getenv("AGENT_MINIMAX_REGION", "global_en").lower() + if region not in MINIMAX_REGIONS: + raise ValueError(f"unknown MiniMax region: {region}") + return region + + def llm_available(): """True iff provider + key are configured. Validation / dream cycle check this before making calls so they degrade gracefully offline.""" @@ -10,9 +41,45 @@ def llm_available(): return bool(os.getenv("ANTHROPIC_API_KEY")) if provider == "openai": return bool(os.getenv("OPENAI_API_KEY")) + if provider == "minimax": + return bool(os.getenv("MINIMAX_API_KEY")) return False +def _call_minimax(system, user, *, temperature, max_tokens, model): + region = _minimax_region() + base_urls = MINIMAX_REGIONS[region] + model = model or os.getenv("AGENT_MODEL", MINIMAX_DEFAULT_MODEL) + if model not in MINIMAX_MODELS: + raise ValueError( + f"unknown MiniMax model: {model}. " + f"Supported: {', '.join(MINIMAX_MODELS)}" + ) + api_key = os.getenv("MINIMAX_API_KEY", "") + wire = os.getenv("AGENT_MINIMAX_WIRE", "openai").lower() + if wire == "anthropic": + from anthropic import Anthropic + c = Anthropic(api_key=api_key, base_url=base_urls["anthropic_base_url"]) + r = c.messages.create( + model=model, + max_tokens=max_tokens, temperature=temperature, + system=system, + messages=[{"role": "user", "content": user}], + ) + return r.content[0].text + if wire == "openai": + from openai import OpenAI + c = OpenAI(api_key=api_key, base_url=base_urls["openai_base_url"]) + r = c.chat.completions.create( + model=model, + temperature=temperature, + messages=[{"role": "system", "content": system}, + {"role": "user", "content": user}], + ) + return r.choices[0].message.content + raise ValueError(f"unknown MiniMax wire: {wire}") + + def call_model(system, user, *, temperature=0.3, max_tokens=4096, model=None): provider = os.getenv("AGENT_PROVIDER", "anthropic").lower() if provider == "anthropic": @@ -35,4 +102,9 @@ def call_model(system, user, *, temperature=0.3, max_tokens=4096, model=None): {"role": "user", "content": user}], ) return r.choices[0].message.content + if provider == "minimax": + return _call_minimax( + system, user, + temperature=temperature, max_tokens=max_tokens, model=model, + ) raise ValueError(f"unknown provider: {provider}") diff --git a/.env.example b/.env.example index 249fa1c..1ec6546 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,15 @@ # Pick one provider ANTHROPIC_API_KEY=sk-ant-... # OPENAI_API_KEY=sk-... +# MINIMAX_API_KEY=... # Optional overrides AGENT_PROVIDER=anthropic AGENT_MODEL=claude-sonnet-4-5 AGENT_MAX_CONTEXT=128000 + +# MiniMax provider (OpenAI- and Anthropic-compatible endpoints) +# AGENT_PROVIDER=minimax +# AGENT_MODEL=MiniMax-M3 +# AGENT_MINIMAX_REGION=global_en # or cn_zh +# AGENT_MINIMAX_WIRE=openai # or anthropic diff --git a/adapters/standalone-python/README.md b/adapters/standalone-python/README.md index d2db321..50cca0e 100644 --- a/adapters/standalone-python/README.md +++ b/adapters/standalone-python/README.md @@ -30,6 +30,13 @@ export AGENT_MODEL=claude-sonnet-4-5 # or export AGENT_PROVIDER=openai export AGENT_MODEL=gpt-4o + +# or +export AGENT_PROVIDER=minimax +export AGENT_MODEL=MiniMax-M3 +export MINIMAX_API_KEY=... +# Optional: AGENT_MINIMAX_REGION=global_en|cn_zh (default global_en) +# Optional: AGENT_MINIMAX_WIRE=openai|anthropic (default openai) ``` ## Cron the dream cycle diff --git a/tests/test_llm_provider.py b/tests/test_llm_provider.py new file mode 100644 index 0000000..02fe0ab --- /dev/null +++ b/tests/test_llm_provider.py @@ -0,0 +1,152 @@ +"""Tests for the shared LLM helper, focused on the MiniMax provider wiring.""" +from __future__ import annotations + +import os +import sys +import unittest +from pathlib import Path +from unittest import mock +from types import SimpleNamespace as _NS # noqa: F401 + +ROOT = Path(__file__).resolve().parents[1] +HARNESS = ROOT / ".agent" / "harness" +sys.path.insert(0, str(HARNESS)) + +import llm # noqa: E402 (path set up above) + + +class _Recorder: + """Captures constructor kwargs for fake SDK clients.""" + + def __init__(self): + self.kwargs = None + self.calls = [] + + def factory(self): + outer = self + + class FakeClient: + def __init__(self, **kwargs): + outer.kwargs = kwargs + + class messages: + @staticmethod + def create(**kwargs): + outer.calls.append(("anthropic", kwargs)) + return _NS(content=[_NS(text="ok-anthropic")]) + + class chat: + class completions: + @staticmethod + def create(**kwargs): + outer.calls.append(("openai", kwargs)) + return _NS( + choices=[_NS(message=_NS(content="ok-openai"))] + ) + + return FakeClient + + +class MiniMaxProviderTest(unittest.TestCase): + def setUp(self): + # Start from a clean env per test. + for key in ( + "AGENT_PROVIDER", "AGENT_MODEL", "AGENT_MINIMAX_REGION", + "AGENT_MINIMAX_WIRE", "MINIMAX_API_KEY", + "ANTHROPIC_API_KEY", "OPENAI_API_KEY", + ): + os.environ.pop(key, None) + + def test_regions_and_models_match_target_config(self): + self.assertEqual( + set(llm.MINIMAX_REGIONS), {"global_en", "cn_zh"} + ) + self.assertEqual( + llm.MINIMAX_REGIONS["global_en"]["openai_base_url"], + "https://api.minimax.io/v1", + ) + self.assertEqual( + llm.MINIMAX_REGIONS["global_en"]["anthropic_base_url"], + "https://api.minimax.io/anthropic", + ) + self.assertEqual( + llm.MINIMAX_REGIONS["cn_zh"]["openai_base_url"], + "https://api.minimaxi.com/v1", + ) + self.assertEqual( + llm.MINIMAX_REGIONS["cn_zh"]["anthropic_base_url"], + "https://api.minimaxi.com/anthropic", + ) + self.assertEqual(set(llm.MINIMAX_MODELS), {"MiniMax-M3", "MiniMax-M2.7"}) + self.assertEqual(llm.MINIMAX_MODELS["MiniMax-M3"]["context_window"], 1000000) + self.assertEqual(llm.MINIMAX_MODELS["MiniMax-M2.7"]["context_window"], 204800) + self.assertEqual(llm.MINIMAX_DEFAULT_MODEL, "MiniMax-M3") + + def test_llm_available_requires_minimax_key(self): + os.environ["AGENT_PROVIDER"] = "minimax" + self.assertFalse(llm.llm_available()) + os.environ["MINIMAX_API_KEY"] = "stub-key" + self.assertTrue(llm.llm_available()) + + def test_call_model_openai_wire_global(self): + os.environ["AGENT_PROVIDER"] = "minimax" + os.environ["MINIMAX_API_KEY"] = "stub-key" + os.environ["AGENT_MINIMAX_REGION"] = "global_en" + os.environ["AGENT_MINIMAX_WIRE"] = "openai" + rec = _Recorder() + with mock.patch.dict(sys.modules, {"openai": _NS(OpenAI=rec.factory())}): + out = llm.call_model("sys", "hi", model="MiniMax-M3") + self.assertEqual(out, "ok-openai") + self.assertEqual(rec.kwargs["base_url"], "https://api.minimax.io/v1") + self.assertEqual(rec.kwargs["api_key"], "stub-key") + wire, kwargs = rec.calls[0] + self.assertEqual(wire, "openai") + self.assertEqual(kwargs["model"], "MiniMax-M3") + + def test_call_model_anthropic_wire_cn(self): + os.environ["AGENT_PROVIDER"] = "minimax" + os.environ["MINIMAX_API_KEY"] = "stub-key" + os.environ["AGENT_MINIMAX_REGION"] = "cn_zh" + os.environ["AGENT_MINIMAX_WIRE"] = "anthropic" + rec = _Recorder() + with mock.patch.dict(sys.modules, {"anthropic": _NS(Anthropic=rec.factory())}): + out = llm.call_model("sys", "hi", model="MiniMax-M2.7") + self.assertEqual(out, "ok-anthropic") + self.assertEqual(rec.kwargs["base_url"], "https://api.minimaxi.com/anthropic") + self.assertEqual(rec.kwargs["api_key"], "stub-key") + wire, kwargs = rec.calls[0] + self.assertEqual(wire, "anthropic") + self.assertEqual(kwargs["model"], "MiniMax-M2.7") + + def test_default_model_is_m3(self): + os.environ["AGENT_PROVIDER"] = "minimax" + os.environ["MINIMAX_API_KEY"] = "stub-key" + rec = _Recorder() + with mock.patch.dict(sys.modules, {"openai": _NS(OpenAI=rec.factory())}): + llm.call_model("sys", "hi") + _, kwargs = rec.calls[0] + self.assertEqual(kwargs["model"], "MiniMax-M3") + + def test_unknown_region_raises(self): + os.environ["AGENT_PROVIDER"] = "minimax" + os.environ["MINIMAX_API_KEY"] = "stub-key" + os.environ["AGENT_MINIMAX_REGION"] = "mars" + with self.assertRaises(ValueError): + llm.call_model("sys", "hi", model="MiniMax-M3") + + def test_unknown_model_raises(self): + os.environ["AGENT_PROVIDER"] = "minimax" + os.environ["MINIMAX_API_KEY"] = "stub-key" + with self.assertRaises(ValueError): + llm.call_model("sys", "hi", model="nope") + + def test_unknown_wire_raises(self): + os.environ["AGENT_PROVIDER"] = "minimax" + os.environ["MINIMAX_API_KEY"] = "stub-key" + os.environ["AGENT_MINIMAX_WIRE"] = "grpc" + with self.assertRaises(ValueError): + llm.call_model("sys", "hi", model="MiniMax-M3") + + +if __name__ == "__main__": + unittest.main()