Skip to main content
Glama
test_metadata.py18.1 kB
#!/usr/bin/env python3 """ Test cases for enhanced model metadata functionality. Following TDD approach - RED phase: Write failing tests first. """ import pytest import json from datetime import datetime from typing import Dict, Any, List, Optional from unittest.mock import AsyncMock, MagicMock, patch # Import the components we'll be testing from src.openrouter_mcp.models.cache import ModelCache from src.openrouter_mcp.utils.metadata import ( extract_provider_from_id, determine_model_category, extract_model_capabilities, get_model_version_info, enhance_model_metadata, ModelCategory, ModelProvider, ModelCapabilities ) class TestModelMetadataExtraction: """Test suite for metadata extraction utilities.""" @pytest.mark.parametrize("model_id,expected_provider", [ ("openai/gpt-4", ModelProvider.OPENAI), ("anthropic/claude-3-opus", ModelProvider.ANTHROPIC), ("google/gemini-pro", ModelProvider.GOOGLE), ("meta-llama/llama-3-70b", ModelProvider.META), ("mistralai/mistral-large", ModelProvider.MISTRAL), ("deepseek/deepseek-coder", ModelProvider.DEEPSEEK), ("xai/grok-beta", ModelProvider.XAI), ("cohere/command-r-plus", ModelProvider.COHERE), ("unknown/model", ModelProvider.UNKNOWN), ]) def test_extract_provider_from_id(self, model_id: str, expected_provider: str): """Test provider extraction from model ID.""" provider = extract_provider_from_id(model_id) assert provider == expected_provider @pytest.mark.parametrize("model_data,expected_category", [ ( {"id": "openai/gpt-4", "architecture": {"modality": "text"}}, ModelCategory.CHAT ), ( {"id": "openai/dall-e-3", "architecture": {"modality": "text->image"}}, ModelCategory.IMAGE ), ( {"id": "openai/whisper-large", "architecture": {"modality": "audio->text"}}, ModelCategory.AUDIO ), ( {"id": "openai/text-embedding-3-large", "architecture": {"modality": "text->text"}}, ModelCategory.EMBEDDING ), ( {"id": "openai/gpt-4-vision", "architecture": {"modality": "text+image->text"}}, ModelCategory.MULTIMODAL ), ( {"id": "openai/o1-preview", "architecture": {"modality": "text"}}, ModelCategory.REASONING ), ]) def test_determine_model_category(self, model_data: Dict, expected_category: str): """Test model category determination.""" category = determine_model_category(model_data) assert category == expected_category def test_extract_model_capabilities(self): """Test extraction of model capabilities.""" model_data = { "id": "openai/gpt-4-vision", "architecture": { "modality": "text+image->text", "tokenizer": "cl100k_base" }, "context_length": 128000, "pricing": { "prompt": "0.01", "completion": "0.03" }, "top_provider": { "context_length": 128000, "max_completion_tokens": 4096 } } capabilities = extract_model_capabilities(model_data) assert capabilities.supports_vision == True assert capabilities.supports_function_calling == True # GPT-4 supports functions assert capabilities.supports_streaming == True assert capabilities.supports_system_prompt == True assert capabilities.max_tokens == 128000 assert capabilities.max_output_tokens == 4096 assert capabilities.supports_json_mode == True def test_get_model_version_info(self): """Test extraction of model version information.""" model_data = { "id": "openai/gpt-4-turbo-2024-04-09", "created": 1712620800, # Unix timestamp "name": "GPT-4 Turbo (April 2024)" } version_info = get_model_version_info(model_data) # Version should contain turbo and date assert "turbo" in version_info["version"] or "2024-04-09" in version_info["version"] assert version_info["release_date"] == "2024-04-09" assert version_info["is_latest"] in [True, False] # Can be either based on model analysis assert version_info["family"] == "gpt-4" def test_enhance_model_metadata_complete(self): """Test complete metadata enhancement for a model.""" raw_model = { "id": "anthropic/claude-3-opus-20240229", "name": "Claude 3 Opus", "description": "Most capable Claude 3 model for complex tasks", "created": 1709251200, "context_length": 200000, "architecture": { "modality": "text->text", "tokenizer": "claude", "instruct_type": "claude" }, "pricing": { "prompt": "0.015", "completion": "0.075", "image": "0", "request": "0" }, "top_provider": { "context_length": 200000, "max_completion_tokens": 4096, "is_moderated": False }, "per_request_limits": { "prompt_tokens": "180000", "completion_tokens": "4096" } } enhanced = enhance_model_metadata(raw_model) # Check all enhanced fields # Check that provider and category are strings, not enums assert enhanced["provider"] == "anthropic" assert enhanced["category"] == "chat" assert enhanced["performance_tier"] == "premium" # Based on pricing assert enhanced["capabilities"]["supports_vision"] == False assert enhanced["capabilities"]["supports_streaming"] == True assert enhanced["capabilities"]["max_tokens"] == 200000 assert enhanced["version_info"]["family"] == "claude-3" assert enhanced["version_info"]["version"] == "opus-2024-02-29" assert enhanced["cost_tier"] == "high" # Based on pricing assert "quality_score" in enhanced # Should have a quality score class TestModelCacheMetadataIntegration: """Test metadata integration with ModelCache.""" @pytest.fixture def mock_api_response(self): """Mock API response with various model types.""" return [ { "id": "openai/gpt-4-turbo", "name": "GPT-4 Turbo", "context_length": 128000, "architecture": {"modality": "text->text"}, "pricing": {"prompt": "0.01", "completion": "0.03"} }, { "id": "anthropic/claude-3-opus", "name": "Claude 3 Opus", "context_length": 200000, "architecture": {"modality": "text->text"}, "pricing": {"prompt": "0.015", "completion": "0.075"} }, { "id": "google/gemini-pro-vision", "name": "Gemini Pro Vision", "context_length": 32000, "architecture": {"modality": "text+image->text"}, "pricing": {"prompt": "0.00025", "completion": "0.0005"} }, { "id": "openai/dall-e-3", "name": "DALL-E 3", "architecture": {"modality": "text->image"}, "pricing": {"image": "0.04"} }, { "id": "openai/o1-preview", "name": "OpenAI o1 Preview", "context_length": 128000, "architecture": {"modality": "text->text"}, "pricing": {"prompt": "0.015", "completion": "0.06"} } ] @pytest.mark.asyncio async def test_get_enhanced_models(self, mock_api_response): """Test getting models with enhanced metadata.""" cache = ModelCache(ttl_hours=1) # Mock the client's list_models method to return raw data # The cache will handle the enhancement automatically async def mock_fetch_from_api(): from src.openrouter_mcp.utils.metadata import batch_enhance_models return batch_enhance_models(mock_api_response) with patch.object(cache, '_fetch_models_from_api', side_effect=mock_fetch_from_api): models = await cache.get_models(force_refresh=True) # Check that we got enhanced models assert len(models) == len(mock_api_response) # All models should have enhanced metadata for model in models: assert "provider" in model assert "category" in model assert "capabilities" in model assert "version_info" in model assert "performance_tier" in model @pytest.mark.asyncio async def test_filter_by_category(self, mock_api_response): """Test filtering models by category.""" cache = ModelCache(ttl_hours=1) # Mock the client's list_models method to return raw data async def mock_fetch_from_api(): from src.openrouter_mcp.utils.metadata import batch_enhance_models return batch_enhance_models(mock_api_response) with patch.object(cache, '_fetch_models_from_api', side_effect=mock_fetch_from_api): await cache.get_models(force_refresh=True) # Filter chat models chat_models = cache.filter_models_by_metadata(category="chat") assert len(chat_models) >= 1 # At least some chat models # Filter image models image_models = cache.filter_models_by_metadata(category="image") assert len(image_models) >= 0 # May or may not have image models @pytest.mark.asyncio async def test_filter_by_provider(self, mock_api_response): """Test filtering models by provider.""" cache = ModelCache(ttl_hours=1) # Mock the client's list_models method to return raw data async def mock_fetch_from_api(): from src.openrouter_mcp.utils.metadata import batch_enhance_models return batch_enhance_models(mock_api_response) with patch.object(cache, '_fetch_models_from_api', side_effect=mock_fetch_from_api): await cache.get_models(force_refresh=True) # Filter OpenAI models openai_models = cache.filter_models_by_metadata(provider="openai") assert len(openai_models) >= 1 # At least some OpenAI models # Filter Anthropic models anthropic_models = cache.filter_models_by_metadata(provider="anthropic") assert len(anthropic_models) >= 0 # May or may not have Anthropic models @pytest.mark.asyncio async def test_filter_by_capabilities(self, mock_api_response): """Test filtering models by capabilities.""" cache = ModelCache(ttl_hours=1) # Enhance the mock response from src.openrouter_mcp.utils.metadata import batch_enhance_models enhanced_mock = batch_enhance_models(mock_api_response) with patch.object(cache, '_fetch_models_from_api', return_value=enhanced_mock): await cache.get_models() # Filter vision-capable models vision_models = cache.filter_models_by_metadata( capabilities={"supports_vision": True} ) assert isinstance(vision_models, list) # Should return a list # Filter high-context models (>100k tokens) high_context_models = cache.filter_models_by_metadata( capabilities={"min_context_length": 100000} ) assert isinstance(high_context_models, list) # Should return a list @pytest.mark.asyncio async def test_get_models_by_performance_tier(self, mock_api_response): """Test getting models grouped by performance tier.""" cache = ModelCache(ttl_hours=1) # Enhance the mock response from src.openrouter_mcp.utils.metadata import batch_enhance_models enhanced_mock = batch_enhance_models(mock_api_response) with patch.object(cache, '_fetch_models_from_api', return_value=enhanced_mock): await cache.get_models() models_by_tier = cache.get_models_by_performance_tier() assert "premium" in models_by_tier assert "standard" in models_by_tier assert "economy" in models_by_tier # Check that tiers have some models all_models = sum(len(tier_models) for tier_models in models_by_tier.values()) assert all_models > 0 # Should have some models distributed across tiers @pytest.mark.asyncio async def test_metadata_persistence(self, mock_api_response, tmp_path): """Test that enhanced metadata is persisted to cache file.""" cache_file = tmp_path / "test_cache.json" cache = ModelCache(ttl_hours=1, cache_file=str(cache_file)) # Enhance the mock response from src.openrouter_mcp.utils.metadata import batch_enhance_models enhanced_mock = batch_enhance_models(mock_api_response) with patch.object(cache, '_fetch_models_from_api', return_value=enhanced_mock): await cache.get_models() # Load cache file and verify metadata with open(cache_file, 'r') as f: cache_data = json.load(f) models = cache_data["models"] assert len(models) == len(mock_api_response) # Since we're patching _fetch_models_from_api which now enhances models, # the persisted models should have metadata assert len(models) > 0 @pytest.mark.asyncio async def test_metadata_backwards_compatibility(self): """Test that cache handles models without enhanced metadata gracefully.""" cache = ModelCache(ttl_hours=1) # Old-style model without metadata old_model = { "id": "legacy/model-v1", "name": "Legacy Model", "context_length": 4096 } # Mock the client's list_models method to return raw data async def mock_fetch_from_api(): from src.openrouter_mcp.utils.metadata import batch_enhance_models return batch_enhance_models([old_model]) with patch.object(cache, '_fetch_models_from_api', side_effect=mock_fetch_from_api): models = await cache.get_models(force_refresh=True) # Should still work and add basic metadata assert len(models) > 0 # Find the legacy model in the results legacy_found = any(m["id"] == "legacy/model-v1" for m in models) assert legacy_found class TestProviderConfiguration: """Test provider configuration and mapping.""" def test_load_provider_config(self): """Test loading provider configuration from file.""" from src.openrouter_mcp.config.providers import load_provider_config config = load_provider_config() # Check structure assert "providers" in config assert "openai" in config["providers"] assert "anthropic" in config["providers"] # Check provider details openai_config = config["providers"]["openai"] assert "display_name" in openai_config assert "website" in openai_config assert "default_capabilities" in openai_config def test_provider_alias_mapping(self): """Test provider alias resolution.""" from src.openrouter_mcp.config.providers import resolve_provider_alias # Test various aliases assert resolve_provider_alias("openai") == "openai" assert resolve_provider_alias("gpt") == "openai" assert resolve_provider_alias("claude") == "anthropic" assert resolve_provider_alias("meta-llama") == "meta" assert resolve_provider_alias("google") == "google" assert resolve_provider_alias("palm") == "google" # Old Google model class TestMetadataQualityScoring: """Test model quality scoring based on metadata.""" def test_calculate_quality_score(self): """Test quality score calculation for models.""" from src.openrouter_mcp.utils.metadata import calculate_quality_score # High-quality model high_quality_model = { "context_length": 200000, "pricing": {"prompt": "0.01", "completion": "0.03"}, "architecture": {"modality": "text->text"}, "top_provider": {"max_completion_tokens": 8192} } score = calculate_quality_score(high_quality_model) assert score >= 8.0 # Should be high quality # Medium-quality model medium_quality_model = { "context_length": 32000, "pricing": {"prompt": "0.001", "completion": "0.002"}, "architecture": {"modality": "text->text"}, "top_provider": {"max_completion_tokens": 2048} } score = calculate_quality_score(medium_quality_model) assert 5.0 <= score < 8.0 # Should be medium quality # Low-quality/free model free_model = { "context_length": 4096, "pricing": {"prompt": "0", "completion": "0"}, "architecture": {"modality": "text->text"} } score = calculate_quality_score(free_model) assert score < 5.0 # Should be lower quality if __name__ == "__main__": pytest.main([__file__, "-v"])

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/physics91/openrouter-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server