From fa84cca7c08e3ec259e6fec9314e2b3c9dc55da7 Mon Sep 17 00:00:00 2001 From: w33d Date: Sat, 11 Jul 2026 19:05:09 +0800 Subject: [PATCH] fix: handle max_tokens for NVIDIA MiniMax M3 --- .../core/provider/sources/openai_source.py | 26 +++++-- tests/test_openai_source.py | 67 ++++++++++++++++++- 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index a49003af17..b1594d608a 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -404,10 +404,26 @@ def _ollama_disable_thinking_enabled(self) -> bool: return value.strip().lower() in {"1", "true", "yes", "on"} return bool(value) - def _apply_provider_specific_extra_body_overrides( - self, extra_body: dict[str, Any] + def _apply_provider_specific_request_overrides( + self, + payloads: dict[str, Any], + extra_body: dict[str, Any], ) -> None: - if self.provider_config.get("provider") != "ollama": + provider = self.provider_config.get("provider") + model = str(payloads.get("model", "")).lower() + + # NVIDIA's hosted MiniMax M3 endpoint can return empty choices when + # max_tokens is omitted (#9206). Scope the compatibility default to + # that model; other NVIDIA models have different token limits. + if ( + provider == "nvidia" + and model == "minimaxai/minimax-m3" + and "max_tokens" not in payloads + and "max_tokens" not in extra_body + ): + payloads["max_tokens"] = 8192 + + if provider != "ollama": return if not self._ollama_disable_thinking_enabled(): return @@ -543,7 +559,7 @@ async def _query( custom_extra_body = self.provider_config.get("custom_extra_body", {}) if isinstance(custom_extra_body, dict): extra_body.update(custom_extra_body) - self._apply_provider_specific_extra_body_overrides(extra_body) + self._apply_provider_specific_request_overrides(payloads, extra_body) model = payloads.get("model", "").lower() @@ -603,7 +619,7 @@ async def _query_stream( to_del.append(key) for key in to_del: del payloads[key] - self._apply_provider_specific_extra_body_overrides(extra_body) + self._apply_provider_specific_request_overrides(payloads, extra_body) self._sanitize_assistant_messages(payloads) diff --git a/tests/test_openai_source.py b/tests/test_openai_source.py index b8262090e4..a45a232938 100644 --- a/tests/test_openai_source.py +++ b/tests/test_openai_source.py @@ -1272,7 +1272,7 @@ async def fake_resolve_media_ref_to_base64_data( @pytest.mark.asyncio -async def test_apply_provider_specific_extra_body_overrides_disables_ollama_thinking(): +async def test_apply_provider_specific_request_overrides_disables_ollama_thinking(): provider = _make_provider( { "provider": "ollama", @@ -1287,7 +1287,7 @@ async def test_apply_provider_specific_extra_body_overrides_disables_ollama_thin "temperature": 0.2, } - provider._apply_provider_specific_extra_body_overrides(extra_body) + provider._apply_provider_specific_request_overrides({}, extra_body) assert extra_body["reasoning_effort"] == "none" assert "reasoning" not in extra_body @@ -1297,6 +1297,69 @@ async def test_apply_provider_specific_extra_body_overrides_disables_ollama_thin await provider.terminate() +@pytest.mark.asyncio +async def test_provider_specific_request_overrides_sets_minimax_m3_max_tokens(): + provider = _make_provider({"provider": "nvidia"}) + try: + payloads = {"model": "minimaxai/minimax-m3"} + extra_body = {"temperature": 0.2} + + provider._apply_provider_specific_request_overrides(payloads, extra_body) + + assert payloads["max_tokens"] == 8192 + assert extra_body == {"temperature": 0.2} + finally: + await provider.terminate() + + +@pytest.mark.asyncio +async def test_minimax_m3_max_tokens_preserves_custom_extra_body_value(): + provider = _make_provider({"provider": "nvidia"}) + try: + payloads = {"model": "minimaxai/minimax-m3"} + extra_body = {"max_tokens": 4096} + + provider._apply_provider_specific_request_overrides(payloads, extra_body) + + assert "max_tokens" not in payloads + assert extra_body["max_tokens"] == 4096 + finally: + await provider.terminate() + + +@pytest.mark.asyncio +async def test_minimax_m3_max_tokens_preserves_standard_payload_value(): + provider = _make_provider({"provider": "nvidia"}) + try: + payloads = { + "model": "minimaxai/minimax-m3", + "max_tokens": 2048, + } + extra_body = {} + + provider._apply_provider_specific_request_overrides(payloads, extra_body) + + assert payloads["max_tokens"] == 2048 + assert extra_body == {} + finally: + await provider.terminate() + + +@pytest.mark.asyncio +async def test_nvidia_request_does_not_set_max_tokens_for_other_models(): + provider = _make_provider({"provider": "nvidia"}) + try: + payloads = {"model": "nvidia/usdcode"} + extra_body = {} + + provider._apply_provider_specific_request_overrides(payloads, extra_body) + + assert "max_tokens" not in payloads + assert "max_tokens" not in extra_body + finally: + await provider.terminate() + + @pytest.mark.asyncio async def test_query_injects_reasoning_effort_none_for_ollama(monkeypatch): provider = _make_provider(