Merge pull request #234 from spotty118/update-opus-4.1
feat: Update Claude models to Opus 4.1 and Sonnet 4.1
This commit is contained in:
22
.env.example
22
.env.example
@@ -37,7 +37,7 @@ OPENROUTER_API_KEY=your_openrouter_api_key_here
|
|||||||
|
|
||||||
# Optional: Default model to use
|
# Optional: Default model to use
|
||||||
# Options: 'auto' (Claude picks best model), 'pro', 'flash', 'o3', 'o3-mini', 'o4-mini', 'o4-mini-high',
|
# Options: 'auto' (Claude picks best model), 'pro', 'flash', 'o3', 'o3-mini', 'o4-mini', 'o4-mini-high',
|
||||||
# 'gpt-5', 'gpt-5-mini', 'grok', 'opus-4', 'sonnet-4', or any DIAL model if DIAL is configured
|
# 'gpt-5', 'gpt-5-mini', 'grok', 'opus-4.1', 'sonnet-4.1', or any DIAL model if DIAL is configured
|
||||||
# When set to 'auto', Claude will select the best model for each task
|
# When set to 'auto', Claude will select the best model for each task
|
||||||
# Defaults to 'auto' if not specified
|
# Defaults to 'auto' if not specified
|
||||||
DEFAULT_MODEL=auto
|
DEFAULT_MODEL=auto
|
||||||
@@ -87,14 +87,14 @@ DEFAULT_THINKING_MODE_THINKDEEP=high
|
|||||||
# - o4-mini-2025-04-16 (200K context, latest O4 mini)
|
# - o4-mini-2025-04-16 (200K context, latest O4 mini)
|
||||||
# - o3 (shorthand for o3-2025-04-16)
|
# - o3 (shorthand for o3-2025-04-16)
|
||||||
# - o4-mini (shorthand for o4-mini-2025-04-16)
|
# - o4-mini (shorthand for o4-mini-2025-04-16)
|
||||||
# - anthropic.claude-sonnet-4-20250514-v1:0 (200K context, Claude 4 Sonnet)
|
# - anthropic.claude-sonnet-4.1-20250805-v1:0 (200K context, Claude 4.1 Sonnet)
|
||||||
# - anthropic.claude-sonnet-4-20250514-v1:0-with-thinking (200K context, Claude 4 Sonnet with thinking mode)
|
# - anthropic.claude-sonnet-4.1-20250805-v1:0-with-thinking (200K context, Claude 4.1 Sonnet with thinking mode)
|
||||||
# - anthropic.claude-opus-4-20250514-v1:0 (200K context, Claude 4 Opus)
|
# - anthropic.claude-opus-4.1-20250805-v1:0 (200K context, Claude 4.1 Opus)
|
||||||
# - anthropic.claude-opus-4-20250514-v1:0-with-thinking (200K context, Claude 4 Opus with thinking mode)
|
# - anthropic.claude-opus-4.1-20250805-v1:0-with-thinking (200K context, Claude 4.1 Opus with thinking mode)
|
||||||
# - sonnet-4 (shorthand for Claude 4 Sonnet)
|
# - sonnet-4.1 (shorthand for Claude 4.1 Sonnet)
|
||||||
# - sonnet-4-thinking (shorthand for Claude 4 Sonnet with thinking)
|
# - sonnet-4.1-thinking (shorthand for Claude 4.1 Sonnet with thinking)
|
||||||
# - opus-4 (shorthand for Claude 4 Opus)
|
# - opus-4.1 (shorthand for Claude 4.1 Opus)
|
||||||
# - opus-4-thinking (shorthand for Claude 4 Opus with thinking)
|
# - opus-4.1-thinking (shorthand for Claude 4.1 Opus with thinking)
|
||||||
# - gemini-2.5-pro-preview-03-25-google-search (1M context, with Google Search)
|
# - gemini-2.5-pro-preview-03-25-google-search (1M context, with Google Search)
|
||||||
# - gemini-2.5-pro-preview-05-06 (1M context, latest preview)
|
# - gemini-2.5-pro-preview-05-06 (1M context, latest preview)
|
||||||
# - gemini-2.5-flash-preview-05-20 (1M context, latest flash preview)
|
# - gemini-2.5-flash-preview-05-20 (1M context, latest flash preview)
|
||||||
@@ -110,8 +110,8 @@ DEFAULT_THINKING_MODE_THINKDEEP=high
|
|||||||
# GOOGLE_ALLOWED_MODELS=flash,pro # Allow both Gemini models
|
# GOOGLE_ALLOWED_MODELS=flash,pro # Allow both Gemini models
|
||||||
# XAI_ALLOWED_MODELS=grok,grok-3-fast # Allow both GROK variants
|
# XAI_ALLOWED_MODELS=grok,grok-3-fast # Allow both GROK variants
|
||||||
# DIAL_ALLOWED_MODELS=o3,o4-mini # Only allow O3/O4 models via DIAL
|
# DIAL_ALLOWED_MODELS=o3,o4-mini # Only allow O3/O4 models via DIAL
|
||||||
# DIAL_ALLOWED_MODELS=opus-4,sonnet-4 # Only Claude 4 models (without thinking)
|
# DIAL_ALLOWED_MODELS=opus-4.1,sonnet-4.1 # Only Claude 4.1 models (without thinking)
|
||||||
# DIAL_ALLOWED_MODELS=opus-4-thinking,sonnet-4-thinking # Only Claude 4 with thinking mode
|
# DIAL_ALLOWED_MODELS=opus-4.1-thinking,sonnet-4.1-thinking # Only Claude 4.1 with thinking mode
|
||||||
# DIAL_ALLOWED_MODELS=gemini-2.5-pro,gemini-2.5-flash # Only Gemini 2.5 models via DIAL
|
# DIAL_ALLOWED_MODELS=gemini-2.5-pro,gemini-2.5-flash # Only Gemini 2.5 models via DIAL
|
||||||
#
|
#
|
||||||
# Note: These restrictions apply even in 'auto' mode - Claude will only pick from allowed models
|
# Note: These restrictions apply even in 'auto' mode - Claude will only pick from allowed models
|
||||||
|
|||||||
@@ -51,8 +51,8 @@
|
|||||||
},
|
},
|
||||||
"models": [
|
"models": [
|
||||||
{
|
{
|
||||||
"model_name": "anthropic/claude-opus-4",
|
"model_name": "anthropic/claude-opus-4.1",
|
||||||
"aliases": ["opus", "claude-opus", "claude4-opus", "claude-4-opus"],
|
"aliases": ["opus", "claude-opus", "claude-opus-4.1", "claude-4.1-opus"],
|
||||||
"context_window": 200000,
|
"context_window": 200000,
|
||||||
"max_output_tokens": 64000,
|
"max_output_tokens": 64000,
|
||||||
"supports_extended_thinking": false,
|
"supports_extended_thinking": false,
|
||||||
@@ -60,11 +60,11 @@
|
|||||||
"supports_function_calling": false,
|
"supports_function_calling": false,
|
||||||
"supports_images": true,
|
"supports_images": true,
|
||||||
"max_image_size_mb": 5.0,
|
"max_image_size_mb": 5.0,
|
||||||
"description": "Claude 4 Opus - Most capable Claude model with vision"
|
"description": "Claude Opus 4.1 - Our most capable and intelligent model yet"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model_name": "anthropic/claude-sonnet-4",
|
"model_name": "anthropic/claude-sonnet-4.1",
|
||||||
"aliases": ["sonnet", "claude-sonnet", "claude4-sonnet", "claude-4-sonnet", "claude"],
|
"aliases": ["sonnet", "claude-sonnet", "claude-sonnet-4.1", "claude-4.1-sonnet", "claude"],
|
||||||
"context_window": 200000,
|
"context_window": 200000,
|
||||||
"max_output_tokens": 64000,
|
"max_output_tokens": 64000,
|
||||||
"supports_extended_thinking": false,
|
"supports_extended_thinking": false,
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"supports_function_calling": false,
|
"supports_function_calling": false,
|
||||||
"supports_images": true,
|
"supports_images": true,
|
||||||
"max_image_size_mb": 5.0,
|
"max_image_size_mb": 5.0,
|
||||||
"description": "Claude 4 Sonnet - Balanced performance with vision"
|
"description": "Claude Sonnet 4.1 - High-performance model with exceptional reasoning and efficiency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model_name": "anthropic/claude-3.5-haiku",
|
"model_name": "anthropic/claude-3.5-haiku",
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ class DIALModelProvider(OpenAICompatibleProvider):
|
|||||||
description="OpenAI O4-mini via DIAL - Fast reasoning model",
|
description="OpenAI O4-mini via DIAL - Fast reasoning model",
|
||||||
aliases=["o4-mini"],
|
aliases=["o4-mini"],
|
||||||
),
|
),
|
||||||
"anthropic.claude-sonnet-4-20250514-v1:0": ModelCapabilities(
|
"anthropic.claude-sonnet-4.1-20250805-v1:0": ModelCapabilities(
|
||||||
provider=ProviderType.DIAL,
|
provider=ProviderType.DIAL,
|
||||||
model_name="anthropic.claude-sonnet-4-20250514-v1:0",
|
model_name="anthropic.claude-sonnet-4.1-20250805-v1:0",
|
||||||
friendly_name="DIAL (Sonnet 4)",
|
friendly_name="DIAL (Sonnet 4.1)",
|
||||||
context_window=200_000,
|
context_window=200_000,
|
||||||
max_output_tokens=64_000,
|
max_output_tokens=64_000,
|
||||||
supports_extended_thinking=False,
|
supports_extended_thinking=False,
|
||||||
@@ -83,13 +83,13 @@ class DIALModelProvider(OpenAICompatibleProvider):
|
|||||||
max_image_size_mb=5.0,
|
max_image_size_mb=5.0,
|
||||||
supports_temperature=True,
|
supports_temperature=True,
|
||||||
temperature_constraint=create_temperature_constraint("range"),
|
temperature_constraint=create_temperature_constraint("range"),
|
||||||
description="Claude Sonnet 4 via DIAL - Balanced performance",
|
description="Claude Sonnet 4.1 via DIAL - Balanced performance",
|
||||||
aliases=["sonnet-4"],
|
aliases=["sonnet-4.1", "sonnet-4"],
|
||||||
),
|
),
|
||||||
"anthropic.claude-sonnet-4-20250514-v1:0-with-thinking": ModelCapabilities(
|
"anthropic.claude-sonnet-4.1-20250805-v1:0-with-thinking": ModelCapabilities(
|
||||||
provider=ProviderType.DIAL,
|
provider=ProviderType.DIAL,
|
||||||
model_name="anthropic.claude-sonnet-4-20250514-v1:0-with-thinking",
|
model_name="anthropic.claude-sonnet-4.1-20250805-v1:0-with-thinking",
|
||||||
friendly_name="DIAL (Sonnet 4 Thinking)",
|
friendly_name="DIAL (Sonnet 4.1 Thinking)",
|
||||||
context_window=200_000,
|
context_window=200_000,
|
||||||
max_output_tokens=64_000,
|
max_output_tokens=64_000,
|
||||||
supports_extended_thinking=True, # Thinking mode variant
|
supports_extended_thinking=True, # Thinking mode variant
|
||||||
@@ -101,13 +101,13 @@ class DIALModelProvider(OpenAICompatibleProvider):
|
|||||||
max_image_size_mb=5.0,
|
max_image_size_mb=5.0,
|
||||||
supports_temperature=True,
|
supports_temperature=True,
|
||||||
temperature_constraint=create_temperature_constraint("range"),
|
temperature_constraint=create_temperature_constraint("range"),
|
||||||
description="Claude Sonnet 4 with thinking mode via DIAL",
|
description="Claude Sonnet 4.1 with thinking mode via DIAL",
|
||||||
aliases=["sonnet-4-thinking"],
|
aliases=["sonnet-4.1-thinking", "sonnet-4-thinking"],
|
||||||
),
|
),
|
||||||
"anthropic.claude-opus-4-20250514-v1:0": ModelCapabilities(
|
"anthropic.claude-opus-4.1-20250805-v1:0": ModelCapabilities(
|
||||||
provider=ProviderType.DIAL,
|
provider=ProviderType.DIAL,
|
||||||
model_name="anthropic.claude-opus-4-20250514-v1:0",
|
model_name="anthropic.claude-opus-4.1-20250805-v1:0",
|
||||||
friendly_name="DIAL (Opus 4)",
|
friendly_name="DIAL (Opus 4.1)",
|
||||||
context_window=200_000,
|
context_window=200_000,
|
||||||
max_output_tokens=64_000,
|
max_output_tokens=64_000,
|
||||||
supports_extended_thinking=False,
|
supports_extended_thinking=False,
|
||||||
@@ -119,13 +119,13 @@ class DIALModelProvider(OpenAICompatibleProvider):
|
|||||||
max_image_size_mb=5.0,
|
max_image_size_mb=5.0,
|
||||||
supports_temperature=True,
|
supports_temperature=True,
|
||||||
temperature_constraint=create_temperature_constraint("range"),
|
temperature_constraint=create_temperature_constraint("range"),
|
||||||
description="Claude Opus 4 via DIAL - Most capable Claude model",
|
description="Claude Opus 4.1 via DIAL - Most capable Claude model",
|
||||||
aliases=["opus-4"],
|
aliases=["opus-4.1", "opus-4"],
|
||||||
),
|
),
|
||||||
"anthropic.claude-opus-4-20250514-v1:0-with-thinking": ModelCapabilities(
|
"anthropic.claude-opus-4.1-20250805-v1:0-with-thinking": ModelCapabilities(
|
||||||
provider=ProviderType.DIAL,
|
provider=ProviderType.DIAL,
|
||||||
model_name="anthropic.claude-opus-4-20250514-v1:0-with-thinking",
|
model_name="anthropic.claude-opus-4.1-20250805-v1:0-with-thinking",
|
||||||
friendly_name="DIAL (Opus 4 Thinking)",
|
friendly_name="DIAL (Opus 4.1 Thinking)",
|
||||||
context_window=200_000,
|
context_window=200_000,
|
||||||
max_output_tokens=64_000,
|
max_output_tokens=64_000,
|
||||||
supports_extended_thinking=True, # Thinking mode variant
|
supports_extended_thinking=True, # Thinking mode variant
|
||||||
@@ -137,8 +137,8 @@ class DIALModelProvider(OpenAICompatibleProvider):
|
|||||||
max_image_size_mb=5.0,
|
max_image_size_mb=5.0,
|
||||||
supports_temperature=True,
|
supports_temperature=True,
|
||||||
temperature_constraint=create_temperature_constraint("range"),
|
temperature_constraint=create_temperature_constraint("range"),
|
||||||
description="Claude Opus 4 with thinking mode via DIAL",
|
description="Claude Opus 4.1 with thinking mode via DIAL",
|
||||||
aliases=["opus-4-thinking"],
|
aliases=["opus-4.1-thinking", "opus-4-thinking"],
|
||||||
),
|
),
|
||||||
"gemini-2.5-pro-preview-03-25-google-search": ModelCapabilities(
|
"gemini-2.5-pro-preview-03-25-google-search": ModelCapabilities(
|
||||||
provider=ProviderType.DIAL,
|
provider=ProviderType.DIAL,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class TestChatTool:
|
|||||||
"prompt": "Test prompt",
|
"prompt": "Test prompt",
|
||||||
"files": ["test.txt"],
|
"files": ["test.txt"],
|
||||||
"images": ["test.png"],
|
"images": ["test.png"],
|
||||||
"model": "anthropic/claude-3-opus",
|
"model": "anthropic/claude-opus-4.1",
|
||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class TestChatTool:
|
|||||||
assert request.prompt == "Test prompt"
|
assert request.prompt == "Test prompt"
|
||||||
assert request.files == ["test.txt"]
|
assert request.files == ["test.txt"]
|
||||||
assert request.images == ["test.png"]
|
assert request.images == ["test.png"]
|
||||||
assert request.model == "anthropic/claude-3-opus"
|
assert request.model == "anthropic/claude-opus-4.1"
|
||||||
assert request.temperature == 0.7
|
assert request.temperature == 0.7
|
||||||
|
|
||||||
def test_required_fields(self):
|
def test_required_fields(self):
|
||||||
@@ -69,7 +69,7 @@ class TestChatTool:
|
|||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
ChatRequest(model="anthropic/claude-3-opus")
|
ChatRequest(model="anthropic/claude-opus-4.1")
|
||||||
|
|
||||||
def test_model_availability(self):
|
def test_model_availability(self):
|
||||||
"""Test that model availability works"""
|
"""Test that model availability works"""
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ class TestDIALProvider:
|
|||||||
# Test valid models
|
# Test valid models
|
||||||
assert provider.validate_model_name("o3-2025-04-16") is True
|
assert provider.validate_model_name("o3-2025-04-16") is True
|
||||||
assert provider.validate_model_name("o3") is True # Shorthand
|
assert provider.validate_model_name("o3") is True # Shorthand
|
||||||
assert provider.validate_model_name("anthropic.claude-opus-4-20250514-v1:0") is True
|
assert provider.validate_model_name("anthropic.claude-opus-4.1-20250805-v1:0") is True
|
||||||
assert provider.validate_model_name("opus-4") is True # Shorthand
|
assert provider.validate_model_name("opus-4.1") is True # Shorthand
|
||||||
assert provider.validate_model_name("gemini-2.5-pro-preview-05-06") is True
|
assert provider.validate_model_name("gemini-2.5-pro-preview-05-06") is True
|
||||||
assert provider.validate_model_name("gemini-2.5-pro") is True # Shorthand
|
assert provider.validate_model_name("gemini-2.5-pro") is True # Shorthand
|
||||||
|
|
||||||
@@ -63,16 +63,16 @@ class TestDIALProvider:
|
|||||||
# Test shorthand resolution
|
# Test shorthand resolution
|
||||||
assert provider._resolve_model_name("o3") == "o3-2025-04-16"
|
assert provider._resolve_model_name("o3") == "o3-2025-04-16"
|
||||||
assert provider._resolve_model_name("o4-mini") == "o4-mini-2025-04-16"
|
assert provider._resolve_model_name("o4-mini") == "o4-mini-2025-04-16"
|
||||||
assert provider._resolve_model_name("opus-4") == "anthropic.claude-opus-4-20250514-v1:0"
|
assert provider._resolve_model_name("opus-4.1") == "anthropic.claude-opus-4.1-20250805-v1:0"
|
||||||
assert provider._resolve_model_name("sonnet-4") == "anthropic.claude-sonnet-4-20250514-v1:0"
|
assert provider._resolve_model_name("sonnet-4.1") == "anthropic.claude-sonnet-4.1-20250805-v1:0"
|
||||||
assert provider._resolve_model_name("gemini-2.5-pro") == "gemini-2.5-pro-preview-05-06"
|
assert provider._resolve_model_name("gemini-2.5-pro") == "gemini-2.5-pro-preview-05-06"
|
||||||
assert provider._resolve_model_name("gemini-2.5-flash") == "gemini-2.5-flash-preview-05-20"
|
assert provider._resolve_model_name("gemini-2.5-flash") == "gemini-2.5-flash-preview-05-20"
|
||||||
|
|
||||||
# Test full name passthrough
|
# Test full name passthrough
|
||||||
assert provider._resolve_model_name("o3-2025-04-16") == "o3-2025-04-16"
|
assert provider._resolve_model_name("o3-2025-04-16") == "o3-2025-04-16"
|
||||||
assert (
|
assert (
|
||||||
provider._resolve_model_name("anthropic.claude-opus-4-20250514-v1:0")
|
provider._resolve_model_name("anthropic.claude-opus-4.1-20250805-v1:0")
|
||||||
== "anthropic.claude-opus-4-20250514-v1:0"
|
== "anthropic.claude-opus-4.1-20250805-v1:0"
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch.dict(os.environ, {"DIAL_ALLOWED_MODELS": ""}, clear=False)
|
@patch.dict(os.environ, {"DIAL_ALLOWED_MODELS": ""}, clear=False)
|
||||||
@@ -90,16 +90,16 @@ class TestDIALProvider:
|
|||||||
assert capabilities.supports_images is True
|
assert capabilities.supports_images is True
|
||||||
assert capabilities.supports_extended_thinking is False
|
assert capabilities.supports_extended_thinking is False
|
||||||
|
|
||||||
# Test Claude 4 capabilities
|
# Test Claude 4.1 capabilities
|
||||||
capabilities = provider.get_capabilities("opus-4")
|
capabilities = provider.get_capabilities("opus-4.1")
|
||||||
assert capabilities.model_name == "anthropic.claude-opus-4-20250514-v1:0"
|
assert capabilities.model_name == "anthropic.claude-opus-4.1-20250805-v1:0"
|
||||||
assert capabilities.context_window == 200_000
|
assert capabilities.context_window == 200_000
|
||||||
assert capabilities.supports_images is True
|
assert capabilities.supports_images is True
|
||||||
assert capabilities.supports_extended_thinking is False
|
assert capabilities.supports_extended_thinking is False
|
||||||
|
|
||||||
# Test Claude 4 with thinking mode
|
# Test Claude 4.1 with thinking mode
|
||||||
capabilities = provider.get_capabilities("opus-4-thinking")
|
capabilities = provider.get_capabilities("opus-4.1-thinking")
|
||||||
assert capabilities.model_name == "anthropic.claude-opus-4-20250514-v1:0-with-thinking"
|
assert capabilities.model_name == "anthropic.claude-opus-4.1-20250805-v1:0-with-thinking"
|
||||||
assert capabilities.context_window == 200_000
|
assert capabilities.context_window == 200_000
|
||||||
assert capabilities.supports_images is True
|
assert capabilities.supports_images is True
|
||||||
assert capabilities.supports_extended_thinking is True
|
assert capabilities.supports_extended_thinking is True
|
||||||
@@ -146,7 +146,7 @@ class TestDIALProvider:
|
|||||||
# Test models with vision support
|
# Test models with vision support
|
||||||
assert provider._supports_vision("o3-2025-04-16") is True
|
assert provider._supports_vision("o3-2025-04-16") is True
|
||||||
assert provider._supports_vision("o3") is True # Via resolution
|
assert provider._supports_vision("o3") is True # Via resolution
|
||||||
assert provider._supports_vision("anthropic.claude-opus-4-20250514-v1:0") is True
|
assert provider._supports_vision("anthropic.claude-opus-4.1-20250805-v1:0") is True
|
||||||
assert provider._supports_vision("gemini-2.5-pro-preview-05-06") is True
|
assert provider._supports_vision("gemini-2.5-pro-preview-05-06") is True
|
||||||
|
|
||||||
# Test unknown model (falls back to parent implementation)
|
# Test unknown model (falls back to parent implementation)
|
||||||
@@ -219,7 +219,7 @@ class TestDIALProvider:
|
|||||||
# Check that Api-Key header is set
|
# Check that Api-Key header is set
|
||||||
assert provider.DEFAULT_HEADERS["Api-Key"] == "test-key"
|
assert provider.DEFAULT_HEADERS["Api-Key"] == "test-key"
|
||||||
|
|
||||||
@patch.dict(os.environ, {"DIAL_ALLOWED_MODELS": "o3-2025-04-16,anthropic.claude-opus-4-20250514-v1:0"})
|
@patch.dict(os.environ, {"DIAL_ALLOWED_MODELS": "o3-2025-04-16,anthropic.claude-opus-4.1-20250805-v1:0"})
|
||||||
@patch("utils.model_restrictions._restriction_service", None)
|
@patch("utils.model_restrictions._restriction_service", None)
|
||||||
def test_allowed_models_restriction(self):
|
def test_allowed_models_restriction(self):
|
||||||
"""Test model allow-list functionality."""
|
"""Test model allow-list functionality."""
|
||||||
@@ -228,13 +228,13 @@ class TestDIALProvider:
|
|||||||
# These should be allowed
|
# These should be allowed
|
||||||
assert provider.validate_model_name("o3-2025-04-16") is True
|
assert provider.validate_model_name("o3-2025-04-16") is True
|
||||||
assert provider.validate_model_name("o3") is True # Alias for o3-2025-04-16
|
assert provider.validate_model_name("o3") is True # Alias for o3-2025-04-16
|
||||||
assert provider.validate_model_name("anthropic.claude-opus-4-20250514-v1:0") is True
|
assert provider.validate_model_name("anthropic.claude-opus-4.1-20250805-v1:0") is True
|
||||||
assert provider.validate_model_name("opus-4") is True # Resolves to anthropic.claude-opus-4-20250514-v1:0
|
assert provider.validate_model_name("opus-4.1") is True # Resolves to anthropic.claude-opus-4.1-20250805-v1:0
|
||||||
|
|
||||||
# These should be blocked
|
# These should be blocked
|
||||||
assert provider.validate_model_name("gemini-2.5-pro-preview-05-06") is False
|
assert provider.validate_model_name("gemini-2.5-pro-preview-05-06") is False
|
||||||
assert provider.validate_model_name("o4-mini-2025-04-16") is False
|
assert provider.validate_model_name("o4-mini-2025-04-16") is False
|
||||||
assert provider.validate_model_name("sonnet-4") is False # sonnet-4 is not in allowed list
|
assert provider.validate_model_name("sonnet-4.1") is False # sonnet-4.1 is not in allowed list
|
||||||
|
|
||||||
@patch("httpx.Client")
|
@patch("httpx.Client")
|
||||||
@patch("openai.OpenAI")
|
@patch("openai.OpenAI")
|
||||||
|
|||||||
@@ -71,26 +71,26 @@ class TestOpenRouterProvider:
|
|||||||
provider = OpenRouterProvider(api_key="test-key")
|
provider = OpenRouterProvider(api_key="test-key")
|
||||||
|
|
||||||
# Test alias resolution
|
# Test alias resolution
|
||||||
assert provider._resolve_model_name("opus") == "anthropic/claude-opus-4"
|
assert provider._resolve_model_name("opus") == "anthropic/claude-opus-4.1"
|
||||||
assert provider._resolve_model_name("sonnet") == "anthropic/claude-sonnet-4"
|
assert provider._resolve_model_name("sonnet") == "anthropic/claude-sonnet-4.1"
|
||||||
assert provider._resolve_model_name("o3") == "openai/o3"
|
assert provider._resolve_model_name("o3") == "openai/o3"
|
||||||
assert provider._resolve_model_name("o3-mini") == "openai/o3-mini"
|
assert provider._resolve_model_name("o3-mini") == "openai/o3-mini"
|
||||||
assert provider._resolve_model_name("o3mini") == "openai/o3-mini"
|
assert provider._resolve_model_name("o3mini") == "openai/o3-mini"
|
||||||
assert provider._resolve_model_name("o4-mini") == "openai/o4-mini"
|
assert provider._resolve_model_name("o4-mini") == "openai/o4-mini"
|
||||||
assert provider._resolve_model_name("o4-mini") == "openai/o4-mini"
|
assert provider._resolve_model_name("o4-mini") == "openai/o4-mini"
|
||||||
assert provider._resolve_model_name("claude") == "anthropic/claude-sonnet-4"
|
assert provider._resolve_model_name("claude") == "anthropic/claude-sonnet-4.1"
|
||||||
assert provider._resolve_model_name("mistral") == "mistralai/mistral-large-2411"
|
assert provider._resolve_model_name("mistral") == "mistralai/mistral-large-2411"
|
||||||
assert provider._resolve_model_name("deepseek") == "deepseek/deepseek-r1-0528"
|
assert provider._resolve_model_name("deepseek") == "deepseek/deepseek-r1-0528"
|
||||||
assert provider._resolve_model_name("r1") == "deepseek/deepseek-r1-0528"
|
assert provider._resolve_model_name("r1") == "deepseek/deepseek-r1-0528"
|
||||||
|
|
||||||
# Test case-insensitive
|
# Test case-insensitive
|
||||||
assert provider._resolve_model_name("OPUS") == "anthropic/claude-opus-4"
|
assert provider._resolve_model_name("OPUS") == "anthropic/claude-opus-4.1"
|
||||||
assert provider._resolve_model_name("O3") == "openai/o3"
|
assert provider._resolve_model_name("O3") == "openai/o3"
|
||||||
assert provider._resolve_model_name("Mistral") == "mistralai/mistral-large-2411"
|
assert provider._resolve_model_name("Mistral") == "mistralai/mistral-large-2411"
|
||||||
assert provider._resolve_model_name("CLAUDE") == "anthropic/claude-sonnet-4"
|
assert provider._resolve_model_name("CLAUDE") == "anthropic/claude-sonnet-4.1"
|
||||||
|
|
||||||
# Test direct model names (should pass through unchanged)
|
# Test direct model names (should pass through unchanged)
|
||||||
assert provider._resolve_model_name("anthropic/claude-opus-4") == "anthropic/claude-opus-4"
|
assert provider._resolve_model_name("anthropic/claude-opus-4.1") == "anthropic/claude-opus-4.1"
|
||||||
assert provider._resolve_model_name("openai/o3") == "openai/o3"
|
assert provider._resolve_model_name("openai/o3") == "openai/o3"
|
||||||
|
|
||||||
# Test unknown models pass through
|
# Test unknown models pass through
|
||||||
@@ -155,8 +155,8 @@ class TestOpenRouterAutoMode:
|
|||||||
"google/gemini-2.5-pro",
|
"google/gemini-2.5-pro",
|
||||||
"openai/o3",
|
"openai/o3",
|
||||||
"openai/o3-mini",
|
"openai/o3-mini",
|
||||||
"anthropic/claude-opus-4",
|
"anthropic/claude-opus-4.1",
|
||||||
"anthropic/claude-sonnet-4",
|
"anthropic/claude-sonnet-4.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
ModelProviderRegistry.register_provider(ProviderType.OPENROUTER, OpenRouterProvider)
|
ModelProviderRegistry.register_provider(ProviderType.OPENROUTER, OpenRouterProvider)
|
||||||
@@ -181,7 +181,7 @@ class TestOpenRouterAutoMode:
|
|||||||
os.environ.pop("OPENAI_API_KEY", None)
|
os.environ.pop("OPENAI_API_KEY", None)
|
||||||
os.environ["OPENROUTER_API_KEY"] = "test-openrouter-key"
|
os.environ["OPENROUTER_API_KEY"] = "test-openrouter-key"
|
||||||
os.environ.pop("OPENROUTER_ALLOWED_MODELS", None)
|
os.environ.pop("OPENROUTER_ALLOWED_MODELS", None)
|
||||||
os.environ["OPENROUTER_ALLOWED_MODELS"] = "anthropic/claude-opus-4,google/gemini-2.5-flash"
|
os.environ["OPENROUTER_ALLOWED_MODELS"] = "anthropic/claude-opus-4.1,google/gemini-2.5-flash"
|
||||||
os.environ["DEFAULT_MODEL"] = "auto"
|
os.environ["DEFAULT_MODEL"] = "auto"
|
||||||
|
|
||||||
# Force reload to pick up new environment variable
|
# Force reload to pick up new environment variable
|
||||||
@@ -193,8 +193,8 @@ class TestOpenRouterAutoMode:
|
|||||||
mock_models = [
|
mock_models = [
|
||||||
"google/gemini-2.5-flash",
|
"google/gemini-2.5-flash",
|
||||||
"google/gemini-2.5-pro",
|
"google/gemini-2.5-pro",
|
||||||
"anthropic/claude-opus-4",
|
"anthropic/claude-opus-4.1",
|
||||||
"anthropic/claude-sonnet-4",
|
"anthropic/claude-sonnet-4.1",
|
||||||
]
|
]
|
||||||
mock_registry.list_models.return_value = mock_models
|
mock_registry.list_models.return_value = mock_models
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ class TestOpenRouterAutoMode:
|
|||||||
|
|
||||||
assert len(available_models) > 0, "Should have some allowed models"
|
assert len(available_models) > 0, "Should have some allowed models"
|
||||||
|
|
||||||
expected_allowed = {"google/gemini-2.5-flash", "anthropic/claude-opus-4"}
|
expected_allowed = {"google/gemini-2.5-flash", "anthropic/claude-opus-4.1"}
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
set(available_models.keys()) == expected_allowed
|
set(available_models.keys()) == expected_allowed
|
||||||
@@ -263,7 +263,7 @@ class TestOpenRouterRegistry:
|
|||||||
# Should have loaded models
|
# Should have loaded models
|
||||||
models = registry.list_models()
|
models = registry.list_models()
|
||||||
assert len(models) > 0
|
assert len(models) > 0
|
||||||
assert "anthropic/claude-opus-4" in models
|
assert "anthropic/claude-opus-4.1" in models
|
||||||
assert "openai/o3" in models
|
assert "openai/o3" in models
|
||||||
|
|
||||||
# Should have loaded aliases
|
# Should have loaded aliases
|
||||||
@@ -282,13 +282,13 @@ class TestOpenRouterRegistry:
|
|||||||
# Test known model
|
# Test known model
|
||||||
caps = registry.get_capabilities("opus")
|
caps = registry.get_capabilities("opus")
|
||||||
assert caps is not None
|
assert caps is not None
|
||||||
assert caps.model_name == "anthropic/claude-opus-4"
|
assert caps.model_name == "anthropic/claude-opus-4.1"
|
||||||
assert caps.context_window == 200000 # Claude's context window
|
assert caps.context_window == 200000 # Claude's context window
|
||||||
|
|
||||||
# Test using full model name
|
# Test using full model name
|
||||||
caps = registry.get_capabilities("anthropic/claude-opus-4")
|
caps = registry.get_capabilities("anthropic/claude-opus-4.1")
|
||||||
assert caps is not None
|
assert caps is not None
|
||||||
assert caps.model_name == "anthropic/claude-opus-4"
|
assert caps.model_name == "anthropic/claude-opus-4.1"
|
||||||
|
|
||||||
# Test unknown model
|
# Test unknown model
|
||||||
caps = registry.get_capabilities("non-existent-model")
|
caps = registry.get_capabilities("non-existent-model")
|
||||||
@@ -301,11 +301,11 @@ class TestOpenRouterRegistry:
|
|||||||
registry = OpenRouterModelRegistry()
|
registry = OpenRouterModelRegistry()
|
||||||
|
|
||||||
# All these should resolve to Claude Sonnet
|
# All these should resolve to Claude Sonnet
|
||||||
sonnet_aliases = ["sonnet", "claude", "claude-sonnet", "claude4-sonnet"]
|
sonnet_aliases = ["sonnet", "claude", "claude-sonnet", "claude-4.1-sonnet"]
|
||||||
for alias in sonnet_aliases:
|
for alias in sonnet_aliases:
|
||||||
config = registry.resolve(alias)
|
config = registry.resolve(alias)
|
||||||
assert config is not None
|
assert config is not None
|
||||||
assert config.model_name == "anthropic/claude-sonnet-4"
|
assert config.model_name == "anthropic/claude-sonnet-4.1"
|
||||||
|
|
||||||
|
|
||||||
class TestOpenRouterFunctionality:
|
class TestOpenRouterFunctionality:
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ class TestOpenRouterModelRegistry:
|
|||||||
|
|
||||||
# Test various aliases
|
# Test various aliases
|
||||||
test_cases = [
|
test_cases = [
|
||||||
("opus", "anthropic/claude-opus-4"),
|
("opus", "anthropic/claude-opus-4.1"),
|
||||||
("OPUS", "anthropic/claude-opus-4"), # Case insensitive
|
("OPUS", "anthropic/claude-opus-4.1"), # Case insensitive
|
||||||
("claude", "anthropic/claude-sonnet-4"),
|
("claude", "anthropic/claude-sonnet-4.1"),
|
||||||
("o3", "openai/o3"),
|
("o3", "openai/o3"),
|
||||||
("deepseek", "deepseek/deepseek-r1-0528"),
|
("deepseek", "deepseek/deepseek-r1-0528"),
|
||||||
("mistral", "mistralai/mistral-large-2411"),
|
("mistral", "mistralai/mistral-large-2411"),
|
||||||
@@ -105,9 +105,9 @@ class TestOpenRouterModelRegistry:
|
|||||||
registry = OpenRouterModelRegistry()
|
registry = OpenRouterModelRegistry()
|
||||||
|
|
||||||
# Should be able to look up by full model name
|
# Should be able to look up by full model name
|
||||||
config = registry.resolve("anthropic/claude-opus-4")
|
config = registry.resolve("anthropic/claude-opus-4.1")
|
||||||
assert config is not None
|
assert config is not None
|
||||||
assert config.model_name == "anthropic/claude-opus-4"
|
assert config.model_name == "anthropic/claude-opus-4.1"
|
||||||
|
|
||||||
config = registry.resolve("openai/o3")
|
config = registry.resolve("openai/o3")
|
||||||
assert config is not None
|
assert config is not None
|
||||||
@@ -131,8 +131,8 @@ class TestOpenRouterModelRegistry:
|
|||||||
|
|
||||||
# Registry now returns ModelCapabilities objects directly
|
# Registry now returns ModelCapabilities objects directly
|
||||||
assert config.provider == ProviderType.OPENROUTER
|
assert config.provider == ProviderType.OPENROUTER
|
||||||
assert config.model_name == "anthropic/claude-opus-4"
|
assert config.model_name == "anthropic/claude-opus-4.1"
|
||||||
assert config.friendly_name == "OpenRouter (anthropic/claude-opus-4)"
|
assert config.friendly_name == "OpenRouter (anthropic/claude-opus-4.1)"
|
||||||
assert config.context_window == 200000
|
assert config.context_window == 200000
|
||||||
assert not config.supports_extended_thinking
|
assert not config.supports_extended_thinking
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ class TestParseModelOption:
|
|||||||
|
|
||||||
def test_openrouter_beta_suffix_preserved(self):
|
def test_openrouter_beta_suffix_preserved(self):
|
||||||
"""Test that OpenRouter :beta suffix is preserved as part of model name."""
|
"""Test that OpenRouter :beta suffix is preserved as part of model name."""
|
||||||
model, option = parse_model_option("anthropic/claude-3-opus:beta")
|
model, option = parse_model_option("anthropic/claude-opus-4.1:beta")
|
||||||
assert model == "anthropic/claude-3-opus:beta"
|
assert model == "anthropic/claude-opus-4.1:beta"
|
||||||
assert option is None
|
assert option is None
|
||||||
|
|
||||||
def test_openrouter_preview_suffix_preserved(self):
|
def test_openrouter_preview_suffix_preserved(self):
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ class TestOpenRouterAliasRestrictions:
|
|||||||
os.environ.pop("OPENAI_API_KEY", None)
|
os.environ.pop("OPENAI_API_KEY", None)
|
||||||
os.environ.pop("XAI_API_KEY", None)
|
os.environ.pop("XAI_API_KEY", None)
|
||||||
os.environ["OPENROUTER_API_KEY"] = "test-key"
|
os.environ["OPENROUTER_API_KEY"] = "test-key"
|
||||||
os.environ["OPENROUTER_ALLOWED_MODELS"] = "o3-mini,anthropic/claude-opus-4,flash"
|
os.environ["OPENROUTER_ALLOWED_MODELS"] = "o3-mini,anthropic/claude-opus-4.1,flash"
|
||||||
|
|
||||||
# Register OpenRouter provider
|
# Register OpenRouter provider
|
||||||
from providers.openrouter import OpenRouterProvider
|
from providers.openrouter import OpenRouterProvider
|
||||||
@@ -356,7 +356,7 @@ class TestOpenRouterAliasRestrictions:
|
|||||||
|
|
||||||
expected_models = {
|
expected_models = {
|
||||||
"openai/o3-mini", # from alias
|
"openai/o3-mini", # from alias
|
||||||
"anthropic/claude-opus-4", # full name
|
"anthropic/claude-opus-4.1", # full name
|
||||||
"google/gemini-2.5-flash", # from alias
|
"google/gemini-2.5-flash", # from alias
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ class TestLocaleModelIntegration(unittest.TestCase):
|
|||||||
def test_model_name_resolution_utf8(self):
|
def test_model_name_resolution_utf8(self):
|
||||||
"""Test model name resolution with UTF-8."""
|
"""Test model name resolution with UTF-8."""
|
||||||
provider = OpenAIModelProvider(api_key="test")
|
provider = OpenAIModelProvider(api_key="test")
|
||||||
model_names = ["gpt-4", "gemini-2.5-flash", "claude-3-opus", "o3-pro"]
|
model_names = ["gpt-4", "gemini-2.5-flash", "anthropic/claude-opus-4.1", "o3-pro"]
|
||||||
for model_name in model_names:
|
for model_name in model_names:
|
||||||
resolved = provider._resolve_model_name(model_name)
|
resolved = provider._resolve_model_name(model_name)
|
||||||
self.assertIsInstance(resolved, str)
|
self.assertIsInstance(resolved, str)
|
||||||
|
|||||||
@@ -105,19 +105,19 @@ class TestSupportedModelsAliases:
|
|||||||
# Test specific aliases
|
# Test specific aliases
|
||||||
assert "o3" in provider.SUPPORTED_MODELS["o3-2025-04-16"].aliases
|
assert "o3" in provider.SUPPORTED_MODELS["o3-2025-04-16"].aliases
|
||||||
assert "o4-mini" in provider.SUPPORTED_MODELS["o4-mini-2025-04-16"].aliases
|
assert "o4-mini" in provider.SUPPORTED_MODELS["o4-mini-2025-04-16"].aliases
|
||||||
assert "sonnet-4" in provider.SUPPORTED_MODELS["anthropic.claude-sonnet-4-20250514-v1:0"].aliases
|
assert "sonnet-4.1" in provider.SUPPORTED_MODELS["anthropic.claude-sonnet-4.1-20250805-v1:0"].aliases
|
||||||
assert "opus-4" in provider.SUPPORTED_MODELS["anthropic.claude-opus-4-20250514-v1:0"].aliases
|
assert "opus-4.1" in provider.SUPPORTED_MODELS["anthropic.claude-opus-4.1-20250805-v1:0"].aliases
|
||||||
assert "gemini-2.5-pro" in provider.SUPPORTED_MODELS["gemini-2.5-pro-preview-05-06"].aliases
|
assert "gemini-2.5-pro" in provider.SUPPORTED_MODELS["gemini-2.5-pro-preview-05-06"].aliases
|
||||||
|
|
||||||
# Test alias resolution
|
# Test alias resolution
|
||||||
assert provider._resolve_model_name("o3") == "o3-2025-04-16"
|
assert provider._resolve_model_name("o3") == "o3-2025-04-16"
|
||||||
assert provider._resolve_model_name("o4-mini") == "o4-mini-2025-04-16"
|
assert provider._resolve_model_name("o4-mini") == "o4-mini-2025-04-16"
|
||||||
assert provider._resolve_model_name("sonnet-4") == "anthropic.claude-sonnet-4-20250514-v1:0"
|
assert provider._resolve_model_name("sonnet-4.1") == "anthropic.claude-sonnet-4.1-20250805-v1:0"
|
||||||
assert provider._resolve_model_name("opus-4") == "anthropic.claude-opus-4-20250514-v1:0"
|
assert provider._resolve_model_name("opus-4.1") == "anthropic.claude-opus-4.1-20250805-v1:0"
|
||||||
|
|
||||||
# Test case insensitive resolution
|
# Test case insensitive resolution
|
||||||
assert provider._resolve_model_name("O3") == "o3-2025-04-16"
|
assert provider._resolve_model_name("O3") == "o3-2025-04-16"
|
||||||
assert provider._resolve_model_name("SONNET-4") == "anthropic.claude-sonnet-4-20250514-v1:0"
|
assert provider._resolve_model_name("SONNET-4.1") == "anthropic.claude-sonnet-4.1-20250805-v1:0"
|
||||||
|
|
||||||
def test_list_models_includes_aliases(self):
|
def test_list_models_includes_aliases(self):
|
||||||
"""Test that list_models returns both base models and aliases."""
|
"""Test that list_models returns both base models and aliases."""
|
||||||
|
|||||||
@@ -1401,7 +1401,7 @@ When recommending searches, be specific about what information you need and why
|
|||||||
"content": (
|
"content": (
|
||||||
f"Image support not available: Model '{model_name}' does not support image processing. "
|
f"Image support not available: Model '{model_name}' does not support image processing. "
|
||||||
f"Please use a vision-capable model such as 'gemini-2.5-flash', 'o3', "
|
f"Please use a vision-capable model such as 'gemini-2.5-flash', 'o3', "
|
||||||
f"or 'claude-3-opus' for image analysis tasks."
|
f"or 'claude-opus-4.1' for image analysis tasks."
|
||||||
),
|
),
|
||||||
"content_type": "text",
|
"content_type": "text",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|||||||
Reference in New Issue
Block a user