59b4abaa14
- 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
79 lines
2.6 KiB
Python
79 lines
2.6 KiB
Python
"""Test auto-consolidation on long context 429 errors."""
|
|
|
|
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
from nanobot.providers.base import LongContextError, LLMResponse
|
|
|
|
|
|
def test_long_context_error_is_exception():
|
|
"""LongContextError should be a distinct exception class."""
|
|
err = LongContextError("too long")
|
|
assert isinstance(err, Exception)
|
|
assert str(err) == "too long"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_provider_raises_long_context_error_on_long_context_429():
|
|
"""Provider should raise LongContextError immediately for long-context 429s."""
|
|
from nanobot.providers.anthropic_oauth import AnthropicOAuthProvider
|
|
|
|
provider = AnthropicOAuthProvider(
|
|
oauth_token="sk-ant-oat01-test-token",
|
|
default_model="claude-sonnet-4-6",
|
|
)
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 429
|
|
mock_response.text = '{"type":"error","error":{"type":"rate_limit_error","message":"Extra usage is required for long context requests."}}'
|
|
mock_response.headers = {}
|
|
|
|
mock_client = AsyncMock()
|
|
mock_client.post.return_value = mock_response
|
|
provider._client = mock_client
|
|
|
|
with pytest.raises(LongContextError, match="Context too long"):
|
|
await provider._make_request(
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
)
|
|
|
|
# Should NOT retry — only one call
|
|
assert mock_client.post.call_count == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_provider_retries_normal_429():
|
|
"""Provider should still retry normal 429s (not long-context)."""
|
|
from nanobot.providers.anthropic_oauth import AnthropicOAuthProvider
|
|
|
|
provider = AnthropicOAuthProvider(
|
|
oauth_token="sk-ant-oat01-test-token",
|
|
default_model="claude-sonnet-4-6",
|
|
)
|
|
|
|
rate_limit_response = MagicMock()
|
|
rate_limit_response.status_code = 429
|
|
rate_limit_response.text = '{"type":"error","error":{"type":"rate_limit_error","message":"Rate limit exceeded"}}'
|
|
rate_limit_response.headers = {}
|
|
|
|
success_response = MagicMock()
|
|
success_response.status_code = 200
|
|
success_response.headers = {}
|
|
success_response.json.return_value = {
|
|
"content": [{"type": "text", "text": "ok"}],
|
|
"stop_reason": "end_turn",
|
|
"usage": {"input_tokens": 1, "output_tokens": 1},
|
|
}
|
|
|
|
mock_client = AsyncMock()
|
|
mock_client.post.side_effect = [rate_limit_response, success_response]
|
|
provider._client = mock_client
|
|
|
|
result = await provider._make_request(
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
)
|
|
|
|
# Should have retried and succeeded
|
|
assert mock_client.post.call_count == 2
|
|
assert result["stop_reason"] == "end_turn"
|