diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py index f147434e7..dce4c19dc 100644 --- a/nanobot/config/schema.py +++ b/nanobot/config/schema.py @@ -74,7 +74,7 @@ class AgentDefaults(Base): max_tool_iterations: int = 200 max_tool_result_chars: int = 16_000 provider_retry_mode: Literal["standard", "persistent"] = "standard" - reasoning_effort: str | None = None # low / medium / high - enables LLM thinking mode + reasoning_effort: str | None = None # low / medium / high / adaptive - enables LLM thinking mode timezone: str = "UTC" # IANA timezone, e.g. "Asia/Shanghai", "America/New_York" dream: DreamConfig = Field(default_factory=DreamConfig) diff --git a/tests/providers/test_anthropic_thinking.py b/tests/providers/test_anthropic_thinking.py new file mode 100644 index 000000000..ab8942e13 --- /dev/null +++ b/tests/providers/test_anthropic_thinking.py @@ -0,0 +1,65 @@ +"""Tests for Anthropic provider thinking / reasoning_effort modes.""" + +from __future__ import annotations + +from unittest.mock import patch + +from nanobot.providers.anthropic_provider import AnthropicProvider + + +def _make_provider(model: str = "claude-sonnet-4-6") -> AnthropicProvider: + with patch("anthropic.AsyncAnthropic"): + return AnthropicProvider(api_key="sk-test", default_model=model) + + +def _build(provider: AnthropicProvider, reasoning_effort: str | None, **overrides): + defaults = dict( + messages=[{"role": "user", "content": "hello"}], + tools=None, + model=None, + max_tokens=4096, + temperature=0.7, + reasoning_effort=reasoning_effort, + tool_choice=None, + supports_caching=False, + ) + defaults.update(overrides) + return provider._build_kwargs(**defaults) + + +def test_adaptive_sets_type_adaptive() -> None: + kw = _build(_make_provider(), "adaptive") + assert kw["thinking"] == {"type": "adaptive"} + + +def test_adaptive_forces_temperature_one() -> None: + kw = _build(_make_provider(), "adaptive") + assert kw["temperature"] == 1.0 + + +def test_adaptive_does_not_inflate_max_tokens() -> None: + kw = _build(_make_provider(), "adaptive", max_tokens=2048) + assert kw["max_tokens"] == 2048 + + +def test_adaptive_no_budget_tokens() -> None: + kw = _build(_make_provider(), "adaptive") + assert "budget_tokens" not in kw["thinking"] + + +def test_high_uses_enabled_with_budget() -> None: + kw = _build(_make_provider(), "high", max_tokens=4096) + assert kw["thinking"]["type"] == "enabled" + assert kw["thinking"]["budget_tokens"] == max(8192, 4096) + assert kw["max_tokens"] >= kw["thinking"]["budget_tokens"] + 4096 + + +def test_low_uses_small_budget() -> None: + kw = _build(_make_provider(), "low") + assert kw["thinking"] == {"type": "enabled", "budget_tokens": 1024} + + +def test_none_does_not_enable_thinking() -> None: + kw = _build(_make_provider(), None) + assert "thinking" not in kw + assert kw["temperature"] == 0.7