chore: bump model defaults to Opus 4.7, Sonnet 4.6
Build Nanobot OAuth / build (pull_request) Successful in 7m4s
Build Nanobot OAuth / cleanup (pull_request) Has been skipped
Build Nanobot OAuth / build (push) Successful in 2m48s
Build Nanobot OAuth / cleanup (push) Successful in 1s

- Default model: anthropic/claude-opus-4-5 → anthropic/claude-opus-4-7
- Quota switcher: claude-opus-4-6 → claude-opus-4-7
- Update all provider defaults and test fixtures
- Update comments/docstrings referencing old model names
- Claude Opus 4.7 released 2026-04-16, same pricing as 4.6
This commit is contained in:
2026-04-17 02:53:08 +02:00
parent 71e65052d1
commit 59b4abaa14
12 changed files with 25 additions and 25 deletions
+1 -1
View File
@@ -143,7 +143,7 @@ Add or merge these **two parts** into your config (other options have defaults).
{ {
"agents": { "agents": {
"defaults": { "defaults": {
"model": "anthropic/claude-opus-4-5", "model": "anthropic/claude-opus-4-7",
"provider": "openrouter" "provider": "openrouter"
} }
} }
+1 -1
View File
@@ -221,7 +221,7 @@ class AgentLoop:
return self._quota_cache["model"] return self._quota_cache["model"]
# Default models # Default models
OPUS = "claude-opus-4-6" OPUS = "claude-opus-4-7"
SONNET = "claude-sonnet-4-6" SONNET = "claude-sonnet-4-6"
TOLERANCE = 1.17 # 17% overage triggers downgrade TOLERANCE = 1.17 # 17% overage triggers downgrade
+1 -1
View File
@@ -220,7 +220,7 @@ class AgentDefaults(Base):
"""Default agent configuration.""" """Default agent configuration."""
workspace: str = "~/.nanobot/workspace" workspace: str = "~/.nanobot/workspace"
model: str = "anthropic/claude-opus-4-5" model: str = "anthropic/claude-opus-4-7"
provider: str = "auto" # Provider name (e.g. "anthropic", "openrouter") or "auto" for auto-detection provider: str = "auto" # Provider name (e.g. "anthropic", "openrouter") or "auto" for auto-detection
max_tokens: int = 8192 max_tokens: int = 8192
temperature: float = 0.1 temperature: float = 0.1
+4 -4
View File
@@ -27,7 +27,7 @@ class AnthropicOAuthProvider(LLMProvider):
def __init__( def __init__(
self, self,
oauth_token: str, oauth_token: str,
default_model: str = "claude-opus-4-5", default_model: str = "claude-opus-4-7",
api_base: str | None = None, api_base: str | None = None,
thinking_budget: int = 0, thinking_budget: int = 0,
): ):
@@ -51,8 +51,8 @@ class AnthropicOAuthProvider(LLMProvider):
def _normalize_model(model: str) -> str: def _normalize_model(model: str) -> str:
"""Normalize model name for the Anthropic API. """Normalize model name for the Anthropic API.
Anthropic model IDs use hyphens (claude-sonnet-4-5), but users often Anthropic model IDs use hyphens (claude-sonnet-4-6), but users often
write dots (claude-sonnet-4.5). Normalize so both work. write dots (claude-sonnet-4.6). Normalize so both work.
""" """
return model.replace(".", "-") return model.replace(".", "-")
@@ -557,7 +557,7 @@ class AnthropicOAuthProvider(LLMProvider):
if "/" in model: if "/" in model:
model = model.split("/")[-1] model = model.split("/")[-1]
# Normalize dots to hyphens (claude-sonnet-4.5 -> claude-sonnet-4-5) # Normalize dots to hyphens (claude-sonnet-4.6 -> claude-sonnet-4-6)
model = self._normalize_model(model) model = self._normalize_model(model)
system, prepared_messages = self._prepare_messages(messages) system, prepared_messages = self._prepare_messages(messages)
+2 -2
View File
@@ -37,7 +37,7 @@ class LiteLLMProvider(LLMProvider):
self, self,
api_key: str | None = None, api_key: str | None = None,
api_base: str | None = None, api_base: str | None = None,
default_model: str = "anthropic/claude-opus-4-5", default_model: str = "anthropic/claude-opus-4-7",
extra_headers: dict[str, str] | None = None, extra_headers: dict[str, str] | None = None,
provider_name: str | None = None, provider_name: str | None = None,
): ):
@@ -187,7 +187,7 @@ class LiteLLMProvider(LLMProvider):
Args: Args:
messages: List of message dicts with 'role' and 'content'. messages: List of message dicts with 'role' and 'content'.
tools: Optional list of tool definitions in OpenAI format. tools: Optional list of tool definitions in OpenAI format.
model: Model identifier (e.g., 'anthropic/claude-sonnet-4-5'). model: Model identifier (e.g., 'anthropic/claude-sonnet-4-6').
max_tokens: Maximum tokens in response. max_tokens: Maximum tokens in response.
temperature: Sampling temperature. temperature: Sampling temperature.
+2 -2
View File
@@ -10,14 +10,14 @@ def provider():
"""Create provider with test OAuth token.""" """Create provider with test OAuth token."""
return AnthropicOAuthProvider( return AnthropicOAuthProvider(
oauth_token="sk-ant-oat01-test-token", oauth_token="sk-ant-oat01-test-token",
default_model="claude-opus-4-5" default_model="claude-opus-4-7"
) )
def test_provider_init(provider): def test_provider_init(provider):
"""Provider should initialize with OAuth token.""" """Provider should initialize with OAuth token."""
assert provider.oauth_token == "sk-ant-oat01-test-token" assert provider.oauth_token == "sk-ant-oat01-test-token"
assert provider.default_model == "claude-opus-4-5" assert provider.default_model == "claude-opus-4-7"
def test_provider_uses_bearer_auth(provider): def test_provider_uses_bearer_auth(provider):
+1 -1
View File
@@ -13,7 +13,7 @@ def test_oauth_token_injected_into_config(tmp_path, monkeypatch):
# Create a minimal config file (no api key set) # Create a minimal config file (no api key set)
config_path = tmp_path / "config.json" config_path = tmp_path / "config.json"
config_path.write_text(json.dumps({ config_path.write_text(json.dumps({
"agents": {"defaults": {"model": "anthropic/claude-opus-4-5"}}, "agents": {"defaults": {"model": "anthropic/claude-opus-4-7"}},
"providers": {"anthropic": {"apiKey": ""}} "providers": {"anthropic": {"apiKey": ""}}
})) }))
+2 -2
View File
@@ -20,7 +20,7 @@ async def test_provider_raises_long_context_error_on_long_context_429():
provider = AnthropicOAuthProvider( provider = AnthropicOAuthProvider(
oauth_token="sk-ant-oat01-test-token", oauth_token="sk-ant-oat01-test-token",
default_model="claude-sonnet-4-5", default_model="claude-sonnet-4-6",
) )
mock_response = MagicMock() mock_response = MagicMock()
@@ -48,7 +48,7 @@ async def test_provider_retries_normal_429():
provider = AnthropicOAuthProvider( provider = AnthropicOAuthProvider(
oauth_token="sk-ant-oat01-test-token", oauth_token="sk-ant-oat01-test-token",
default_model="claude-sonnet-4-5", default_model="claude-sonnet-4-6",
) )
rate_limit_response = MagicMock() rate_limit_response = MagicMock()
+4 -4
View File
@@ -52,7 +52,7 @@ async def test_extract_facts_passes_thinking_budget_zero(mock_provider):
{"role": "assistant", "content": "That's great! Python is versatile."}, {"role": "assistant", "content": "That's great! Python is versatile."},
] ]
facts = await store.extract_facts(messages, mock_provider, "claude-sonnet-4-5") facts = await store.extract_facts(messages, mock_provider, "claude-sonnet-4-6")
# Verify provider.chat was called with thinking_budget=0 # Verify provider.chat was called with thinking_budget=0
mock_provider.chat.assert_called_once() mock_provider.chat.assert_called_once()
@@ -75,7 +75,7 @@ async def test_extract_facts_returns_parsed_facts(mock_provider):
{"role": "user", "content": "I like Python programming"}, {"role": "user", "content": "I like Python programming"},
] ]
facts = await store.extract_facts(messages, mock_provider, "claude-sonnet-4-5") facts = await store.extract_facts(messages, mock_provider, "claude-sonnet-4-6")
assert facts == ["user likes Python", "user works on nanobot"] assert facts == ["user likes Python", "user works on nanobot"]
@@ -99,7 +99,7 @@ async def test_extract_facts_handles_empty_response():
messages = [{"role": "user", "content": "Hello there"}] messages = [{"role": "user", "content": "Hello there"}]
facts = await store.extract_facts(messages, provider, "claude-sonnet-4-5") facts = await store.extract_facts(messages, provider, "claude-sonnet-4-6")
assert facts == [] assert facts == []
@@ -122,7 +122,7 @@ async def test_extract_facts_skips_empty_messages():
{"role": "assistant", "content": ""}, {"role": "assistant", "content": ""},
] ]
facts = await store.extract_facts(messages, provider, "claude-sonnet-4-5") facts = await store.extract_facts(messages, provider, "claude-sonnet-4-6")
assert facts == [] assert facts == []
# Provider should not be called when there's no content # Provider should not be called when there's no content
+1 -1
View File
@@ -16,7 +16,7 @@ IDENTITY_TEXT = get_claude_code_system_prefix()
def provider(): def provider():
return AnthropicOAuthProvider( return AnthropicOAuthProvider(
oauth_token="sk-ant-oat01-test-token", oauth_token="sk-ant-oat01-test-token",
default_model="claude-opus-4-5", default_model="claude-opus-4-7",
) )
+3 -3
View File
@@ -9,7 +9,7 @@ def test_create_provider_oauth_token():
"""OAuth tokens should create AnthropicOAuthProvider.""" """OAuth tokens should create AnthropicOAuthProvider."""
provider = create_provider( provider = create_provider(
api_key="sk-ant-oat01-test-token", api_key="sk-ant-oat01-test-token",
model="anthropic/claude-opus-4-5" model="anthropic/claude-opus-4-7"
) )
assert isinstance(provider, AnthropicOAuthProvider) assert isinstance(provider, AnthropicOAuthProvider)
@@ -18,7 +18,7 @@ def test_create_provider_regular_key():
"""Regular API keys should create LiteLLMProvider.""" """Regular API keys should create LiteLLMProvider."""
provider = create_provider( provider = create_provider(
api_key="sk-ant-api03-regular-key", api_key="sk-ant-api03-regular-key",
model="anthropic/claude-opus-4-5" model="anthropic/claude-opus-4-7"
) )
assert isinstance(provider, LiteLLMProvider) assert isinstance(provider, LiteLLMProvider)
@@ -27,6 +27,6 @@ def test_create_provider_openrouter():
"""OpenRouter keys should create LiteLLMProvider.""" """OpenRouter keys should create LiteLLMProvider."""
provider = create_provider( provider = create_provider(
api_key="sk-or-v1-xxx", api_key="sk-or-v1-xxx",
model="anthropic/claude-opus-4-5" model="anthropic/claude-opus-4-7"
) )
assert isinstance(provider, LiteLLMProvider) assert isinstance(provider, LiteLLMProvider)
+3 -3
View File
@@ -5,14 +5,14 @@ from nanobot.providers.registry import should_use_oauth_provider
def test_should_use_oauth_for_oat_token(): def test_should_use_oauth_for_oat_token():
"""OAuth provider should be used for sk-ant-oat tokens.""" """OAuth provider should be used for sk-ant-oat tokens."""
assert should_use_oauth_provider("sk-ant-oat01-xxx", "anthropic/claude-opus-4-5") is True assert should_use_oauth_provider("sk-ant-oat01-xxx", "anthropic/claude-opus-4-7") is True
assert should_use_oauth_provider("sk-ant-oat01-xxx", "claude-sonnet-4") is True assert should_use_oauth_provider("sk-ant-oat01-xxx", "claude-sonnet-4") is True
def test_should_not_use_oauth_for_regular_key(): def test_should_not_use_oauth_for_regular_key():
"""Regular API keys should not use OAuth provider.""" """Regular API keys should not use OAuth provider."""
assert should_use_oauth_provider("sk-ant-api03-xxx", "claude-opus-4-5") is False assert should_use_oauth_provider("sk-ant-api03-xxx", "claude-opus-4-7") is False
assert should_use_oauth_provider("sk-or-v1-xxx", "anthropic/claude-opus-4-5") is False assert should_use_oauth_provider("sk-or-v1-xxx", "anthropic/claude-opus-4-7") is False
def test_should_not_use_oauth_for_non_anthropic(): def test_should_not_use_oauth_for_non_anthropic():