import pytest import litellm @pytest.fixture(autouse=True) def add_mistral_api_key_to_env(monkeypatch): """Add Mistral API key to environment for testing.""" monkeypatch.setenv("MISTRAL_API_KEY", "fake-mistral-api-key-12345") @pytest.fixture def mistral_api_response(): """Mock response data for Mistral API calls.""" return { "id": "chatcmpl-mistral-123", "object": "chat.completion", "created": 1677652288, "model": "mistral-medium-latest", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Hello from Mistral! How can I help you today?", }, "finish_reason": "stop", } ], "usage": {"prompt_tokens": 10, "completion_tokens": 15, "total_tokens": 25}, } @pytest.fixture def mistral_api_response_with_empty_content(): """Mock response data for Mistral API calls with empty content that should be converted to None.""" return { "id": "chatcmpl-mistral-123", "object": "chat.completion", "created": 1677652288, "model": "mistral-medium-latest", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "", # Empty string that should be converted to None }, "finish_reason": "stop", } ], "usage": {"prompt_tokens": 10, "completion_tokens": 0, "total_tokens": 10}, } @pytest.mark.parametrize("sync_mode", [True, False]) @pytest.mark.asyncio async def test_mistral_basic_completion(sync_mode, respx_mock, mistral_api_response): """Test basic Mistral completion functionality.""" litellm.disable_aiohttp_transport = True model = "mistral/mistral-medium-latest" messages = [{"role": "user", "content": "Hello, how are you?"}] # Mock the Mistral API endpoint respx_mock.post("https://api.mistral.ai/v1/chat/completions").respond( json=mistral_api_response ) if sync_mode: response = litellm.completion(model=model, messages=messages) else: response = await litellm.acompletion(model=model, messages=messages) # Verify response assert ( response.choices[0].message.content == "Hello from Mistral! How can I help you today?" ) assert response.model == "mistral-medium-latest" assert response.usage.total_tokens == 25 @pytest.mark.parametrize("sync_mode", [True, False]) @pytest.mark.asyncio async def test_mistral_transform_response_empty_content_conversion( sync_mode, respx_mock, mistral_api_response_with_empty_content ): """ Test that Mistral's transform_response method is being called by verifying the specific behavior of converting empty string content to None. This test verifies that the _handle_empty_content_response method in MistralConfig.transform_response is being applied. """ litellm.disable_aiohttp_transport = True model = "mistral/mistral-medium-latest" messages = [{"role": "user", "content": "Generate an empty response"}] # Mock the Mistral API endpoint with empty content respx_mock.post("https://api.mistral.ai/v1/chat/completions").respond( json=mistral_api_response_with_empty_content ) if sync_mode: response = litellm.completion(model=model, messages=messages) else: response = await litellm.acompletion(model=model, messages=messages) # Verify that the transform_response method was called by checking that # empty string content was converted to None (Mistral-specific behavior) assert response.choices[0].message.content is None assert response.model == "mistral-medium-latest" assert response.usage.total_tokens == 10 @pytest.mark.parametrize("sync_mode", [True, False]) @pytest.mark.asyncio async def test_mistral_transform_request_name_field_removal( sync_mode, respx_mock, mistral_api_response ): """ Test that Mistral's transform_request method is being called by verifying the specific behavior of removing the 'name' field from non-tool messages. This test verifies that the _handle_name_in_message method in MistralConfig._transform_messages is being applied. """ litellm.disable_aiohttp_transport = True model = "mistral/mistral-medium-latest" # Include a message with 'name' field that should be removed for non-tool messages messages = [ {"role": "user", "content": "Hello", "name": "should_be_removed"}, {"role": "assistant", "content": "Hi there!"}, {"role": "user", "content": "How are you?"}, ] # Mock the Mistral API endpoint respx_mock.post("https://api.mistral.ai/v1/chat/completions").respond( json=mistral_api_response ) if sync_mode: response = litellm.completion(model=model, messages=messages) else: response = await litellm.acompletion(model=model, messages=messages) # Verify the response works (if transform_request wasn't called, the API would reject the request) assert ( response.choices[0].message.content == "Hello from Mistral! How can I help you today?" ) assert response.model == "mistral-medium-latest" # Verify that the request was made (if transform_request failed, this would fail) assert len(respx_mock.calls) == 1 # Get the actual request that was made request = respx_mock.calls[0].request import json request_data = json.loads(request.content.decode("utf-8")) # Verify that the 'name' field was removed from the user message # (Mistral API only supports 'name' in tool messages) user_message = request_data["messages"][0] assert user_message["role"] == "user" assert user_message["content"] == "Hello" assert "name" not in user_message # The 'name' field should have been removed