* feat: Update Claude model references from v3 to v4 - Update model configurations from claude-3-opus to claude-4-opus - Update model configurations from claude-3-sonnet to claude-4-sonnet - Maintain backward compatibility through existing aliases (opus, sonnet, claude) - Update provider registry preferred models list - Update all test cases and assertions to reflect new model names - Update documentation and examples consistently across all files - Add Claude 4 model support while preserving existing functionality Files modified: 15 (config, docs, providers, tests, tools) Pattern: Systematic claude-3-* → claude-4-* model reference migration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * PR feedback: changed anthropic/claude-4-opus -> anthropic/claude-opus-4 and anthropic/claude-4-haiku -> anthropic/claude-3.5-haiku * changed anthropic/claude-4-sonnet -> anthropic/claude-sonnet-4 * PR feedback removed specific model from test mock * PR feedback removed base.py --------- Co-authored-by: Omry Nachman <omry@wix.com> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
"Self-hosted APIs - Any OpenAI-compatible endpoint"
|
"Self-hosted APIs - Any OpenAI-compatible endpoint"
|
||||||
],
|
],
|
||||||
"documentation": "https://github.com/BeehiveInnovations/zen-mcp-server/blob/main/docs/custom_models.md",
|
"documentation": "https://github.com/BeehiveInnovations/zen-mcp-server/blob/main/docs/custom_models.md",
|
||||||
"usage": "Models can be accessed via aliases (e.g., 'opus', 'local-llama') or full names (e.g., 'anthropic/claude-3-opus', 'llama3.2')",
|
"usage": "Models can be accessed via aliases (e.g., 'opus', 'local-llama') or full names (e.g., 'anthropic/claude-opus-4', 'llama3.2')",
|
||||||
"instructions": [
|
"instructions": [
|
||||||
"Add new models by copying an existing entry and modifying it",
|
"Add new models by copying an existing entry and modifying it",
|
||||||
"Aliases are case-insensitive and should be unique across all models",
|
"Aliases are case-insensitive and should be unique across all models",
|
||||||
@@ -15,11 +15,11 @@
|
|||||||
"Set supports_* flags based on the model's actual capabilities",
|
"Set supports_* flags based on the model's actual capabilities",
|
||||||
"Set is_custom=true for models that should ONLY work with custom endpoints (Ollama, vLLM, etc.)",
|
"Set is_custom=true for models that should ONLY work with custom endpoints (Ollama, vLLM, etc.)",
|
||||||
"Models not listed here will use generic defaults (32K context window, basic features)",
|
"Models not listed here will use generic defaults (32K context window, basic features)",
|
||||||
"For OpenRouter models: Use official OpenRouter model names (e.g., 'anthropic/claude-3-opus')",
|
"For OpenRouter models: Use official OpenRouter model names (e.g., 'anthropic/claude-opus-4')",
|
||||||
"For local/custom models: Use model names as they appear in your API (e.g., 'llama3.2', 'gpt-3.5-turbo')"
|
"For local/custom models: Use model names as they appear in your API (e.g., 'llama3.2', 'gpt-3.5-turbo')"
|
||||||
],
|
],
|
||||||
"field_descriptions": {
|
"field_descriptions": {
|
||||||
"model_name": "The model identifier - OpenRouter format (e.g., 'anthropic/claude-3-opus') or custom model name (e.g., 'llama3.2')",
|
"model_name": "The model identifier - OpenRouter format (e.g., 'anthropic/claude-opus-4') or custom model name (e.g., 'llama3.2')",
|
||||||
"aliases": "Array of short names users can type instead of the full model name",
|
"aliases": "Array of short names users can type instead of the full model name",
|
||||||
"context_window": "Total number of tokens the model can process (input + output combined)",
|
"context_window": "Total number of tokens the model can process (input + output combined)",
|
||||||
"supports_extended_thinking": "Whether the model supports extended reasoning tokens (currently none do via OpenRouter or custom APIs)",
|
"supports_extended_thinking": "Whether the model supports extended reasoning tokens (currently none do via OpenRouter or custom APIs)",
|
||||||
@@ -49,29 +49,29 @@
|
|||||||
},
|
},
|
||||||
"models": [
|
"models": [
|
||||||
{
|
{
|
||||||
"model_name": "anthropic/claude-3-opus",
|
"model_name": "anthropic/claude-opus-4",
|
||||||
"aliases": ["opus", "claude-opus", "claude3-opus", "claude-3-opus"],
|
"aliases": ["opus", "claude-opus", "claude4-opus", "claude-4-opus"],
|
||||||
"context_window": 200000,
|
"context_window": 200000,
|
||||||
"supports_extended_thinking": false,
|
"supports_extended_thinking": false,
|
||||||
"supports_json_mode": false,
|
"supports_json_mode": false,
|
||||||
"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 3 Opus - Most capable Claude model with vision"
|
"description": "Claude 4 Opus - Most capable Claude model with vision"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model_name": "anthropic/claude-3-sonnet",
|
"model_name": "anthropic/claude-sonnet-4",
|
||||||
"aliases": ["sonnet", "claude-sonnet", "claude3-sonnet", "claude-3-sonnet", "claude"],
|
"aliases": ["sonnet", "claude-sonnet", "claude4-sonnet", "claude-4-sonnet", "claude"],
|
||||||
"context_window": 200000,
|
"context_window": 200000,
|
||||||
"supports_extended_thinking": false,
|
"supports_extended_thinking": false,
|
||||||
"supports_json_mode": false,
|
"supports_json_mode": false,
|
||||||
"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 3 Sonnet - Balanced performance with vision"
|
"description": "Claude 4 Sonnet - Balanced performance with vision"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model_name": "anthropic/claude-3-haiku",
|
"model_name": "anthropic/claude-3.5-haiku",
|
||||||
"aliases": ["haiku", "claude-haiku", "claude3-haiku", "claude-3-haiku"],
|
"aliases": ["haiku", "claude-haiku", "claude3-haiku", "claude-3-haiku"],
|
||||||
"context_window": 200000,
|
"context_window": 200000,
|
||||||
"supports_extended_thinking": false,
|
"supports_extended_thinking": false,
|
||||||
|
|||||||
@@ -690,7 +690,7 @@ When a user requests a model (e.g., "pro", "o3", "example-large-v1"), the system
|
|||||||
2. OpenAI skips (Gemini already handled it)
|
2. OpenAI skips (Gemini already handled it)
|
||||||
3. OpenRouter never sees it
|
3. OpenRouter never sees it
|
||||||
|
|
||||||
### Example: Model "claude-3-opus"
|
### Example: Model "claude-4-opus"
|
||||||
|
|
||||||
1. **Gemini provider** checks: NO, not my model → skip
|
1. **Gemini provider** checks: NO, not my model → skip
|
||||||
2. **OpenAI provider** checks: NO, not my model → skip
|
2. **OpenAI provider** checks: NO, not my model → skip
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ The server uses `conf/custom_models.json` to map convenient aliases to both Open
|
|||||||
|
|
||||||
| Alias | Maps to OpenRouter Model |
|
| Alias | Maps to OpenRouter Model |
|
||||||
|-------|-------------------------|
|
|-------|-------------------------|
|
||||||
| `opus` | `anthropic/claude-3-opus` |
|
| `opus` | `anthropic/claude-opus-4` |
|
||||||
| `sonnet`, `claude` | `anthropic/claude-3-sonnet` |
|
| `sonnet`, `claude` | `anthropic/claude-sonnet-4` |
|
||||||
| `haiku` | `anthropic/claude-3-haiku` |
|
| `haiku` | `anthropic/claude-3.5-haiku` |
|
||||||
| `gpt4o`, `4o` | `openai/gpt-4o` |
|
| `gpt4o`, `4o` | `openai/gpt-4o` |
|
||||||
| `gpt4o-mini`, `4o-mini` | `openai/gpt-4o-mini` |
|
| `gpt4o-mini`, `4o-mini` | `openai/gpt-4o-mini` |
|
||||||
| `pro`, `gemini` | `google/gemini-2.5-pro` |
|
| `pro`, `gemini` | `google/gemini-2.5-pro` |
|
||||||
@@ -151,8 +151,8 @@ CUSTOM_MODEL_NAME=your-loaded-model
|
|||||||
**Using model aliases (from conf/custom_models.json):**
|
**Using model aliases (from conf/custom_models.json):**
|
||||||
```
|
```
|
||||||
# OpenRouter models:
|
# OpenRouter models:
|
||||||
"Use opus for deep analysis" # → anthropic/claude-3-opus
|
"Use opus for deep analysis" # → anthropic/claude-opus-4
|
||||||
"Use sonnet to review this code" # → anthropic/claude-3-sonnet
|
"Use sonnet to review this code" # → anthropic/claude-sonnet-4
|
||||||
"Use pro via zen to analyze this" # → google/gemini-2.5-pro
|
"Use pro via zen to analyze this" # → google/gemini-2.5-pro
|
||||||
"Use gpt4o via zen to analyze this" # → openai/gpt-4o
|
"Use gpt4o via zen to analyze this" # → openai/gpt-4o
|
||||||
"Use mistral via zen to optimize" # → mistral/mistral-large
|
"Use mistral via zen to optimize" # → mistral/mistral-large
|
||||||
@@ -165,7 +165,7 @@ CUSTOM_MODEL_NAME=your-loaded-model
|
|||||||
**Using full model names:**
|
**Using full model names:**
|
||||||
```
|
```
|
||||||
# OpenRouter models:
|
# OpenRouter models:
|
||||||
"Use anthropic/claude-3-opus via zen for deep analysis"
|
"Use anthropic/claude-opus-4 via zen for deep analysis"
|
||||||
"Use openai/gpt-4o via zen to debug this"
|
"Use openai/gpt-4o via zen to debug this"
|
||||||
"Use deepseek/deepseek-coder via zen to generate code"
|
"Use deepseek/deepseek-coder via zen to generate code"
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ Edit `conf/custom_models.json` to add new models. The configuration supports bot
|
|||||||
|
|
||||||
Popular models available through OpenRouter:
|
Popular models available through OpenRouter:
|
||||||
- **GPT-4** - OpenAI's most capable model
|
- **GPT-4** - OpenAI's most capable model
|
||||||
- **Claude 3** - Anthropic's models (Opus, Sonnet, Haiku)
|
- **Claude 4** - Anthropic's models (Opus, Sonnet, Haiku)
|
||||||
- **Mistral** - Including Mistral Large
|
- **Mistral** - Including Mistral Large
|
||||||
- **Llama 3** - Meta's open models
|
- **Llama 3** - Meta's open models
|
||||||
- Many more at [openrouter.ai/models](https://openrouter.ai/models)
|
- Many more at [openrouter.ai/models](https://openrouter.ai/models)
|
||||||
|
|||||||
@@ -402,8 +402,8 @@ class ModelProviderRegistry:
|
|||||||
if openrouter_provider:
|
if openrouter_provider:
|
||||||
# Prefer models known for deep reasoning
|
# Prefer models known for deep reasoning
|
||||||
preferred_models = [
|
preferred_models = [
|
||||||
"anthropic/claude-3.5-sonnet",
|
"anthropic/claude-sonnet-4",
|
||||||
"anthropic/claude-3-opus-20240229",
|
"anthropic/claude-opus-4",
|
||||||
"google/gemini-2.5-pro",
|
"google/gemini-2.5-pro",
|
||||||
"google/gemini-pro-1.5",
|
"google/gemini-pro-1.5",
|
||||||
"meta-llama/llama-3.1-70b-instruct",
|
"meta-llama/llama-3.1-70b-instruct",
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class OpenRouterModelsTest(BaseSimulatorTest):
|
|||||||
self.logger.info(" ✅ Direct OpenRouter model call completed")
|
self.logger.info(" ✅ Direct OpenRouter model call completed")
|
||||||
|
|
||||||
# Test 5: OpenRouter alias from config
|
# Test 5: OpenRouter alias from config
|
||||||
self.logger.info(" 5: Testing OpenRouter alias from config ('opus' -> anthropic/claude-3-opus)")
|
self.logger.info(" 5: Testing OpenRouter alias from config ('opus' -> anthropic/claude-opus-4)")
|
||||||
|
|
||||||
response5, _ = self.call_mcp_tool(
|
response5, _ = self.call_mcp_tool(
|
||||||
"chat",
|
"chat",
|
||||||
|
|||||||
@@ -527,7 +527,7 @@ class TestAutoModeComprehensive:
|
|||||||
"google/gemini-2.5-pro",
|
"google/gemini-2.5-pro",
|
||||||
"openai/o3",
|
"openai/o3",
|
||||||
"openai/o4-mini",
|
"openai/o4-mini",
|
||||||
"anthropic/claude-3-opus",
|
"anthropic/claude-opus-4",
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch.object(OpenRouterProvider, "_registry", mock_registry):
|
with patch.object(OpenRouterProvider, "_registry", mock_registry):
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ class TestListModelsRestrictions(unittest.TestCase):
|
|||||||
# Set up mock to return only allowed models when restrictions are respected
|
# Set up mock to return only allowed models when restrictions are respected
|
||||||
# Include both aliased models and full model names without aliases
|
# Include both aliased models and full model names without aliases
|
||||||
self.mock_openrouter.list_models.return_value = [
|
self.mock_openrouter.list_models.return_value = [
|
||||||
"anthropic/claude-3-opus-20240229", # Has alias "opus"
|
"anthropic/claude-opus-4", # Has alias "opus"
|
||||||
"anthropic/claude-3-sonnet-20240229", # Has alias "sonnet"
|
"anthropic/claude-sonnet-4", # Has alias "sonnet"
|
||||||
"deepseek/deepseek-r1-0528:free", # No alias, full name
|
"deepseek/deepseek-r1-0528:free", # No alias, full name
|
||||||
"qwen/qwen3-235b-a22b-04-28:free", # No alias, full name
|
"qwen/qwen3-235b-a22b-04-28:free", # No alias, full name
|
||||||
]
|
]
|
||||||
@@ -67,12 +67,12 @@ class TestListModelsRestrictions(unittest.TestCase):
|
|||||||
def resolve_side_effect(model_name):
|
def resolve_side_effect(model_name):
|
||||||
if "opus" in model_name.lower():
|
if "opus" in model_name.lower():
|
||||||
config = MagicMock()
|
config = MagicMock()
|
||||||
config.model_name = "anthropic/claude-3-opus-20240229"
|
config.model_name = "anthropic/claude-opus-4-20240229"
|
||||||
config.context_window = 200000
|
config.context_window = 200000
|
||||||
return config
|
return config
|
||||||
elif "sonnet" in model_name.lower():
|
elif "sonnet" in model_name.lower():
|
||||||
config = MagicMock()
|
config = MagicMock()
|
||||||
config.model_name = "anthropic/claude-3-sonnet-20240229"
|
config.model_name = "anthropic/claude-sonnet-4-20240229"
|
||||||
config.context_window = 200000
|
config.context_window = 200000
|
||||||
return config
|
return config
|
||||||
return None # No config for models without aliases
|
return None # No config for models without aliases
|
||||||
@@ -93,8 +93,8 @@ class TestListModelsRestrictions(unittest.TestCase):
|
|||||||
mock_get_models.return_value = {
|
mock_get_models.return_value = {
|
||||||
"gemini-2.5-flash": ProviderType.GOOGLE,
|
"gemini-2.5-flash": ProviderType.GOOGLE,
|
||||||
"gemini-2.5-pro": ProviderType.GOOGLE,
|
"gemini-2.5-pro": ProviderType.GOOGLE,
|
||||||
"anthropic/claude-3-opus-20240229": ProviderType.OPENROUTER,
|
"anthropic/claude-opus-4-20240229": ProviderType.OPENROUTER,
|
||||||
"anthropic/claude-3-sonnet-20240229": ProviderType.OPENROUTER,
|
"anthropic/claude-sonnet-4-20240229": ProviderType.OPENROUTER,
|
||||||
"deepseek/deepseek-r1-0528:free": ProviderType.OPENROUTER,
|
"deepseek/deepseek-r1-0528:free": ProviderType.OPENROUTER,
|
||||||
"qwen/qwen3-235b-a22b-04-28:free": ProviderType.OPENROUTER,
|
"qwen/qwen3-235b-a22b-04-28:free": ProviderType.OPENROUTER,
|
||||||
}
|
}
|
||||||
@@ -172,7 +172,7 @@ class TestListModelsRestrictions(unittest.TestCase):
|
|||||||
utils.model_restrictions._restriction_service = None
|
utils.model_restrictions._restriction_service = None
|
||||||
|
|
||||||
# Set up mock to return many models when no restrictions
|
# Set up mock to return many models when no restrictions
|
||||||
all_models = [f"provider{i//10}/model-{i}" for i in range(50)] # Simulate 50 models from different providers
|
all_models = [f"provider{i // 10}/model-{i}" for i in range(50)] # Simulate 50 models from different providers
|
||||||
self.mock_openrouter.list_models.return_value = all_models
|
self.mock_openrouter.list_models.return_value = all_models
|
||||||
|
|
||||||
# Mock registry instance
|
# Mock registry instance
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class TestModelRestrictionService:
|
|||||||
assert service.is_allowed(ProviderType.OPENAI, "o3-mini")
|
assert service.is_allowed(ProviderType.OPENAI, "o3-mini")
|
||||||
assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-pro")
|
assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-pro")
|
||||||
assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-flash")
|
assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-flash")
|
||||||
assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-3-opus")
|
assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-opus-4")
|
||||||
assert service.is_allowed(ProviderType.OPENROUTER, "openai/o3")
|
assert service.is_allowed(ProviderType.OPENROUTER, "openai/o3")
|
||||||
|
|
||||||
# Should have no restrictions
|
# Should have no restrictions
|
||||||
@@ -44,7 +44,7 @@ class TestModelRestrictionService:
|
|||||||
|
|
||||||
# Google and OpenRouter should have no restrictions
|
# Google and OpenRouter should have no restrictions
|
||||||
assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-pro")
|
assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-pro")
|
||||||
assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-3-opus")
|
assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-opus-4")
|
||||||
|
|
||||||
def test_load_multiple_models_restriction(self):
|
def test_load_multiple_models_restriction(self):
|
||||||
"""Test loading multiple allowed models."""
|
"""Test loading multiple allowed models."""
|
||||||
@@ -159,7 +159,7 @@ class TestModelRestrictionService:
|
|||||||
# Should only allow specified OpenRouter models
|
# Should only allow specified OpenRouter models
|
||||||
assert service.is_allowed(ProviderType.OPENROUTER, "opus")
|
assert service.is_allowed(ProviderType.OPENROUTER, "opus")
|
||||||
assert service.is_allowed(ProviderType.OPENROUTER, "sonnet")
|
assert service.is_allowed(ProviderType.OPENROUTER, "sonnet")
|
||||||
assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-3-opus", "opus") # With original name
|
assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-opus-4", "opus") # With original name
|
||||||
assert not service.is_allowed(ProviderType.OPENROUTER, "haiku")
|
assert not service.is_allowed(ProviderType.OPENROUTER, "haiku")
|
||||||
assert not service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-3-haiku")
|
assert not service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-3-haiku")
|
||||||
assert not service.is_allowed(ProviderType.OPENROUTER, "mistral-large")
|
assert not service.is_allowed(ProviderType.OPENROUTER, "mistral-large")
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class TestOpenRouterProvider:
|
|||||||
|
|
||||||
# Should accept any model - OpenRouter handles validation
|
# Should accept any model - OpenRouter handles validation
|
||||||
assert provider.validate_model_name("gpt-4") is True
|
assert provider.validate_model_name("gpt-4") is True
|
||||||
assert provider.validate_model_name("claude-3-opus") is True
|
assert provider.validate_model_name("claude-4-opus") is True
|
||||||
assert provider.validate_model_name("any-model-name") is True
|
assert provider.validate_model_name("any-model-name") is True
|
||||||
assert provider.validate_model_name("GPT-4") is True
|
assert provider.validate_model_name("GPT-4") is True
|
||||||
assert provider.validate_model_name("unknown-model") is True
|
assert provider.validate_model_name("unknown-model") is True
|
||||||
@@ -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-3-opus"
|
assert provider._resolve_model_name("opus") == "anthropic/claude-opus-4"
|
||||||
assert provider._resolve_model_name("sonnet") == "anthropic/claude-3-sonnet"
|
assert provider._resolve_model_name("sonnet") == "anthropic/claude-sonnet-4"
|
||||||
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-high") == "openai/o4-mini-high"
|
assert provider._resolve_model_name("o4-mini-high") == "openai/o4-mini-high"
|
||||||
assert provider._resolve_model_name("claude") == "anthropic/claude-3-sonnet"
|
assert provider._resolve_model_name("claude") == "anthropic/claude-sonnet-4"
|
||||||
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-3-opus"
|
assert provider._resolve_model_name("OPUS") == "anthropic/claude-opus-4"
|
||||||
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-3-sonnet"
|
assert provider._resolve_model_name("CLAUDE") == "anthropic/claude-sonnet-4"
|
||||||
|
|
||||||
# Test direct model names (should pass through unchanged)
|
# Test direct model names (should pass through unchanged)
|
||||||
assert provider._resolve_model_name("anthropic/claude-3-opus") == "anthropic/claude-3-opus"
|
assert provider._resolve_model_name("anthropic/claude-opus-4") == "anthropic/claude-opus-4"
|
||||||
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-3-opus",
|
"anthropic/claude-opus-4",
|
||||||
"anthropic/claude-3-sonnet",
|
"anthropic/claude-sonnet-4",
|
||||||
]
|
]
|
||||||
|
|
||||||
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-3-opus,google/gemini-2.5-flash"
|
os.environ["OPENROUTER_ALLOWED_MODELS"] = "anthropic/claude-opus-4,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-3-opus",
|
"anthropic/claude-opus-4",
|
||||||
"anthropic/claude-3-sonnet",
|
"anthropic/claude-sonnet-4",
|
||||||
]
|
]
|
||||||
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-3-opus"}
|
expected_allowed = {"google/gemini-2.5-flash", "anthropic/claude-opus-4"}
|
||||||
|
|
||||||
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-3-opus" in models
|
assert "anthropic/claude-opus-4" 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-3-opus"
|
assert caps.model_name == "anthropic/claude-opus-4"
|
||||||
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-3-opus")
|
caps = registry.get_capabilities("anthropic/claude-opus-4")
|
||||||
assert caps is not None
|
assert caps is not None
|
||||||
assert caps.model_name == "anthropic/claude-3-opus"
|
assert caps.model_name == "anthropic/claude-opus-4"
|
||||||
|
|
||||||
# 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", "claude3-sonnet"]
|
sonnet_aliases = ["sonnet", "claude", "claude-sonnet", "claude4-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-3-sonnet"
|
assert config.model_name == "anthropic/claude-sonnet-4"
|
||||||
|
|
||||||
|
|
||||||
class TestOpenRouterFunctionality:
|
class TestOpenRouterFunctionality:
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ class TestOpenRouterModelRegistry:
|
|||||||
|
|
||||||
# Test various aliases
|
# Test various aliases
|
||||||
test_cases = [
|
test_cases = [
|
||||||
("opus", "anthropic/claude-3-opus"),
|
("opus", "anthropic/claude-opus-4"),
|
||||||
("OPUS", "anthropic/claude-3-opus"), # Case insensitive
|
("OPUS", "anthropic/claude-opus-4"), # Case insensitive
|
||||||
("claude", "anthropic/claude-3-sonnet"),
|
("claude", "anthropic/claude-sonnet-4"),
|
||||||
("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"),
|
||||||
@@ -92,9 +92,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-3-opus")
|
config = registry.resolve("anthropic/claude-opus-4")
|
||||||
assert config is not None
|
assert config is not None
|
||||||
assert config.model_name == "anthropic/claude-3-opus"
|
assert config.model_name == "anthropic/claude-opus-4"
|
||||||
|
|
||||||
config = registry.resolve("openai/o3")
|
config = registry.resolve("openai/o3")
|
||||||
assert config is not None
|
assert config is not None
|
||||||
@@ -118,7 +118,7 @@ class TestOpenRouterModelRegistry:
|
|||||||
|
|
||||||
caps = config.to_capabilities()
|
caps = config.to_capabilities()
|
||||||
assert caps.provider == ProviderType.OPENROUTER
|
assert caps.provider == ProviderType.OPENROUTER
|
||||||
assert caps.model_name == "anthropic/claude-3-opus"
|
assert caps.model_name == "anthropic/claude-opus-4"
|
||||||
assert caps.friendly_name == "OpenRouter"
|
assert caps.friendly_name == "OpenRouter"
|
||||||
assert caps.context_window == 200000
|
assert caps.context_window == 200000
|
||||||
assert not caps.supports_extended_thinking
|
assert not caps.supports_extended_thinking
|
||||||
|
|||||||
@@ -288,11 +288,11 @@ class TestProviderHelperMethods:
|
|||||||
with patch.object(ModelProviderRegistry, "get_provider") as mock_get_provider:
|
with patch.object(ModelProviderRegistry, "get_provider") as mock_get_provider:
|
||||||
# Mock openrouter provider
|
# Mock openrouter provider
|
||||||
mock_openrouter = MagicMock()
|
mock_openrouter = MagicMock()
|
||||||
mock_openrouter.validate_model_name.side_effect = lambda m: m == "anthropic/claude-3.5-sonnet"
|
mock_openrouter.validate_model_name.side_effect = lambda m: m == "anthropic/claude-sonnet-4"
|
||||||
mock_get_provider.side_effect = lambda ptype: mock_openrouter if ptype == ProviderType.OPENROUTER else None
|
mock_get_provider.side_effect = lambda ptype: mock_openrouter if ptype == ProviderType.OPENROUTER else None
|
||||||
|
|
||||||
model = ModelProviderRegistry._find_extended_thinking_model()
|
model = ModelProviderRegistry._find_extended_thinking_model()
|
||||||
assert model == "anthropic/claude-3.5-sonnet"
|
assert model == "anthropic/claude-sonnet-4"
|
||||||
|
|
||||||
def test_find_extended_thinking_model_none_found(self):
|
def test_find_extended_thinking_model_none_found(self):
|
||||||
"""Test when no thinking model is found."""
|
"""Test when no thinking model is found."""
|
||||||
|
|||||||
@@ -318,7 +318,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-3-opus,flash"
|
os.environ["OPENROUTER_ALLOWED_MODELS"] = "o3-mini,anthropic/claude-opus-4,flash"
|
||||||
|
|
||||||
# Register OpenRouter provider
|
# Register OpenRouter provider
|
||||||
from providers.openrouter import OpenRouterProvider
|
from providers.openrouter import OpenRouterProvider
|
||||||
@@ -330,7 +330,7 @@ class TestOpenRouterAliasRestrictions:
|
|||||||
|
|
||||||
expected_models = {
|
expected_models = {
|
||||||
"openai/o3-mini", # from alias
|
"openai/o3-mini", # from alias
|
||||||
"anthropic/claude-3-opus", # full name
|
"anthropic/claude-opus-4", # full name
|
||||||
"google/gemini-2.5-flash", # from alias
|
"google/gemini-2.5-flash", # from alias
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ class BaseTool(ABC):
|
|||||||
except Exception:
|
except Exception:
|
||||||
description += (
|
description += (
|
||||||
" OpenRouter: Any model available on openrouter.ai "
|
" OpenRouter: Any model available on openrouter.ai "
|
||||||
"(e.g., 'gpt-4', 'claude-3-opus', 'mistral-large')."
|
"(e.g., 'gpt-4', 'claude-4-opus', 'mistral-large')."
|
||||||
)
|
)
|
||||||
description += f" Defaults to '{DEFAULT_MODEL}' if not specified."
|
description += f" Defaults to '{DEFAULT_MODEL}' if not specified."
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user