diff --git a/README.md b/README.md index c7b1df6..8cedc5b 100644 --- a/README.md +++ b/README.md @@ -69,10 +69,21 @@ Then you need to set the weights path in the file `jarvis/steveI/path.py`. ## Usage -You need to set the environment variable `TMPDIR` and `OPENAI_API_KEY` first. +You need to set the environment variable `TMPDIR` and your MiniMax API key first. ```bash export TMPDIR=/tmp -export OPENAI_API_KEY="sk-******" +export MINIMAX_API_KEY="******" +``` + +The planner talks to the MiniMax OpenAI-compatible API. You can optionally +select the region, model, or an explicit base URL: +```bash +# Region selects the endpoint: "global_en" (default) or "cn_zh". +export MINIMAX_REGION="global_en" +# Model id: "MiniMax-M3" (default) or "MiniMax-M2.7". +export MINIMAX_MODEL="MiniMax-M3" +# Optional: override the base URL directly (takes precedence over MINIMAX_REGION). +export MINIMAX_BASE_URL="https://api.minimax.io/v1" ``` ### Learning with dynamic memory (Coming Soon) diff --git a/jarvis/assembly/base.py b/jarvis/assembly/base.py index 21fe949..d7312f2 100644 --- a/jarvis/assembly/base.py +++ b/jarvis/assembly/base.py @@ -1,7 +1,57 @@ from jarvis.utils import write_video +import os from openai import OpenAI -client = OpenAI() + +# MiniMax exposes an OpenAI-compatible API. The global and mainland-China +# endpoints share the same request/response schema but live on different hosts, +# so the client is configured by region instead of being hardcoded. +MINIMAX_BASE_URLS = { + "global_en": "https://api.minimax.io/v1", + "cn_zh": "https://api.minimaxi.com/v1", +} +DEFAULT_REGION = "global_en" + +# Supported MiniMax text models; the first entry is used as the default. +MINIMAX_MODELS = ["MiniMax-M3", "MiniMax-M2.7"] +DEFAULT_MODEL = MINIMAX_MODELS[0] + + +def get_base_url(): + """Resolve the MiniMax OpenAI-compatible base URL. + + ``MINIMAX_BASE_URL`` takes precedence when set; otherwise ``MINIMAX_REGION`` + selects between the global (``global_en``) and mainland-China (``cn_zh``) + endpoints, defaulting to the global endpoint. + """ + base_url = os.environ.get("MINIMAX_BASE_URL") + if base_url: + return base_url + region = os.environ.get("MINIMAX_REGION", DEFAULT_REGION) + return MINIMAX_BASE_URLS.get(region, MINIMAX_BASE_URLS[DEFAULT_REGION]) + + +def get_model(): + """Resolve the MiniMax text model id from ``MINIMAX_MODEL``. + + Defaults to :data:`DEFAULT_MODEL` when the variable is unset. + """ + return os.environ.get("MINIMAX_MODEL", DEFAULT_MODEL) + + +def build_client(): + """Create an OpenAI-compatible client pointed at the MiniMax API. + + The API key is read from ``MINIMAX_API_KEY`` and falls back to + ``OPENAI_API_KEY`` for backward compatibility. + """ + return OpenAI( + api_key=os.environ.get("MINIMAX_API_KEY") or os.environ.get("OPENAI_API_KEY"), + base_url=get_base_url(), + ) + + +client = build_client() import json from jarvis.assets import TASKS_FILE, TAG_ITEMS_FILE, SPAWN_FILE, SKILL_FILE diff --git a/jarvis/assembly/core.py b/jarvis/assembly/core.py index ec612eb..59ea1a3 100644 --- a/jarvis/assembly/core.py +++ b/jarvis/assembly/core.py @@ -1,4 +1,4 @@ -from jarvis.assembly.base import client +from jarvis.assembly.base import client, get_model from jarvis.assembly.base import skills import random @@ -59,7 +59,7 @@ def get_skill(task, info): print("query: ", query) response = client.chat.completions.create( - model="gpt-3.5-turbo", + model=get_model(), messages=[ { "role": "system", @@ -107,8 +107,8 @@ def get_skill(task, info): return skills[task][action_index-1] class JARVIS: - def __init__(self, model = 'gpt-3.5-turbo'): - self.model = model + def __init__(self, model=None): + self.model = model if model is not None else get_model() # TODO: add online generating plans function # zhwang4ai: release online planning agent in the next version diff --git a/tests/test_minimax_provider.py b/tests/test_minimax_provider.py new file mode 100644 index 0000000..065c1c4 --- /dev/null +++ b/tests/test_minimax_provider.py @@ -0,0 +1,57 @@ +"""Tests for the configurable MiniMax provider setup in ``jarvis.assembly.base``.""" + +import importlib + +import pytest + + +@pytest.fixture(autouse=True) +def _clean_env(monkeypatch): + # A placeholder key lets the OpenAI-compatible client be constructed at + # import time without contacting the network. + monkeypatch.setenv("MINIMAX_API_KEY", "test-key") + for var in ("MINIMAX_BASE_URL", "MINIMAX_REGION", "MINIMAX_MODEL", "OPENAI_API_KEY"): + monkeypatch.delenv(var, raising=False) + + +def _load_base(): + import jarvis.assembly.base as base + + return importlib.reload(base) + + +def test_default_region_is_global(): + base = _load_base() + assert base.DEFAULT_REGION == "global_en" + assert base.get_base_url() == "https://api.minimax.io/v1" + + +def test_cn_region_uses_mainland_endpoint(monkeypatch): + monkeypatch.setenv("MINIMAX_REGION", "cn_zh") + base = _load_base() + assert base.get_base_url() == "https://api.minimaxi.com/v1" + + +def test_explicit_base_url_overrides_region(monkeypatch): + monkeypatch.setenv("MINIMAX_REGION", "cn_zh") + monkeypatch.setenv("MINIMAX_BASE_URL", "https://example.test/v1") + base = _load_base() + assert base.get_base_url() == "https://example.test/v1" + + +def test_default_model(): + base = _load_base() + assert base.MINIMAX_MODELS[:2] == ["MiniMax-M3", "MiniMax-M2.7"] + assert base.DEFAULT_MODEL == "MiniMax-M3" + assert base.get_model() == "MiniMax-M3" + + +def test_model_selection(monkeypatch): + monkeypatch.setenv("MINIMAX_MODEL", "MiniMax-M2.7") + base = _load_base() + assert base.get_model() == "MiniMax-M2.7" + + +def test_client_points_at_resolved_base_url(): + base = _load_base() + assert str(base.client.base_url).rstrip("/") == "https://api.minimax.io/v1"