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 <buraysandro9@gmail.com>
This commit is contained in:
32
run_agent.py
32
run_agent.py
@@ -3302,8 +3302,7 @@ class AIAgent:
|
|||||||
extra_body["provider"] = provider_preferences
|
extra_body["provider"] = provider_preferences
|
||||||
_is_nous = "nousresearch" in self.base_url.lower()
|
_is_nous = "nousresearch" in self.base_url.lower()
|
||||||
|
|
||||||
_is_mistral = "api.mistral.ai" in self.base_url.lower()
|
if self._supports_reasoning_extra_body():
|
||||||
if (_is_openrouter or _is_nous) and not _is_mistral:
|
|
||||||
if self.reasoning_config is not None:
|
if self.reasoning_config is not None:
|
||||||
rc = dict(self.reasoning_config)
|
rc = dict(self.reasoning_config)
|
||||||
# Nous Portal requires reasoning enabled — don't send
|
# Nous Portal requires reasoning enabled — don't send
|
||||||
@@ -3327,6 +3326,32 @@ class AIAgent:
|
|||||||
|
|
||||||
return api_kwargs
|
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:
|
def _build_assistant_message(self, assistant_message, finish_reason: str) -> dict:
|
||||||
"""Build a normalized assistant message dict from an API response message.
|
"""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())
|
api_messages.insert(sys_offset + idx, pfm.copy())
|
||||||
|
|
||||||
summary_extra_body = {}
|
summary_extra_body = {}
|
||||||
_is_openrouter = "openrouter" in self.base_url.lower()
|
|
||||||
_is_nous = "nousresearch" 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:
|
if self.reasoning_config is not None:
|
||||||
summary_extra_body["reasoning"] = self.reasoning_config
|
summary_extra_body["reasoning"] = self.reasoning_config
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -612,6 +612,25 @@ class TestBuildApiKwargs:
|
|||||||
kwargs = agent._build_api_kwargs(messages)
|
kwargs = agent._build_api_kwargs(messages)
|
||||||
assert kwargs["extra_body"]["reasoning"] == {"enabled": False}
|
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):
|
def test_max_tokens_injected(self, agent):
|
||||||
agent.max_tokens = 4096
|
agent.max_tokens = 4096
|
||||||
messages = [{"role": "user", "content": "hi"}]
|
messages = [{"role": "user", "content": "hi"}]
|
||||||
@@ -942,6 +961,19 @@ class TestHandleMaxIterations:
|
|||||||
assert "error" in result.lower()
|
assert "error" in result.lower()
|
||||||
assert "API down" in result
|
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:
|
class TestRunConversation:
|
||||||
"""Tests for the main run_conversation method.
|
"""Tests for the main run_conversation method.
|
||||||
|
|||||||
Reference in New Issue
Block a user