mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-18 09:51:59 +00:00
Follow up PR #46609's api.minimax.io reasoning report by moving the behavior out of the broad run_agent host gate and into the MiniMax provider profile. Only MiniMax-M3 on the documented OpenAI-compatible /v1 route gets reasoning_split/thinking/reasoning_effort; Anthropic-format MiniMax and non-M3 models keep their existing wire shapes. Co-authored-by: goku94123 <gooku94123@gmail.com>
232 lines
9 KiB
Python
232 lines
9 KiB
Python
"""Unit tests for the MiniMax provider profile.
|
|
|
|
Three MiniMax provider profiles (`minimax` direct API, `minimax-cn` China direct
|
|
API, `minimax-oauth` browser OAuth) all advertise a `default_aux_model` on
|
|
their `ProviderProfile`. The previous M2.7 / M2.7-highspeed values were
|
|
stale relative to the current frontier model (M3, released 2026-06-01) and
|
|
inconsistent with the `_PROVIDER_MODELS["minimax"]` catalog top entry in
|
|
`hermes_cli/models.py`.
|
|
|
|
This file pins the new defaults so the choice is reviewable and any future
|
|
revert shows up in a failing test rather than silent behavior drift.
|
|
|
|
Refs:
|
|
- Issue #36196: M3 support request
|
|
- PR #36205 (closed unmerged): Csrayz's M3 + 1M context work
|
|
- PR #36212 (open): adds M3 to `_PROVIDER_MODELS["minimax"]` catalog
|
|
- PR #6082: M2.7-highspeed → M2.7 for aux model (half-price fix)
|
|
- Commit 773a0faca: same profile-layer fix pattern for `deepseek`
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture(params=["minimax", "minimax-cn", "minimax-oauth"])
|
|
def minimax_profile(request):
|
|
"""Resolve each registered MiniMax profile.
|
|
|
|
Going through ``providers.get_provider_profile`` keeps the test honest —
|
|
if someone later replaces the registered class with a plain
|
|
``ProviderProfile``, every assertion below collapses.
|
|
"""
|
|
import model_tools # noqa: F401 -- triggers plugin discovery
|
|
import providers
|
|
|
|
profile = providers.get_provider_profile(request.param)
|
|
assert profile is not None, f"{request.param} provider profile must be registered"
|
|
return profile, request.param
|
|
|
|
|
|
class TestMinimaxAuxModelM3:
|
|
"""MiniMax profile aux model is the new frontier M3, not the stale M2.7.
|
|
|
|
The catalog top entry is ``MiniMax-M3`` in
|
|
``hermes_cli.models._PROVIDER_MODELS['minimax']`` and the
|
|
user-facing ``model.default`` for a Token-Plan install is M3,
|
|
so pinning the aux default to the same model keeps the runtime
|
|
consistent (same auth, same billing pool, same rate limits, no
|
|
surprise 2x-cost highspeed variant). M3 was released 2026-06-01
|
|
— picking it as the aux default matches the forward-looking
|
|
catalog order rather than the pre-M3 era.
|
|
"""
|
|
|
|
@pytest.mark.parametrize(
|
|
"provider_id,expected",
|
|
[
|
|
("minimax", "MiniMax-M3"),
|
|
("minimax-cn", "MiniMax-M3"),
|
|
# minimax-oauth sticks with M2.7: the OAuth / Coding Plan
|
|
# tier historically used -highspeed (PR #6082 collapsed that
|
|
# to plain M2.7 to avoid the 2x TPS surcharge). M3 is not on
|
|
# the OAuth/Coding Plan tier per platform docs as of this PR,
|
|
# so the safe choice is the cheapest generally-available
|
|
# M2.7 — matching PR #6082's intent.
|
|
("minimax-oauth", "MiniMax-M2.7"),
|
|
],
|
|
)
|
|
def test_profile_advertises_expected_aux_model(
|
|
self, provider_id, expected
|
|
):
|
|
import model_tools # noqa: F401
|
|
import providers
|
|
|
|
profile = providers.get_provider_profile(provider_id)
|
|
assert profile is not None
|
|
assert profile.default_aux_model == expected, (
|
|
f"{provider_id} default_aux_model drifted to "
|
|
f"{profile.default_aux_model!r}, expected {expected!r}"
|
|
)
|
|
|
|
def test_consumer_api_returns_non_empty_for_each_provider(self, minimax_profile):
|
|
from agent.auxiliary_client import _get_aux_model_for_provider
|
|
|
|
profile, provider_id = minimax_profile
|
|
resolved = _get_aux_model_for_provider(provider_id)
|
|
assert resolved != "", (
|
|
f"_get_aux_model_for_provider({provider_id!r}) returned empty — "
|
|
"the 'No auxiliary LLM provider configured' warning will fire on "
|
|
f"every {provider_id} session even though the profile advertises "
|
|
f"default_aux_model={profile.default_aux_model!r}"
|
|
)
|
|
assert resolved == profile.default_aux_model, (
|
|
f"_get_aux_model_for_provider({provider_id!r}) returned "
|
|
f"{resolved!r} but profile advertises {profile.default_aux_model!r} "
|
|
"— the consumer API and the profile have drifted out of sync"
|
|
)
|
|
|
|
|
|
class TestMinimaxAuxModelNotHighspeed:
|
|
"""Regression guard against re-introducing the M2.7-highspeed aux default.
|
|
|
|
PR #6082 collapsed the highspeed aux choice to plain M2.7 because the
|
|
highspeed variant costs 2x with no real benefit for compression / vision /
|
|
session-search aux tasks. None of the three MiniMax profiles should
|
|
silently re-introduce that 2x-cost path.
|
|
"""
|
|
|
|
@pytest.mark.parametrize("provider_id", ["minimax", "minimax-cn", "minimax-oauth"])
|
|
def test_default_aux_model_is_not_highspeed(self, provider_id):
|
|
import model_tools # noqa: F401
|
|
import providers
|
|
|
|
profile = providers.get_provider_profile(provider_id)
|
|
assert profile is not None
|
|
assert "highspeed" not in profile.default_aux_model.lower(), (
|
|
f"{provider_id} default_aux_model={profile.default_aux_model!r} "
|
|
"is a -highspeed variant — that costs 2x for the same model and "
|
|
"broke #4082 the first time. Revert to plain M2.7 or M3."
|
|
)
|
|
|
|
|
|
class TestMinimaxM3OpenAIReasoningWireShape:
|
|
"""MiniMax-M3 on api.minimax.io/v1 gets MiniMax's OpenAI-compatible knobs."""
|
|
|
|
def test_m3_openai_route_requests_reasoning_split_by_default(self):
|
|
import model_tools # noqa: F401
|
|
import providers
|
|
|
|
profile = providers.get_provider_profile("minimax")
|
|
assert profile is not None
|
|
extra_body, top_level = profile.build_api_kwargs_extras(
|
|
reasoning_config=None,
|
|
model="MiniMax-M3",
|
|
base_url="https://api.minimax.io/v1",
|
|
)
|
|
assert extra_body == {"reasoning_split": True}
|
|
assert top_level == {}
|
|
|
|
def test_m3_openai_route_maps_explicit_effort_to_adaptive_only(self):
|
|
import model_tools # noqa: F401
|
|
import providers
|
|
|
|
profile = providers.get_provider_profile("minimax")
|
|
assert profile is not None
|
|
extra_body, top_level = profile.build_api_kwargs_extras(
|
|
reasoning_config={"enabled": True, "effort": "high"},
|
|
model="MiniMax-M3",
|
|
base_url="https://api.minimax.io/v1",
|
|
)
|
|
assert extra_body == {
|
|
"reasoning_split": True,
|
|
"thinking": {"type": "adaptive"},
|
|
}
|
|
assert top_level == {}
|
|
|
|
def test_m3_openai_route_does_not_send_reasoning_effort(self):
|
|
import model_tools # noqa: F401
|
|
import providers
|
|
|
|
profile = providers.get_provider_profile("minimax")
|
|
assert profile is not None
|
|
extra_body, _top_level = profile.build_api_kwargs_extras(
|
|
reasoning_config={"enabled": True, "effort": "xhigh"},
|
|
model="MiniMax-M3",
|
|
base_url="https://api.minimax.io/v1/",
|
|
)
|
|
assert extra_body == {
|
|
"reasoning_split": True,
|
|
"thinking": {"type": "adaptive"},
|
|
}
|
|
|
|
def test_m3_openai_route_can_disable_thinking(self):
|
|
import model_tools # noqa: F401
|
|
import providers
|
|
|
|
profile = providers.get_provider_profile("minimax")
|
|
assert profile is not None
|
|
extra_body, top_level = profile.build_api_kwargs_extras(
|
|
reasoning_config={"enabled": False, "effort": "high"},
|
|
model="MiniMax-M3",
|
|
base_url="https://api.minimax.io/v1",
|
|
)
|
|
assert extra_body == {
|
|
"reasoning_split": True,
|
|
"thinking": {"type": "disabled"},
|
|
}
|
|
assert top_level == {}
|
|
|
|
@pytest.mark.parametrize(
|
|
"model,base_url",
|
|
[
|
|
("MiniMax-M2.7", "https://api.minimax.io/v1"),
|
|
("MiniMax-M3", "https://api.minimax.io/anthropic"),
|
|
("MiniMax-M3", "https://api.minimaxi.com/v1"),
|
|
],
|
|
)
|
|
def test_non_m3_or_non_global_openai_routes_emit_no_openai_reasoning_knobs(
|
|
self, model, base_url
|
|
):
|
|
import model_tools # noqa: F401
|
|
import providers
|
|
|
|
profile = providers.get_provider_profile("minimax")
|
|
assert profile is not None
|
|
extra_body, top_level = profile.build_api_kwargs_extras(
|
|
reasoning_config={"enabled": True, "effort": "high"},
|
|
model=model,
|
|
base_url=base_url,
|
|
)
|
|
assert extra_body == {}
|
|
assert top_level == {}
|
|
|
|
def test_transport_threads_base_url_to_profile(self):
|
|
import model_tools # noqa: F401
|
|
import providers
|
|
from agent.transports.chat_completions import ChatCompletionsTransport
|
|
|
|
profile = providers.get_provider_profile("minimax")
|
|
assert profile is not None
|
|
kwargs = ChatCompletionsTransport().build_kwargs(
|
|
model="MiniMax-M3",
|
|
messages=[{"role": "user", "content": "ping"}],
|
|
tools=None,
|
|
provider_profile=profile,
|
|
reasoning_config={"enabled": True, "effort": "medium"},
|
|
base_url="https://api.minimax.io/v1",
|
|
)
|
|
assert kwargs["extra_body"] == {
|
|
"reasoning_split": True,
|
|
"thinking": {"type": "adaptive"},
|
|
}
|