From 44b9614814c08f102101dabd6e862c6206308fa3 Mon Sep 17 00:00:00 2001 From: octopus Date: Thu, 26 Mar 2026 12:00:00 +0000 Subject: [PATCH] feat: add MiniMax as first-class LLM provider Add MiniMax AI (https://www.minimax.io/) as a directly supported LLM provider alongside OpenAI, Cerebras, and Azure OpenAI. MiniMax's API is OpenAI-compatible, so this uses the OpenAI SDK with MiniMax's base URL for seamless integration. Changes: - Add MINIMAX_API_KEY detection in get_config() with auto base URL - Add temperature clamping for MiniMax: values are clamped to (0, 1] - Update README provider table with MiniMax documentation - Add 17 unit tests covering provider detection, priority, and temp clamping - Add 3 integration tests for live API verification --- README.md | 1 + optillm/server.py | 16 ++ tests/test_minimax_integration.py | 87 +++++++++ tests/test_minimax_provider.py | 288 ++++++++++++++++++++++++++++++ 4 files changed, 392 insertions(+) create mode 100644 tests/test_minimax_integration.py create mode 100644 tests/test_minimax_provider.py diff --git a/README.md b/README.md index b3eedf5..ea30c21 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,7 @@ We support all major LLM providers and models for inference. You need to set the |----------|-------------------------------|------------------| | OptiLLM | `OPTILLM_API_KEY` | Uses the inbuilt local server for inference, supports logprobs and decoding techniques like `cot_decoding` & `entropy_decoding` | | OpenAI | `OPENAI_API_KEY` | You can use this with any OpenAI compatible endpoint (e.g. OpenRouter) by setting the `base_url` | +| MiniMax | `MINIMAX_API_KEY` | Uses the [MiniMax API](https://www.minimax.io/) (OpenAI-compatible). Supports MiniMax-M2.7 and other models. Temperature is auto-clamped to (0, 1] | | Cerebras | `CEREBRAS_API_KEY` | You can use this for fast inference with supported models, see [docs for details](https://inference-docs.cerebras.ai/introduction) | | Azure OpenAI | `AZURE_OPENAI_API_KEY`
`AZURE_API_VERSION`
`AZURE_API_BASE` | - | | Azure OpenAI (Managed Identity) | `AZURE_API_VERSION`
`AZURE_API_BASE` | Login required using `az login`, see [docs for details](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/managed-identity) | diff --git a/optillm/server.py b/optillm/server.py index bc2c88f..05cc592 100644 --- a/optillm/server.py +++ b/optillm/server.py @@ -91,6 +91,13 @@ def get_config(): default_client = Cerebras(api_key=API_KEY, base_url=base_url, http_client=http_client) else: default_client = Cerebras(api_key=API_KEY, http_client=http_client) + elif os.environ.get("MINIMAX_API_KEY"): + API_KEY = os.environ.get("MINIMAX_API_KEY") + base_url = server_config['base_url'] + if base_url == "": + base_url = "https://api.minimax.io/v1" + default_client = OpenAI(api_key=API_KEY, base_url=base_url, http_client=http_client) + logger.info(f"Created MiniMax client with base_url: {base_url}") elif os.environ.get("OPENAI_API_KEY"): API_KEY = os.environ.get("OPENAI_API_KEY") base_url = server_config['base_url'] @@ -759,6 +766,15 @@ def proxy(): base_url = server_config['base_url'] default_client, api_key = get_config() + # Clamp temperature for MiniMax provider: must be in (0.0, 1.0] + if os.environ.get("MINIMAX_API_KEY") and 'temperature' in request_config: + temp = request_config['temperature'] + if temp is not None: + if temp <= 0: + request_config['temperature'] = 0.01 + elif temp > 1.0: + request_config['temperature'] = 1.0 + operation, approaches, model = parse_combined_approach(model, known_approaches, plugin_approaches) # Start conversation logging if enabled diff --git a/tests/test_minimax_integration.py b/tests/test_minimax_integration.py new file mode 100644 index 0000000..071549d --- /dev/null +++ b/tests/test_minimax_integration.py @@ -0,0 +1,87 @@ +""" +Integration tests for MiniMax provider support in optillm. + +These tests verify that the MiniMax provider works end-to-end with the actual +MiniMax API. They require a valid MINIMAX_API_KEY environment variable. + +Run with: MINIMAX_API_KEY=your-key pytest tests/test_minimax_integration.py -v +""" + +import unittest +import os +import sys + +# Add parent directory to path to import optillm modules +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# Skip all tests if MINIMAX_API_KEY is not set +MINIMAX_API_KEY = os.environ.get('MINIMAX_API_KEY', '') +SKIP_REASON = "MINIMAX_API_KEY environment variable not set" + + +@unittest.skipUnless(MINIMAX_API_KEY, SKIP_REASON) +class TestMiniMaxIntegration(unittest.TestCase): + """Integration tests that call the actual MiniMax API.""" + + def setUp(self): + """Set up OpenAI client pointing to MiniMax API.""" + from openai import OpenAI + self.client = OpenAI( + api_key=MINIMAX_API_KEY, + base_url="https://api.minimax.io/v1" + ) + + def test_basic_completion(self): + """Test basic chat completion with MiniMax API.""" + response = self.client.chat.completions.create( + model="MiniMax-M2.7", + messages=[ + {"role": "user", "content": "Say hello in one word."} + ], + max_tokens=10, + temperature=0.7 + ) + + assert hasattr(response, 'choices') + assert len(response.choices) > 0 + assert response.choices[0].message.content is not None + assert len(response.choices[0].message.content) > 0 + + def test_temperature_boundary(self): + """Test that MiniMax API accepts temperature at boundary value 0.01.""" + response = self.client.chat.completions.create( + model="MiniMax-M2.7", + messages=[ + {"role": "user", "content": "What is 2+2?"} + ], + max_tokens=10, + temperature=0.01 + ) + + assert hasattr(response, 'choices') + assert len(response.choices) > 0 + + def test_streaming_completion(self): + """Test streaming chat completion with MiniMax API.""" + stream = self.client.chat.completions.create( + model="MiniMax-M2.7", + messages=[ + {"role": "user", "content": "Count from 1 to 3."} + ], + max_tokens=30, + temperature=0.5, + stream=True + ) + + chunks = list(stream) + assert len(chunks) > 0 + content_chunks = [ + chunk.choices[0].delta.content + for chunk in chunks + if chunk.choices[0].delta.content + ] + assert len(content_chunks) > 0 + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_minimax_provider.py b/tests/test_minimax_provider.py new file mode 100644 index 0000000..017d91b --- /dev/null +++ b/tests/test_minimax_provider.py @@ -0,0 +1,288 @@ +""" +Unit tests for MiniMax provider support in optillm. + +Tests verify that the MiniMax provider: +- Is correctly detected via MINIMAX_API_KEY environment variable +- Creates an OpenAI client with the MiniMax base URL +- Respects custom base_url when set +- Takes priority over OpenAI when both keys are set +- Does not interfere when MINIMAX_API_KEY is not set +- Properly clamps temperature to MiniMax's valid range (0, 1] +""" + +import unittest +from unittest.mock import patch, MagicMock +import sys +import os +import json + +# Add parent directory to path to import optillm modules +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# Mock heavy dependencies that may not be installed in test environments +for mod_name in ['z3', 'torch', 'torch.nn', 'torch.nn.functional', + 'transformers', 'adaptive_classifier', + 'peft', 'bitsandbytes', 'outlines', 'spacy', + 'presidio_analyzer', 'presidio_anonymizer']: + if mod_name not in sys.modules: + sys.modules[mod_name] = MagicMock() + +from optillm import server_config + + +class TestMiniMaxProviderDetection(unittest.TestCase): + """Test MiniMax provider detection via MINIMAX_API_KEY.""" + + def setUp(self): + """Reset server_config before each test.""" + self.original_config = server_config.copy() + # Clear provider-related environment variables + for key in ['MINIMAX_API_KEY', 'OPENAI_API_KEY', 'CEREBRAS_API_KEY', + 'AZURE_OPENAI_API_KEY', 'OPTILLM_API_KEY']: + if key in os.environ: + del os.environ[key] + + def tearDown(self): + """Restore original server_config after each test.""" + server_config.clear() + server_config.update(self.original_config) + + @patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-minimax-key'}) + def test_minimax_provider_detected(self): + """Test that MINIMAX_API_KEY triggers MiniMax provider.""" + from optillm.server import get_config + + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = '' + server_config['base_url'] = '' + + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + client, api_key = get_config() + + # Should use the MiniMax API key + assert api_key == 'test-minimax-key' + + # Should create OpenAI client with MiniMax base URL + mock_openai.assert_called_once() + call_kwargs = mock_openai.call_args[1] + assert call_kwargs['api_key'] == 'test-minimax-key' + assert call_kwargs['base_url'] == 'https://api.minimax.io/v1' + + @patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-minimax-key'}) + def test_minimax_default_base_url(self): + """Test MiniMax provider uses default base URL https://api.minimax.io/v1.""" + from optillm.server import get_config + + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = '' + server_config['base_url'] = '' + + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + get_config() + call_kwargs = mock_openai.call_args[1] + assert call_kwargs['base_url'] == 'https://api.minimax.io/v1' + + @patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-minimax-key'}) + def test_minimax_with_custom_base_url(self): + """Test MiniMax provider with custom base_url.""" + from optillm.server import get_config + + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = '' + server_config['base_url'] = 'https://custom-proxy.example.com/v1' + + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + client, api_key = get_config() + + call_kwargs = mock_openai.call_args[1] + assert call_kwargs['base_url'] == 'https://custom-proxy.example.com/v1' + + @patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-minimax-key'}) + def test_minimax_client_receives_http_client(self): + """Test that MiniMax client receives the configured httpx client.""" + from optillm.server import get_config + + server_config['ssl_verify'] = False + server_config['ssl_cert_path'] = '' + server_config['base_url'] = '' + + mock_http_client_instance = MagicMock() + + with patch('httpx.Client', return_value=mock_http_client_instance) as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + get_config() + + call_kwargs = mock_openai.call_args[1] + assert 'http_client' in call_kwargs + assert call_kwargs['http_client'] == mock_http_client_instance + + @patch.dict(os.environ, {'MINIMAX_API_KEY': 'minimax-key', 'OPENAI_API_KEY': 'openai-key'}) + def test_minimax_takes_priority_over_openai(self): + """Test that MINIMAX_API_KEY takes priority over OPENAI_API_KEY.""" + from optillm.server import get_config + + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = '' + server_config['base_url'] = '' + + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + client, api_key = get_config() + + assert api_key == 'minimax-key' + call_kwargs = mock_openai.call_args[1] + assert call_kwargs['base_url'] == 'https://api.minimax.io/v1' + + @patch.dict(os.environ, {'OPENAI_API_KEY': 'openai-key'}) + def test_openai_still_works_without_minimax(self): + """Test that OpenAI provider works when MINIMAX_API_KEY is not set.""" + from optillm.server import get_config + + # Ensure MINIMAX_API_KEY is not set + if 'MINIMAX_API_KEY' in os.environ: + del os.environ['MINIMAX_API_KEY'] + + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = '' + server_config['base_url'] = '' + + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + client, api_key = get_config() + + assert api_key == 'openai-key' + call_kwargs = mock_openai.call_args[1] + # Should NOT have MiniMax base URL + assert 'base_url' not in call_kwargs + + @patch.dict(os.environ, {'CEREBRAS_API_KEY': 'cerebras-key', 'MINIMAX_API_KEY': 'minimax-key'}) + def test_cerebras_takes_priority_over_minimax(self): + """Test that CEREBRAS_API_KEY takes priority over MINIMAX_API_KEY.""" + from optillm.server import get_config + + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = '' + server_config['base_url'] = '' + + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.Cerebras') as mock_cerebras: + client, api_key = get_config() + + assert api_key == 'cerebras-key' + mock_cerebras.assert_called_once() + + +class TestMiniMaxTemperatureClamping(unittest.TestCase): + """Test temperature clamping for MiniMax provider. + + The clamping logic in server.py's proxy() checks MINIMAX_API_KEY env var + and adjusts temperature in request_config to MiniMax's valid range (0, 1]. + These tests verify the clamping logic directly. + """ + + def _clamp_temperature(self, temp, is_minimax=True): + """Simulate the temperature clamping logic from server.py proxy().""" + request_config = {'temperature': temp} + if is_minimax and 'temperature' in request_config: + t = request_config['temperature'] + if t is not None: + if t <= 0: + request_config['temperature'] = 0.01 + elif t > 1.0: + request_config['temperature'] = 1.0 + return request_config['temperature'] + + def test_temperature_zero_clamped(self): + """Test that temperature=0 is clamped to 0.01 for MiniMax.""" + assert self._clamp_temperature(0) == 0.01 + + def test_temperature_negative_clamped(self): + """Test that negative temperature is clamped to 0.01 for MiniMax.""" + assert self._clamp_temperature(-0.5) == 0.01 + + def test_temperature_above_one_clamped(self): + """Test that temperature > 1.0 is clamped to 1.0 for MiniMax.""" + assert self._clamp_temperature(1.5) == 1.0 + + def test_temperature_exactly_two_clamped(self): + """Test that temperature=2.0 is clamped to 1.0 for MiniMax.""" + assert self._clamp_temperature(2.0) == 1.0 + + def test_valid_temperature_point_seven_unchanged(self): + """Test that valid temperature 0.7 is not modified.""" + assert self._clamp_temperature(0.7) == 0.7 + + def test_valid_temperature_one_unchanged(self): + """Test that temperature=1.0 (boundary) is not modified.""" + assert self._clamp_temperature(1.0) == 1.0 + + def test_valid_temperature_point_one_unchanged(self): + """Test that temperature=0.1 is not modified.""" + assert self._clamp_temperature(0.1) == 0.1 + + def test_no_clamping_for_non_minimax(self): + """Test that temperature is NOT clamped when not using MiniMax.""" + assert self._clamp_temperature(0, is_minimax=False) == 0 + assert self._clamp_temperature(1.5, is_minimax=False) == 1.5 + assert self._clamp_temperature(-1, is_minimax=False) == -1 + + +class TestMiniMaxProviderPriority(unittest.TestCase): + """Test provider priority ordering with MiniMax.""" + + def setUp(self): + """Set up test environment.""" + self.original_config = server_config.copy() + for key in ['MINIMAX_API_KEY', 'OPENAI_API_KEY', 'CEREBRAS_API_KEY', + 'AZURE_OPENAI_API_KEY', 'OPTILLM_API_KEY']: + if key in os.environ: + del os.environ[key] + + def tearDown(self): + """Restore original server_config.""" + server_config.clear() + server_config.update(self.original_config) + + @patch.dict(os.environ, {'OPTILLM_API_KEY': 'optillm-key', 'MINIMAX_API_KEY': 'minimax-key'}) + def test_optillm_takes_priority_over_minimax(self): + """Test that OPTILLM_API_KEY takes priority over MINIMAX_API_KEY.""" + from optillm.server import get_config + + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = '' + server_config['base_url'] = '' + + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + # Mock the local inference path + with patch.dict(sys.modules, {'optillm.inference': MagicMock()}): + # Reimport to pick up the mock + import importlib + from optillm import server + importlib.reload(server) + client, api_key = server.get_config() + assert api_key == 'optillm-key' + + @patch.dict(os.environ, {'MINIMAX_API_KEY': 'minimax-key', 'AZURE_OPENAI_API_KEY': 'azure-key', + 'AZURE_API_VERSION': '2024-02-15', 'AZURE_API_BASE': 'https://test.openai.azure.com'}) + def test_minimax_takes_priority_over_azure(self): + """Test that MINIMAX_API_KEY takes priority over AZURE_OPENAI_API_KEY.""" + from optillm.server import get_config + + server_config['ssl_verify'] = True + server_config['ssl_cert_path'] = '' + server_config['base_url'] = '' + + with patch('httpx.Client') as mock_httpx_client, \ + patch('optillm.server.OpenAI') as mock_openai: + client, api_key = get_config() + assert api_key == 'minimax-key' + call_kwargs = mock_openai.call_args[1] + assert call_kwargs['base_url'] == 'https://api.minimax.io/v1' + + +if __name__ == '__main__': + unittest.main()