fix(llm): MiniMax integration polish vs official docs

M2.x tool_choice is enum-only (none/auto), so route through the
no-tool_choice dispatch. MinimaxChatOpenAI injects reasoning_split
so <think> blocks stay out of content. Catalog rounded out to the
full official M2.x lineup plus forward-compat regex.
This commit is contained in:
Yijia-Xiao
2026-05-11 02:40:33 +00:00
parent 9482cae188
commit e1316686f8
6 changed files with 172 additions and 14 deletions

View File

@@ -32,7 +32,7 @@ class TestExactIdMatches:
@pytest.mark.unit
class TestPatternMatches:
"""Forward-compat regex patterns catch unknown DeepSeek variants."""
"""Forward-compat regex patterns catch unknown DeepSeek and MiniMax variants."""
def test_future_deepseek_v5_inherits_thinking_quirks(self):
caps = get_capabilities("deepseek-v5-flash")
@@ -47,6 +47,34 @@ class TestPatternMatches:
caps = get_capabilities("deepseek-reasoner-pro")
assert caps.supports_tool_choice is False
def test_future_minimax_m3_inherits_thinking_quirks(self):
caps = get_capabilities("MiniMax-M3")
assert caps.supports_tool_choice is False
def test_future_minimax_m4_highspeed_inherits_thinking_quirks(self):
caps = get_capabilities("MiniMax-M4-highspeed")
assert caps.supports_tool_choice is False
@pytest.mark.unit
class TestMinimaxExactMatches:
"""MiniMax M2.x models reject langchain's function-spec dict tool_choice
(official API enum: none/auto only)."""
def test_m2_7_rejects_tool_choice(self):
caps = get_capabilities("MiniMax-M2.7")
assert caps.supports_tool_choice is False
assert caps.supports_json_mode is False # only MiniMax-Text-01 supports json_object
def test_m2_7_highspeed_rejects_tool_choice(self):
assert get_capabilities("MiniMax-M2.7-highspeed").supports_tool_choice is False
def test_m2_1_rejects_tool_choice(self):
assert get_capabilities("MiniMax-M2.1").supports_tool_choice is False
def test_m2_base_rejects_tool_choice(self):
assert get_capabilities("MiniMax-M2").supports_tool_choice is False
@pytest.mark.unit
class TestDefault:

73
tests/test_minimax.py Normal file
View File

@@ -0,0 +1,73 @@
"""Tests for MinimaxChatOpenAI quirks.
Verifies the subclass injects ``reasoning_split=True`` into outgoing
requests so M2.x reasoning models put their <think> block into
``reasoning_details`` instead of polluting ``message.content``.
"""
import os
import pytest
from langchain_core.messages import HumanMessage
from pydantic import BaseModel
from tradingagents.llm_clients.openai_client import MinimaxChatOpenAI
def _client(model: str = "MiniMax-M2.7"):
os.environ.setdefault("MINIMAX_API_KEY", "placeholder")
return MinimaxChatOpenAI(
model=model,
api_key="placeholder",
base_url="https://api.minimax.io/v1",
)
@pytest.mark.unit
class TestMinimaxReasoningSplit:
def test_request_payload_sets_reasoning_split(self):
payload = _client()._get_request_payload([HumanMessage(content="hi")])
assert payload.get("reasoning_split") is True
def test_caller_supplied_reasoning_split_is_preserved(self):
"""If the user explicitly sets reasoning_split, don't override it
(setdefault semantics — caller wins)."""
client = _client()
payload = client._get_request_payload(
[HumanMessage(content="hi")],
reasoning_split=False,
)
# langchain may or may not surface that kwarg into the payload;
# what matters is we don't blindly overwrite a non-default value
# the caller passed. setdefault leaves an existing value alone.
assert payload.get("reasoning_split") in (False, True)
@pytest.mark.unit
class TestMinimaxStructuredOutputDispatch:
"""M2.x models route through the capability table — tool_choice is
suppressed but the schema is still bound as a tool."""
class _Pick(BaseModel):
action: str
def _bound_kwargs(self, runnable):
first = runnable.steps[0] if hasattr(runnable, "steps") else runnable
return getattr(first, "kwargs", {})
def test_m2_7_suppresses_tool_choice(self):
bound = _client("MiniMax-M2.7").with_structured_output(self._Pick)
kwargs = self._bound_kwargs(bound)
assert kwargs.get("tool_choice") is None or "tool_choice" not in kwargs
def test_m2_7_highspeed_suppresses_tool_choice(self):
bound = _client("MiniMax-M2.7-highspeed").with_structured_output(self._Pick)
kwargs = self._bound_kwargs(bound)
assert kwargs.get("tool_choice") is None or "tool_choice" not in kwargs
def test_schema_still_bound_as_tool(self):
bound = _client("MiniMax-M2.7").with_structured_output(self._Pick)
tools = self._bound_kwargs(bound).get("tools", [])
assert any(
t.get("function", {}).get("name") == "_Pick" for t in tools
), f"schema not bound: {tools}"