From 8694bd070d6d8db787b57d7c0e3d88b2ff7ffedd Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Sun, 31 May 2026 06:20:55 +0000 Subject: [PATCH] fix(llm): send MiniMax reasoning_split via extra_body so the openai SDK accepts it (#826) --- tests/test_minimax.py | 28 +++++++--------------- tradingagents/llm_clients/openai_client.py | 22 ++++++++++------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/tests/test_minimax.py b/tests/test_minimax.py index f33d2e7b7..62f5072f6 100644 --- a/tests/test_minimax.py +++ b/tests/test_minimax.py @@ -25,34 +25,22 @@ def _client(model: str = "MiniMax-M2.7"): @pytest.mark.unit class TestMinimaxReasoningSplit: - def test_request_payload_sets_reasoning_split(self): + def test_reasoning_split_sent_via_extra_body_not_top_level(self): + # Must be in extra_body, not top-level: the openai SDK validates + # top-level params and rejects unknown ones like reasoning_split (#826). 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) + assert payload.get("extra_body", {}).get("reasoning_split") is True + assert "reasoning_split" not in payload # never top-level def test_non_reasoning_minimax_does_not_inject_reasoning_split(self): """Coding Plan / MiniMax-Text-01 / any non-M2-prefixed model must NOT - receive reasoning_split — the openai SDK rejects unknown kwargs with - TypeError (#826).""" + receive reasoning_split at all (top-level or extra_body) (#826).""" for model in ("minimax-text-01", "MiniMax-Coding-Plan"): payload = _client(model)._get_request_payload( [HumanMessage(content="hi")] ) - assert "reasoning_split" not in payload, ( - f"{model!r} payload unexpectedly contains reasoning_split" - ) + assert "reasoning_split" not in payload + assert "reasoning_split" not in payload.get("extra_body", {}) @pytest.mark.unit diff --git a/tradingagents/llm_clients/openai_client.py b/tradingagents/llm_clients/openai_client.py index 89462b4c7..0d78893a0 100644 --- a/tradingagents/llm_clients/openai_client.py +++ b/tradingagents/llm_clients/openai_client.py @@ -114,14 +114,15 @@ class MinimaxChatOpenAI(NormalizedChatOpenAI): M2.x reasoning models embed ``...`` blocks directly in ``message.content`` by default, which would pollute saved reports. - Per platform.minimax.io/docs/api-reference/text-openai-api, setting - ``reasoning_split=True`` in the request body redirects the thinking - block into ``reasoning_details`` so ``content`` stays clean. + Per platform.minimax.io/docs/api-reference/text-openai-api, + ``reasoning_split=True`` redirects the thinking block into + ``reasoning_details`` so ``content`` stays clean. It is sent via + ``extra_body`` (not a top-level kwarg) because the openai SDK validates + top-level params and rejects unknown ones like reasoning_split (#826). - The flag is gated by ``ModelCapabilities.requires_reasoning_split`` - because non-reasoning MiniMax endpoints (Coding Plan, MiniMax-Text-01) - reject the parameter via the openai SDK's strict kwarg validation - (#826). + The flag is gated by ``ModelCapabilities.requires_reasoning_split`` so + only M2.x reasoning models receive it; non-reasoning MiniMax endpoints + (Coding Plan, MiniMax-Text-01) never see it. Tool-choice handling for M2.x — those models accept only the string enum ``{"none", "auto"}`` and reject langchain's function-spec dict — @@ -132,7 +133,12 @@ class MinimaxChatOpenAI(NormalizedChatOpenAI): def _get_request_payload(self, input_, *, stop=None, **kwargs): payload = super()._get_request_payload(input_, stop=stop, **kwargs) if get_capabilities(self.model_name).requires_reasoning_split: - payload.setdefault("reasoning_split", True) + # Pass via extra_body, not as a top-level kwarg: the openai SDK + # (>=1.56) validates top-level params against Completions.create + # and rejects unknown ones like reasoning_split (#826). extra_body + # is forwarded into the request body untouched. + extra_body = payload.setdefault("extra_body", {}) + extra_body.setdefault("reasoning_split", True) return payload