From 3f0f4a04a951bd5d64ba1f1c00be4f01945774fd Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sun, 15 Mar 2026 20:42:07 -0700 Subject: [PATCH] fix(agent): skip reasoning extra_body for unsupported OpenRouter models (#1485) * fix(agent): skip reasoning extra_body for models that don't support it Sending reasoning config to models like MiniMax or Nvidia via OpenRouter causes a 400 BadRequestError. Previously, reasoning extra_body was sent to all OpenRouter and Nous models unconditionally. Fix: only send reasoning extra_body when the model slug starts with a known reasoning-capable prefix (deepseek/, anthropic/, openai/, x-ai/, google/gemini-2, qwen/qwen3) or when using Nous Portal directly. Applies to both the main API call path (_build_api_kwargs) and the conversation summary path. Fixes #1083 * test(agent): cover reasoning extra_body gating --------- Co-authored-by: ygd58 --- run_agent.py | 32 ++++++++++++++++++++++++++++---- tests/test_run_agent.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/run_agent.py b/run_agent.py index c7f3f983e..7ee22876d 100644 --- a/run_agent.py +++ b/run_agent.py @@ -3302,8 +3302,7 @@ class AIAgent: extra_body["provider"] = provider_preferences _is_nous = "nousresearch" in self.base_url.lower() - _is_mistral = "api.mistral.ai" in self.base_url.lower() - if (_is_openrouter or _is_nous) and not _is_mistral: + if self._supports_reasoning_extra_body(): if self.reasoning_config is not None: rc = dict(self.reasoning_config) # Nous Portal requires reasoning enabled — don't send @@ -3327,6 +3326,32 @@ class AIAgent: return api_kwargs + def _supports_reasoning_extra_body(self) -> bool: + """Return True when reasoning extra_body is safe to send for this route/model. + + OpenRouter forwards unknown extra_body fields to upstream providers. + Some providers/routes reject `reasoning` with 400s, so gate it to + known reasoning-capable model families and direct Nous Portal. + """ + base_url = (self.base_url or "").lower() + if "nousresearch" in base_url: + return True + if "openrouter" not in base_url: + return False + if "api.mistral.ai" in base_url: + return False + + model = (self.model or "").lower() + reasoning_model_prefixes = ( + "deepseek/", + "anthropic/", + "openai/", + "x-ai/", + "google/gemini-2", + "qwen/qwen3", + ) + return any(model.startswith(prefix) for prefix in reasoning_model_prefixes) + def _build_assistant_message(self, assistant_message, finish_reason: str) -> dict: """Build a normalized assistant message dict from an API response message. @@ -4265,9 +4290,8 @@ class AIAgent: api_messages.insert(sys_offset + idx, pfm.copy()) summary_extra_body = {} - _is_openrouter = "openrouter" in self.base_url.lower() _is_nous = "nousresearch" in self.base_url.lower() - if _is_openrouter or _is_nous: + if self._supports_reasoning_extra_body(): if self.reasoning_config is not None: summary_extra_body["reasoning"] = self.reasoning_config else: diff --git a/tests/test_run_agent.py b/tests/test_run_agent.py index 54571fd74..1d54c9d01 100644 --- a/tests/test_run_agent.py +++ b/tests/test_run_agent.py @@ -612,6 +612,25 @@ class TestBuildApiKwargs: kwargs = agent._build_api_kwargs(messages) assert kwargs["extra_body"]["reasoning"] == {"enabled": False} + def test_reasoning_not_sent_for_unsupported_openrouter_model(self, agent): + agent.model = "minimax/minimax-m2.5" + messages = [{"role": "user", "content": "hi"}] + kwargs = agent._build_api_kwargs(messages) + assert "reasoning" not in kwargs.get("extra_body", {}) + + def test_reasoning_sent_for_supported_openrouter_model(self, agent): + agent.model = "qwen/qwen3.5-plus-02-15" + messages = [{"role": "user", "content": "hi"}] + kwargs = agent._build_api_kwargs(messages) + assert kwargs["extra_body"]["reasoning"]["effort"] == "medium" + + def test_reasoning_sent_for_nous_route(self, agent): + agent.base_url = "https://inference-api.nousresearch.com/v1" + agent.model = "minimax/minimax-m2.5" + messages = [{"role": "user", "content": "hi"}] + kwargs = agent._build_api_kwargs(messages) + assert kwargs["extra_body"]["reasoning"]["effort"] == "medium" + def test_max_tokens_injected(self, agent): agent.max_tokens = 4096 messages = [{"role": "user", "content": "hi"}] @@ -942,6 +961,19 @@ class TestHandleMaxIterations: assert "error" in result.lower() assert "API down" in result + def test_summary_skips_reasoning_for_unsupported_openrouter_model(self, agent): + agent.model = "minimax/minimax-m2.5" + resp = _mock_response(content="Summary") + agent.client.chat.completions.create.return_value = resp + agent._cached_system_prompt = "You are helpful." + messages = [{"role": "user", "content": "do stuff"}] + + result = agent._handle_max_iterations(messages, 60) + + assert result == "Summary" + kwargs = agent.client.chat.completions.create.call_args.kwargs + assert "reasoning" not in kwargs.get("extra_body", {}) + class TestRunConversation: """Tests for the main run_conversation method.