Skip to main content
Glama
test_multimodal.py16.1 kB
import pytest import base64 import io import json from unittest.mock import Mock, patch, MagicMock from PIL import Image # Test fixtures @pytest.fixture def sample_image_base64(): """Create a sample base64 encoded image for testing.""" # Create a simple 100x100 red image image = Image.new('RGB', (100, 100), color='red') buffer = io.BytesIO() image.save(buffer, format='JPEG') image_bytes = buffer.getvalue() return base64.b64encode(image_bytes).decode('utf-8') @pytest.fixture def sample_large_image_base64(): """Create a large base64 encoded image for testing resize functionality.""" # Create a 3000x3000 image to test resizing image = Image.new('RGB', (3000, 3000), color='blue') buffer = io.BytesIO() image.save(buffer, format='JPEG') image_bytes = buffer.getvalue() return base64.b64encode(image_bytes).decode('utf-8') @pytest.fixture def sample_image_url(): """Sample image URL for testing.""" return "https://example.com/image.jpg" @pytest.fixture def sample_vision_models(): """Sample list of vision-capable models.""" return [ { "id": "openai/gpt-4o", "name": "GPT-4o", "architecture": { "input_modalities": ["text", "image"], "output_modalities": ["text"] } }, { "id": "anthropic/claude-3-sonnet", "name": "Claude 3 Sonnet", "architecture": { "input_modalities": ["text", "image"], "output_modalities": ["text"] } }, { "id": "openai/gpt-3.5-turbo", "name": "GPT-3.5 Turbo", "architecture": { "input_modalities": ["text"], "output_modalities": ["text"] } } ] class TestImageProcessing: """Test image processing utilities.""" def test_encode_image_to_base64_with_file_path(self, tmp_path): """Test encoding image file to base64.""" from src.openrouter_mcp.handlers.multimodal import encode_image_to_base64 # Create a test image file image = Image.new('RGB', (100, 100), color='green') image_path = tmp_path / "test_image.jpg" image.save(image_path) # Test encoding base64_string = encode_image_to_base64(str(image_path)) # Verify it's a valid base64 string assert isinstance(base64_string, str) assert len(base64_string) > 0 # Verify we can decode it back decoded_bytes = base64.b64decode(base64_string) assert len(decoded_bytes) > 0 def test_encode_image_to_base64_with_bytes(self): """Test encoding image bytes to base64.""" from src.openrouter_mcp.handlers.multimodal import encode_image_to_base64 # Create image bytes image = Image.new('RGB', (100, 100), color='green') buffer = io.BytesIO() image.save(buffer, format='JPEG') image_bytes = buffer.getvalue() # Test encoding base64_string = encode_image_to_base64(image_bytes) # Verify it's a valid base64 string assert isinstance(base64_string, str) assert len(base64_string) > 0 decoded_bytes = base64.b64decode(base64_string) assert decoded_bytes == image_bytes def test_validate_image_format_valid_formats(self): """Test image format validation for supported formats.""" from src.openrouter_mcp.handlers.multimodal import validate_image_format valid_formats = ['JPEG', 'PNG', 'WEBP', 'GIF'] for format_name in valid_formats: assert validate_image_format(format_name) == True def test_validate_image_format_invalid_formats(self): """Test image format validation for unsupported formats.""" from src.openrouter_mcp.handlers.multimodal import validate_image_format invalid_formats = ['BMP', 'TIFF', 'ICO', 'SVG'] for format_name in invalid_formats: assert validate_image_format(format_name) == False def test_process_image_no_resize_needed(self, sample_image_base64): """Test image processing when no resize is needed.""" from src.openrouter_mcp.handlers.multimodal import process_image # Process small image (should not be resized) processed_data, was_resized = process_image(sample_image_base64) assert isinstance(processed_data, str) assert was_resized == False # Should be the same as input for small images assert len(processed_data) == len(sample_image_base64) def test_process_image_resize_needed(self, sample_large_image_base64): """Test image processing when resize is needed.""" from src.openrouter_mcp.handlers.multimodal import process_image # Process large image with very small limit to force resize processed_data, was_resized = process_image(sample_large_image_base64, max_size_mb=0.1) assert isinstance(processed_data, str) assert was_resized == True # Processed image should be smaller than original assert len(processed_data) < len(sample_large_image_base64) def test_process_image_max_size_limit(self): """Test image processing respects max size limit.""" from src.openrouter_mcp.handlers.multimodal import process_image # Create a very large image that exceeds size limits image = Image.new('RGB', (5000, 5000), color='red') buffer = io.BytesIO() image.save(buffer, format='JPEG', quality=95) # High quality = large size large_image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') # Process should resize it processed_data, was_resized = process_image(large_image_base64) # Check that the processed image is under the size limit processed_bytes = base64.b64decode(processed_data) max_size_mb = 20 assert len(processed_bytes) <= max_size_mb * 1024 * 1024 def test_format_vision_message_with_base64_image(self, sample_image_base64): """Test formatting vision message with base64 image.""" from src.openrouter_mcp.handlers.multimodal import format_vision_message message = format_vision_message( text="What's in this image?", image_data=sample_image_base64, image_type="base64" ) expected_structure = { "role": "user", "content": [ {"type": "text", "text": "What's in this image?"}, { "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{sample_image_base64}"} } ] } assert message == expected_structure def test_format_vision_message_with_url_image(self, sample_image_url): """Test formatting vision message with image URL.""" from src.openrouter_mcp.handlers.multimodal import format_vision_message message = format_vision_message( text="Describe this image", image_data=sample_image_url, image_type="url" ) expected_structure = { "role": "user", "content": [ {"type": "text", "text": "Describe this image"}, { "type": "image_url", "image_url": {"url": sample_image_url} } ] } assert message == expected_structure def test_format_vision_message_multiple_images(self, sample_image_base64, sample_image_url): """Test formatting vision message with multiple images.""" from src.openrouter_mcp.handlers.multimodal import format_vision_message # Test with list of images images = [ {"data": sample_image_base64, "type": "base64"}, {"data": sample_image_url, "type": "url"} ] message = format_vision_message( text="Compare these images", images=images ) # Should have text + 2 image entries assert len(message["content"]) == 3 assert message["content"][0]["type"] == "text" assert message["content"][1]["type"] == "image_url" assert message["content"][2]["type"] == "image_url" class TestVisionModelSupport: """Test vision model detection and support.""" def test_is_vision_model_with_vision_capable(self, sample_vision_models): """Test vision model detection for models that support images.""" from src.openrouter_mcp.handlers.multimodal import is_vision_model vision_model = sample_vision_models[0] # GPT-4o assert is_vision_model(vision_model) == True def test_is_vision_model_with_text_only(self, sample_vision_models): """Test vision model detection for text-only models.""" from src.openrouter_mcp.handlers.multimodal import is_vision_model text_model = sample_vision_models[2] # GPT-3.5 Turbo assert is_vision_model(text_model) == False def test_filter_vision_models(self, sample_vision_models): """Test filtering models to get only vision-capable ones.""" from src.openrouter_mcp.handlers.multimodal import filter_vision_models vision_models = filter_vision_models(sample_vision_models) assert len(vision_models) == 2 assert vision_models[0]["id"] == "openai/gpt-4o" assert vision_models[1]["id"] == "anthropic/claude-3-sonnet" def test_get_vision_model_names(self, sample_vision_models): """Test getting names of vision-capable models.""" from src.openrouter_mcp.handlers.multimodal import get_vision_model_names model_names = get_vision_model_names(sample_vision_models) expected_names = ["GPT-4o", "Claude 3 Sonnet"] assert model_names == expected_names class TestMultimodalMCPHandlers: """Test MCP handlers for multimodal functionality.""" def test_chat_with_vision_base64_image(self, sample_image_base64): """Test chat with vision using base64 image - validates request structure.""" from src.openrouter_mcp.handlers.multimodal import VisionChatRequest, ImageInput # Test that we can create valid request objects request = VisionChatRequest( model="openai/gpt-4o", messages=[{"role": "user", "content": "What's in this image?"}], images=[ImageInput(data=sample_image_base64, type="base64")] ) assert request.model == "openai/gpt-4o" assert len(request.images) == 1 assert request.images[0].type == "base64" assert request.images[0].data == sample_image_base64 def test_chat_with_vision_url_image(self, sample_image_url): """Test chat with vision using image URL - validates request structure.""" from src.openrouter_mcp.handlers.multimodal import VisionChatRequest, ImageInput # Test that we can create valid request objects request = VisionChatRequest( model="anthropic/claude-3-sonnet", messages=[{"role": "user", "content": "Describe this image"}], images=[ImageInput(data=sample_image_url, type="url")] ) assert request.model == "anthropic/claude-3-sonnet" assert len(request.images) == 1 assert request.images[0].type == "url" assert request.images[0].data == sample_image_url def test_chat_with_vision_streaming(self, sample_image_base64): """Test streaming chat with vision - validates request structure.""" from src.openrouter_mcp.handlers.multimodal import VisionChatRequest, ImageInput # Test that we can create valid streaming request objects request = VisionChatRequest( model="openai/gpt-4o", messages=[{"role": "user", "content": "What's in this image?"}], images=[ImageInput(data=sample_image_base64, type="base64")], stream=True ) assert request.model == "openai/gpt-4o" assert request.stream == True assert len(request.images) == 1 def test_list_vision_models(self, sample_vision_models): """Test listing vision-capable models - validates filtering logic.""" from src.openrouter_mcp.handlers.multimodal import filter_vision_models, VisionModelRequest # Test the filtering function directly vision_models = filter_vision_models(sample_vision_models) # Should return only vision-capable models assert len(vision_models) == 2 assert vision_models[0]["id"] == "openai/gpt-4o" assert vision_models[1]["id"] == "anthropic/claude-3-sonnet" def test_list_vision_models_with_filter(self, sample_vision_models): """Test listing vision models with name filter - validates filter logic.""" from src.openrouter_mcp.handlers.multimodal import filter_vision_models, VisionModelRequest # Test the filtering function directly with name filter vision_models = filter_vision_models(sample_vision_models) gpt_models = [model for model in vision_models if "gpt" in model["id"].lower()] # Should return only GPT vision models assert len(gpt_models) == 1 assert gpt_models[0]["id"] == "openai/gpt-4o" def test_vision_chat_request_validation(self): """Test VisionChatRequest Pydantic model validation.""" from src.openrouter_mcp.handlers.multimodal import VisionChatRequest, ImageInput # Valid request valid_request = VisionChatRequest( model="openai/gpt-4o", messages=[{"role": "user", "content": "Test"}], images=[ImageInput(data="test_image.jpg", type="base64")] ) assert valid_request.model == "openai/gpt-4o" assert len(valid_request.messages) == 1 assert len(valid_request.images) == 1 assert valid_request.temperature == 0.7 # Default value def test_image_input_validation(self): """Test ImageInput Pydantic model validation.""" from src.openrouter_mcp.handlers.multimodal import ImageInput # Valid base64 input base64_input = ImageInput(data="base64_string_here", type="base64") assert base64_input.type == "base64" # Valid URL input url_input = ImageInput(data="https://example.com/image.jpg", type="url") assert url_input.type == "url" # Invalid type should raise validation error with pytest.raises(ValueError): ImageInput(data="test", type="invalid_type") class TestClientIntegration: """Test integration with OpenRouter client for vision functionality.""" @pytest.mark.asyncio async def test_openrouter_client_vision_method_exists(self): """Test that OpenRouter client has vision methods.""" from src.openrouter_mcp.client.openrouter import OpenRouterClient # Check if the methods exist (will be implemented in GREEN phase) client = OpenRouterClient(api_key="test") assert hasattr(client, 'chat_completion_with_vision') assert hasattr(client, 'stream_chat_completion_with_vision') @pytest.mark.asyncio async def test_client_vision_methods_callable(self): """Test that vision methods are callable.""" from src.openrouter_mcp.client.openrouter import OpenRouterClient client = OpenRouterClient(api_key="test") # Methods should be callable (even if not implemented yet) assert callable(getattr(client, 'chat_completion_with_vision', None)) assert callable(getattr(client, 'stream_chat_completion_with_vision', None))

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