diff --git a/nanobot/providers/openai_compat_provider.py b/nanobot/providers/openai_compat_provider.py index b49a0b32c..950907212 100644 --- a/nanobot/providers/openai_compat_provider.py +++ b/nanobot/providers/openai_compat_provider.py @@ -297,22 +297,23 @@ class OpenAICompatProvider(LLMProvider): if reasoning_effort: kwargs["reasoning_effort"] = reasoning_effort - # Provider-specific thinking parameters - if spec: - # Refer: https://docs.byteplus.com/en/docs/ModelArk/1449737#adjust-reasoning-length - # The agent will stop thinking if reasoning_effort is minimal or None. Otherwise, it will think. - thinking_enabled = ( - reasoning_effort is not None and reasoning_effort.lower() != "minimal" - ) + # Provider-specific thinking parameters. + # Only sent when reasoning_effort is explicitly configured so that + # the provider default is preserved otherwise. + if spec and reasoning_effort is not None: + thinking_enabled = reasoning_effort.lower() != "minimal" + extra: dict[str, Any] | None = None if spec.name == "dashscope": - # Qwen: extra_body={"enable_thinking": True/False} - kwargs["extra_body"] = {"enable_thinking": thinking_enabled} - elif spec.name in ("volcengine", "volcengine_coding_plan"): - # VolcEngine/Byteplus ModelArk: extra_body={"thinking": {"type": "enabled"/"disabled"}} - kwargs["extra_body"] = { + extra = {"enable_thinking": thinking_enabled} + elif spec.name in ( + "volcengine", "volcengine_coding_plan", + "byteplus", "byteplus_coding_plan", + ): + extra = { "thinking": {"type": "enabled" if thinking_enabled else "disabled"} } - + if extra: + kwargs.setdefault("extra_body", {}).update(extra) if tools: kwargs["tools"] = tools diff --git a/tests/providers/test_litellm_kwargs.py b/tests/providers/test_litellm_kwargs.py index 1be505872..2e885e165 100644 --- a/tests/providers/test_litellm_kwargs.py +++ b/tests/providers/test_litellm_kwargs.py @@ -307,3 +307,54 @@ async def test_openai_compat_stream_watchdog_returns_error_on_stall(monkeypatch) assert result.finish_reason == "error" assert result.content is not None assert "stream stalled" in result.content + + +# --------------------------------------------------------------------------- +# Provider-specific thinking parameters (extra_body) +# --------------------------------------------------------------------------- + +def _build_kwargs_for(provider_name: str, model: str, reasoning_effort=None): + spec = find_by_name(provider_name) + with patch("nanobot.providers.openai_compat_provider.AsyncOpenAI"): + p = OpenAICompatProvider(api_key="k", default_model=model, spec=spec) + return p._build_kwargs( + messages=[{"role": "user", "content": "hi"}], + tools=None, model=model, max_tokens=1024, temperature=0.7, + reasoning_effort=reasoning_effort, tool_choice=None, + ) + + +def test_dashscope_thinking_enabled_with_reasoning_effort() -> None: + kw = _build_kwargs_for("dashscope", "qwen3-plus", reasoning_effort="medium") + assert kw["extra_body"] == {"enable_thinking": True} + + +def test_dashscope_thinking_disabled_for_minimal() -> None: + kw = _build_kwargs_for("dashscope", "qwen3-plus", reasoning_effort="minimal") + assert kw["extra_body"] == {"enable_thinking": False} + + +def test_dashscope_no_extra_body_when_reasoning_effort_none() -> None: + kw = _build_kwargs_for("dashscope", "qwen-turbo", reasoning_effort=None) + assert "extra_body" not in kw + + +def test_volcengine_thinking_enabled() -> None: + kw = _build_kwargs_for("volcengine", "doubao-seed-2-0-pro", reasoning_effort="high") + assert kw["extra_body"] == {"thinking": {"type": "enabled"}} + + +def test_byteplus_thinking_disabled_for_minimal() -> None: + kw = _build_kwargs_for("byteplus", "doubao-seed-2-0-pro", reasoning_effort="minimal") + assert kw["extra_body"] == {"thinking": {"type": "disabled"}} + + +def test_byteplus_no_extra_body_when_reasoning_effort_none() -> None: + kw = _build_kwargs_for("byteplus", "doubao-seed-2-0-pro", reasoning_effort=None) + assert "extra_body" not in kw + + +def test_openai_no_thinking_extra_body() -> None: + """Non-thinking providers should never get extra_body for thinking.""" + kw = _build_kwargs_for("openai", "gpt-4o", reasoning_effort="medium") + assert "extra_body" not in kw