"""Unit tests for MiniMax provider support in llmcore.py."""
import json
import re
import sys
import os
import unittest
from unittest.mock import patch, MagicMock
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
class TestMiniMaxTemperatureClamping(unittest.TestCase):
"""Test MiniMax temperature clamping in _openai_stream."""
def _make_stream_call(self, model, temperature):
"""Capture the payload sent by _openai_stream."""
from llmcore import _openai_stream
captured = {}
def fake_post(url, headers=None, json=None, stream=None, timeout=None, proxies=None):
captured['payload'] = json
captured['url'] = url
resp = MagicMock()
resp.status_code = 200
resp.iter_lines.return_value = iter([b'data: [DONE]'])
resp.__enter__ = lambda s: s
resp.__exit__ = MagicMock(return_value=False)
return resp
with patch('llmcore.requests.post', side_effect=fake_post):
gen = _openai_stream(
'https://api.minimax.io/v1', 'test-key', [{"role": "user", "content": "hi"}],
model, temperature=temperature
)
# Drain the generator
for _ in gen:
pass
return captured.get('payload', {})
def test_minimax_temp_zero_clamped(self):
"""MiniMax rejects temperature=0, should be clamped to 0.01."""
payload = self._make_stream_call('MiniMax-M2.7', 0.0)
self.assertAlmostEqual(payload['temperature'], 0.01)
def test_minimax_temp_negative_clamped(self):
"""Negative temperature should be clamped to 0.01."""
payload = self._make_stream_call('MiniMax-M2.5', -0.5)
self.assertAlmostEqual(payload['temperature'], 0.01)
def test_minimax_temp_normal_preserved(self):
"""Normal temperature (0 < t <= 1) should be preserved."""
payload = self._make_stream_call('MiniMax-M2.7', 0.5)
self.assertAlmostEqual(payload['temperature'], 0.5)
def test_minimax_temp_one_preserved(self):
"""Temperature=1.0 should be preserved."""
payload = self._make_stream_call('MiniMax-M2.7-highspeed', 1.0)
self.assertAlmostEqual(payload['temperature'], 1.0)
def test_minimax_temp_above_one_clamped(self):
"""Temperature > 1.0 should be clamped to 1.0."""
payload = self._make_stream_call('MiniMax-M2.7', 1.5)
self.assertAlmostEqual(payload['temperature'], 1.0)
def test_minimax_case_insensitive(self):
"""Model name matching should be case-insensitive."""
payload = self._make_stream_call('minimax-m2.7', 0.0)
self.assertAlmostEqual(payload['temperature'], 0.01)
def test_non_minimax_temp_zero_unchanged(self):
"""Non-MiniMax models should not have temperature clamped."""
payload = self._make_stream_call('gpt-4o', 0.0)
self.assertAlmostEqual(payload['temperature'], 0.0)
def test_kimi_temp_still_forced(self):
"""Kimi/Moonshot temp override should still work."""
payload = self._make_stream_call('kimi-2.0', 0.5)
self.assertAlmostEqual(payload['temperature'], 1.0)
class TestMiniMaxThinkTagHandling(unittest.TestCase):
"""Test ... tag stripping for MiniMax M2.7 responses."""
def test_think_tag_stripped_from_response(self):
""" tags (used by MiniMax M2.7) should be stripped from content."""
from llmcore import ToolClient, LLMSession
mock_cfg = {
'apikey': 'test', 'apibase': 'https://api.minimax.io/v1',
'model': 'MiniMax-M2.7',
}
with patch('llmcore._load_mykeys', return_value={}):
session = LLMSession(mock_cfg)
client = ToolClient(session)
text = 'Let me reason about this task.\n\nHere is the answer.'
result = client._parse_mixed_response(text)
self.assertEqual(result.thinking, 'Let me reason about this task.')
self.assertEqual(result.content, 'Here is the answer.')
def test_thinking_tag_still_works(self):
""" tags (used by Claude) should still work."""
from llmcore import ToolClient, LLMSession
mock_cfg = {
'apikey': 'test', 'apibase': 'https://api.anthropic.com',
'model': 'claude-sonnet-4-20250514',
}
with patch('llmcore._load_mykeys', return_value={}):
session = LLMSession(mock_cfg)
client = ToolClient(session)
text = 'Let me analyze this.\n\nThe result is 42.'
result = client._parse_mixed_response(text)
self.assertEqual(result.thinking, 'Let me analyze this.')
self.assertEqual(result.content, 'The result is 42.')
def test_think_tag_with_tool_use(self):
""" tags should be separated from tool_use blocks."""
from llmcore import ToolClient, LLMSession
mock_cfg = {
'apikey': 'test', 'apibase': 'https://api.minimax.io/v1',
'model': 'MiniMax-M2.7',
}
with patch('llmcore._load_mykeys', return_value={}):
session = LLMSession(mock_cfg)
client = ToolClient(session)
text = 'I need to read the file first.\n\nReading config\n\n\n{"name": "file_read", "arguments": {"path": "/tmp/test.txt"}}\n'
result = client._parse_mixed_response(text)
self.assertEqual(result.thinking, 'I need to read the file first.')
self.assertTrue(len(result.tool_calls) > 0)
self.assertEqual(result.tool_calls[0].function.name, 'file_read')
class TestMiniMaxCompressHistoryTags(unittest.TestCase):
"""Test that tags are compressed in history like tags."""
def test_think_tag_compressed_in_old_messages(self):
""" tags in old messages should be truncated."""
from llmcore import compress_history_tags
long_think = "A" * 2000
messages = [
{"role": "assistant", "prompt": f"{long_think}\nShort answer."},
{"role": "user", "prompt": "Follow up"},
] + [{"role": "user", "prompt": f"msg{i}"} for i in range(12)]
# Force compression (counter divisible by 5)
compress_history_tags._cd = 4
result = compress_history_tags(messages, keep_recent=10, max_len=800)
# The first message's content should be truncated
first_content = result[0]["prompt"]
self.assertIn("", first_content)
self.assertIn("...", first_content)
self.assertLess(len(first_content), len(f"{long_think}\nShort answer."))
class TestMiniMaxAutoMakeUrl(unittest.TestCase):
"""Test URL construction for MiniMax API base."""
def test_minimax_base_url(self):
from llmcore import auto_make_url
url = auto_make_url('https://api.minimax.io/v1', 'chat/completions')
self.assertEqual(url, 'https://api.minimax.io/v1/chat/completions')
def test_minimax_base_url_no_version(self):
from llmcore import auto_make_url
url = auto_make_url('https://api.minimax.io', 'chat/completions')
self.assertEqual(url, 'https://api.minimax.io/v1/chat/completions')
def test_minimax_full_url_preserved(self):
from llmcore import auto_make_url
url = auto_make_url('https://api.minimax.io/v1/chat/completions$', 'chat/completions')
self.assertEqual(url, 'https://api.minimax.io/v1/chat/completions')
class TestMiniMaxNativeOAISessionThinkTag(unittest.TestCase):
"""Test tag handling in NativeOAISession."""
def test_think_tag_extracted_in_native_oai(self):
"""NativeOAISession.ask should extract tags from MiniMax M2.7 responses."""
from llmcore import NativeOAISession
cfg = {
'apikey': 'test-key',
'apibase': 'https://api.minimax.io/v1',
'model': 'MiniMax-M2.7',
}
session = NativeOAISession(cfg)
# Mock the raw_ask to return content with tag via generator
def mock_raw_ask(messages, tools=None, system=None, model=None, temperature=0.5, max_tokens=6144, **kw):
content_text = "Planning the approach.\n\nHere is the result."
yield content_text
return [{"type": "text", "text": content_text}]
session.raw_ask = mock_raw_ask
msg = {"role": "user", "content": [{"type": "text", "text": "test"}]}
gen = session.ask(msg)
# Drain generator
try:
while True:
next(gen)
except StopIteration as e:
resp = e.value
self.assertEqual(resp.thinking, 'Planning the approach.')
self.assertNotIn('', resp.content)
self.assertIn('Here is the result.', resp.content)
class TestMiniMaxLLMSessionConfig(unittest.TestCase):
"""Test LLMSession configuration with MiniMax settings."""
def test_llm_session_init_with_minimax(self):
"""LLMSession should initialize correctly with MiniMax config."""
from llmcore import LLMSession
cfg = {
'apikey': 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9',
'apibase': 'https://api.minimax.io/v1',
'model': 'MiniMax-M2.7',
'context_win': 50000,
'max_retries': 2,
'connect_timeout': 10,
'read_timeout': 120,
}
session = LLMSession(cfg)
self.assertEqual(session.default_model, 'MiniMax-M2.7')
self.assertEqual(session.api_base, 'https://api.minimax.io/v1')
self.assertEqual(session.context_win, 50000)
self.assertEqual(session.max_retries, 2)
def test_llm_session_minimax_highspeed(self):
"""LLMSession should work with MiniMax-M2.7-highspeed model."""
from llmcore import LLMSession
cfg = {
'apikey': 'test-key',
'apibase': 'https://api.minimax.io/v1',
'model': 'MiniMax-M2.7-highspeed',
}
session = LLMSession(cfg)
self.assertEqual(session.default_model, 'MiniMax-M2.7-highspeed')
class TestMiniMaxNativeToolClientThinkTag(unittest.TestCase):
"""Test tag handling in NativeToolClient.chat."""
def test_native_tool_client_think_tag(self):
"""NativeToolClient should extract tags from MiniMax responses."""
from llmcore import NativeToolClient, NativeOAISession, MockResponse
cfg = {
'apikey': 'test-key',
'apibase': 'https://api.minimax.io/v1',
'model': 'MiniMax-M2.7',
}
session = NativeOAISession(cfg)
client = NativeToolClient(session)
# Mock the backend.ask to yield chunks and return a MockResponse with think tags
def mock_ask(msg, tools=None, model=None):
text = "Analyzing the request.\n\nResult: success"
yield text
return MockResponse('', text, [], text)
session.ask = mock_ask
messages = [{"role": "user", "content": "test query"}]
gen = client.chat(messages)
resp = None
try:
while True:
next(gen)
except StopIteration as e:
resp = e.value
self.assertIsNotNone(resp)
self.assertEqual(resp.thinking, 'Analyzing the request.')
self.assertEqual(resp.content, 'Result: success')
if __name__ == '__main__':
unittest.main()