+
+
+
@@ -1495,7 +1507,8 @@
const providerNames = {
'openai': 'OpenAI',
'anthropic': 'Anthropic',
- 'qwen': 'Qwen'
+ 'qwen': 'Qwen',
+ 'minimax': 'MiniMax'
};
const providerName = providerNames[provider] || provider;
@@ -1782,12 +1795,14 @@
const openaiGroup = document.getElementById('openaiApiKeyGroup');
const anthropicGroup = document.getElementById('anthropicApiKeyGroup');
const qwenGroup = document.getElementById('qwenApiKeyGroup');
-
+ const minimaxGroup = document.getElementById('minimaxApiKeyGroup');
+
// Hide all groups first
openaiGroup.style.display = 'none';
anthropicGroup.style.display = 'none';
qwenGroup.style.display = 'none';
-
+ minimaxGroup.style.display = 'none';
+
// Show the selected provider's group
if (provider === 'openai') {
openaiGroup.style.display = 'block';
@@ -1795,6 +1810,8 @@
anthropicGroup.style.display = 'block';
} else if (provider === 'qwen') {
qwenGroup.style.display = 'block';
+ } else if (provider === 'minimax') {
+ minimaxGroup.style.display = 'block';
}
// Update provider on backend
@@ -1839,6 +1856,8 @@
apiKey = document.getElementById('anthropicApiKeyInput').value.trim();
} else if (actualProvider === 'qwen') {
apiKey = document.getElementById('qwenApiKeyInput').value.trim();
+ } else if (actualProvider === 'minimax') {
+ apiKey = document.getElementById('minimaxApiKeyInput').value.trim();
}
if (!apiKey) {
@@ -1872,7 +1891,8 @@
const providerNames = {
'openai': 'OpenAI',
'anthropic': 'Anthropic',
- 'qwen': 'Qwen'
+ 'qwen': 'Qwen',
+ 'minimax': 'MiniMax'
};
showApiKeySuccess(`${providerNames[actualProvider] || actualProvider} API key updated successfully!`);
if (actualProvider === 'openai') {
@@ -1881,6 +1901,8 @@
document.getElementById('anthropicApiKeyInput').value = '';
} else if (actualProvider === 'qwen') {
document.getElementById('qwenApiKeyInput').value = '';
+ } else if (actualProvider === 'minimax') {
+ document.getElementById('minimaxApiKeyInput').value = '';
}
checkApiKeyStatus();
} else {
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_minimax_integration.py b/tests/test_minimax_integration.py
new file mode 100644
index 0000000..5b9f1ca
--- /dev/null
+++ b/tests/test_minimax_integration.py
@@ -0,0 +1,89 @@
+"""Integration tests for MiniMax provider in QuantAgent.
+
+These tests verify end-to-end behavior with the actual MiniMax API.
+They require the MINIMAX_API_KEY environment variable to be set.
+Skip with: pytest -k "not integration"
+"""
+
+import os
+import sys
+import unittest
+from unittest.mock import MagicMock
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
+
+# Mock heavy native dependencies
+for mod_name in ["talib", "langchain_qwq"]:
+ if mod_name not in sys.modules:
+ sys.modules[mod_name] = MagicMock()
+
+MINIMAX_API_KEY = os.environ.get("MINIMAX_API_KEY", "")
+SKIP_REASON = "MINIMAX_API_KEY not set"
+
+
+@unittest.skipUnless(MINIMAX_API_KEY, SKIP_REASON)
+class TestMiniMaxIntegration(unittest.TestCase):
+ """Integration tests that hit the real MiniMax API."""
+
+ def test_create_llm_minimax_m27(self):
+ """Should create a working MiniMax M2.7 LLM via ChatOpenAI."""
+ from trading_graph import TradingGraph
+ from default_config import DEFAULT_CONFIG
+
+ config = DEFAULT_CONFIG.copy()
+ config["agent_llm_provider"] = "minimax"
+ config["graph_llm_provider"] = "minimax"
+ config["agent_llm_model"] = "MiniMax-M2.7"
+ config["graph_llm_model"] = "MiniMax-M2.7"
+ config["minimax_api_key"] = MINIMAX_API_KEY
+
+ tg = TradingGraph(config=config)
+
+ # The agent_llm should be a ChatOpenAI instance
+ from langchain_openai import ChatOpenAI
+ self.assertIsInstance(tg.agent_llm, ChatOpenAI)
+
+ def test_minimax_simple_invoke(self):
+ """Should successfully invoke MiniMax M2.7-highspeed for a simple query."""
+ from trading_graph import TradingGraph
+ from default_config import DEFAULT_CONFIG
+
+ config = DEFAULT_CONFIG.copy()
+ config["agent_llm_provider"] = "minimax"
+ config["graph_llm_provider"] = "minimax"
+ config["agent_llm_model"] = "MiniMax-M2.7-highspeed"
+ config["graph_llm_model"] = "MiniMax-M2.7-highspeed"
+ config["minimax_api_key"] = MINIMAX_API_KEY
+
+ tg = TradingGraph(config=config)
+
+ # Simple invoke test
+ response = tg.agent_llm.invoke("Say 'hello' and nothing else.")
+ self.assertIsNotNone(response)
+ self.assertTrue(len(response.content) > 0)
+
+ def test_minimax_provider_full_lifecycle(self):
+ """Test full lifecycle: create -> update key -> refresh."""
+ from trading_graph import TradingGraph
+ from default_config import DEFAULT_CONFIG
+
+ config = DEFAULT_CONFIG.copy()
+ config["agent_llm_provider"] = "minimax"
+ config["graph_llm_provider"] = "minimax"
+ config["agent_llm_model"] = "MiniMax-M2.7-highspeed"
+ config["graph_llm_model"] = "MiniMax-M2.7-highspeed"
+ config["minimax_api_key"] = MINIMAX_API_KEY
+
+ tg = TradingGraph(config=config)
+
+ # Update API key (same key, just testing the mechanism)
+ tg.update_api_key(MINIMAX_API_KEY, provider="minimax")
+
+ # Verify the LLM still works after refresh
+ response = tg.agent_llm.invoke("Reply with just the word 'ok'.")
+ self.assertIsNotNone(response)
+ self.assertTrue(len(response.content) > 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..4395542
--- /dev/null
+++ b/tests/test_minimax_provider.py
@@ -0,0 +1,360 @@
+"""Unit tests for MiniMax provider integration in QuantAgent."""
+
+import os
+import sys
+import unittest
+from unittest.mock import MagicMock, patch, PropertyMock
+
+# Add project root to path
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
+
+# Mock heavy native/incompatible dependencies before importing project modules
+# TA-Lib requires a C library; langchain has pydantic v1/v2 conflicts
+for mod_name in [
+ "talib",
+ "langchain_qwq",
+]:
+ if mod_name not in sys.modules:
+ sys.modules[mod_name] = MagicMock()
+
+from default_config import DEFAULT_CONFIG
+
+
+class TestDefaultConfig(unittest.TestCase):
+ """Tests for MiniMax fields in DEFAULT_CONFIG."""
+
+ def test_minimax_api_key_field_exists(self):
+ """DEFAULT_CONFIG should contain a minimax_api_key field."""
+ self.assertIn("minimax_api_key", DEFAULT_CONFIG)
+
+ def test_provider_comment_mentions_minimax(self):
+ """Provider fields should accept 'minimax' as a valid value."""
+ config = DEFAULT_CONFIG.copy()
+ config["agent_llm_provider"] = "minimax"
+ config["graph_llm_provider"] = "minimax"
+ self.assertEqual(config["agent_llm_provider"], "minimax")
+ self.assertEqual(config["graph_llm_provider"], "minimax")
+
+
+class TestTradingGraphGetApiKey(unittest.TestCase):
+ """Tests for TradingGraph._get_api_key() with minimax provider."""
+
+ def _make_graph(self, config):
+ """Create a TradingGraph with mocked LLM creation."""
+ from trading_graph import TradingGraph
+ orig_create = TradingGraph._create_llm
+ TradingGraph._create_llm = MagicMock(return_value=MagicMock())
+ tg = TradingGraph(config=config)
+ TradingGraph._create_llm = orig_create
+ return tg
+
+ def test_get_api_key_from_config(self):
+ """Should return minimax_api_key from config."""
+ config = DEFAULT_CONFIG.copy()
+ config["minimax_api_key"] = "test-minimax-key-123"
+ tg = self._make_graph(config)
+ key = tg._get_api_key("minimax")
+ self.assertEqual(key, "test-minimax-key-123")
+
+ def test_get_api_key_from_env(self):
+ """Should fall back to MINIMAX_API_KEY env var."""
+ config = DEFAULT_CONFIG.copy()
+ config["minimax_api_key"] = ""
+ tg = self._make_graph(config)
+ with patch.dict(os.environ, {"MINIMAX_API_KEY": "env-minimax-key"}):
+ key = tg._get_api_key("minimax")
+ self.assertEqual(key, "env-minimax-key")
+
+ def test_get_api_key_missing_raises(self):
+ """Should raise ValueError if no MiniMax API key is available."""
+ config = DEFAULT_CONFIG.copy()
+ config["minimax_api_key"] = ""
+ tg = self._make_graph(config)
+ with patch.dict(os.environ, {}, clear=True):
+ os.environ.pop("MINIMAX_API_KEY", None)
+ with self.assertRaises(ValueError) as ctx:
+ tg._get_api_key("minimax")
+ self.assertIn("MiniMax", str(ctx.exception))
+
+ def test_unsupported_provider_raises(self):
+ """Should raise ValueError for unsupported provider."""
+ config = DEFAULT_CONFIG.copy()
+ tg = self._make_graph(config)
+ with self.assertRaises(ValueError) as ctx:
+ tg._get_api_key("unsupported_provider")
+ self.assertIn("Unsupported provider", str(ctx.exception))
+ self.assertIn("minimax", str(ctx.exception))
+
+
+class TestTradingGraphCreateLlm(unittest.TestCase):
+ """Tests for TradingGraph._create_llm() with minimax provider."""
+
+ def _make_graph(self, config):
+ """Create a TradingGraph with mocked LLM creation."""
+ from trading_graph import TradingGraph
+ orig_create = TradingGraph._create_llm
+ TradingGraph._create_llm = MagicMock(return_value=MagicMock())
+ tg = TradingGraph(config=config)
+ TradingGraph._create_llm = orig_create
+ return tg
+
+ @patch("trading_graph.ChatOpenAI")
+ def test_create_llm_minimax_uses_chatopenai(self, mock_openai):
+ """MiniMax provider should create ChatOpenAI with custom base URL."""
+ config = DEFAULT_CONFIG.copy()
+ config["minimax_api_key"] = "test-key"
+ tg = self._make_graph(config)
+ tg.config = config
+
+ mock_openai.return_value = MagicMock()
+ result = tg._create_llm("minimax", "MiniMax-M2.7", 0.1)
+
+ mock_openai.assert_called_once_with(
+ model="MiniMax-M2.7",
+ temperature=0.1,
+ api_key="test-key",
+ openai_api_base="https://api.minimax.io/v1",
+ )
+
+ @patch("trading_graph.ChatOpenAI")
+ def test_create_llm_minimax_clamps_temperature(self, mock_openai):
+ """MiniMax temperature should be clamped to (0.0, 1.0]."""
+ config = DEFAULT_CONFIG.copy()
+ config["minimax_api_key"] = "test-key"
+ tg = self._make_graph(config)
+ tg.config = config
+
+ mock_openai.return_value = MagicMock()
+ tg._create_llm("minimax", "MiniMax-M2.7", 0.0)
+ call_args = mock_openai.call_args
+ self.assertAlmostEqual(call_args.kwargs["temperature"], 0.01)
+
+ @patch("trading_graph.ChatOpenAI")
+ def test_create_llm_minimax_clamps_high_temperature(self, mock_openai):
+ """MiniMax temperature > 1.0 should be clamped to 1.0."""
+ config = DEFAULT_CONFIG.copy()
+ config["minimax_api_key"] = "test-key"
+ tg = self._make_graph(config)
+ tg.config = config
+
+ mock_openai.return_value = MagicMock()
+ tg._create_llm("minimax", "MiniMax-M2.7", 1.5)
+ call_args = mock_openai.call_args
+ self.assertAlmostEqual(call_args.kwargs["temperature"], 1.0)
+
+ @patch("trading_graph.ChatOpenAI")
+ def test_create_llm_minimax_normal_temperature(self, mock_openai):
+ """Normal temperature within range should be passed through."""
+ config = DEFAULT_CONFIG.copy()
+ config["minimax_api_key"] = "test-key"
+ tg = self._make_graph(config)
+ tg.config = config
+
+ mock_openai.return_value = MagicMock()
+ tg._create_llm("minimax", "MiniMax-M2.7", 0.5)
+ call_args = mock_openai.call_args
+ self.assertAlmostEqual(call_args.kwargs["temperature"], 0.5)
+
+
+class TestTradingGraphUpdateApiKey(unittest.TestCase):
+ """Tests for TradingGraph.update_api_key() with minimax provider."""
+
+ def _make_graph(self, config):
+ from trading_graph import TradingGraph
+ orig_create = TradingGraph._create_llm
+ TradingGraph._create_llm = MagicMock(return_value=MagicMock())
+ tg = TradingGraph(config=config)
+ TradingGraph._create_llm = orig_create
+ return tg
+
+ def test_update_api_key_minimax(self):
+ """update_api_key('minimax') should update config and env var."""
+ config = DEFAULT_CONFIG.copy()
+ config["minimax_api_key"] = ""
+ config["agent_llm_provider"] = "minimax"
+ config["graph_llm_provider"] = "minimax"
+ config["agent_llm_model"] = "MiniMax-M2.7"
+ config["graph_llm_model"] = "MiniMax-M2.7"
+ tg = self._make_graph(config)
+
+ with patch.object(tg, "refresh_llms"):
+ tg.update_api_key("new-minimax-key", provider="minimax")
+
+ self.assertEqual(tg.config["minimax_api_key"], "new-minimax-key")
+ self.assertEqual(os.environ.get("MINIMAX_API_KEY"), "new-minimax-key")
+
+ def test_update_api_key_unsupported_raises(self):
+ """update_api_key() with unsupported provider should raise ValueError."""
+ config = DEFAULT_CONFIG.copy()
+ tg = self._make_graph(config)
+ with self.assertRaises(ValueError) as ctx:
+ tg.update_api_key("key", provider="unsupported")
+ self.assertIn("minimax", str(ctx.exception))
+
+
+class TestTradingGraphRefreshLlms(unittest.TestCase):
+ """Tests for TradingGraph.refresh_llms() with minimax provider."""
+
+ @patch("trading_graph.ChatOpenAI")
+ @patch("trading_graph.ChatAnthropic")
+ @patch("trading_graph.ChatQwen")
+ def test_refresh_llms_minimax(self, mock_qwen, mock_anthropic, mock_openai):
+ """refresh_llms() should recreate LLMs when provider is minimax."""
+ from trading_graph import TradingGraph
+
+ config = DEFAULT_CONFIG.copy()
+ config["agent_llm_provider"] = "minimax"
+ config["graph_llm_provider"] = "minimax"
+ config["agent_llm_model"] = "MiniMax-M2.7"
+ config["graph_llm_model"] = "MiniMax-M2.7"
+ config["minimax_api_key"] = "test-key"
+
+ mock_openai.return_value = MagicMock()
+ tg = TradingGraph(config=config)
+
+ mock_openai.reset_mock()
+ tg.refresh_llms()
+
+ # ChatOpenAI should be called twice (agent_llm + graph_llm)
+ self.assertEqual(mock_openai.call_count, 2)
+ for call in mock_openai.call_args_list:
+ self.assertEqual(call.kwargs["openai_api_base"], "https://api.minimax.io/v1")
+
+
+class TestWebInterfaceProviderUpdate(unittest.TestCase):
+ """Tests for web interface provider update with MiniMax."""
+
+ @patch("web_interface.TradingGraph")
+ def test_update_provider_minimax(self, mock_tg_class):
+ """POST /api/update-provider with minimax should succeed."""
+ mock_tg = MagicMock()
+ mock_tg.config = DEFAULT_CONFIG.copy()
+ mock_tg_class.return_value = mock_tg
+
+ from web_interface import app, analyzer
+ analyzer.config = DEFAULT_CONFIG.copy()
+ analyzer.trading_graph = mock_tg
+
+ client = app.test_client()
+ resp = client.post(
+ "/api/update-provider",
+ json={"provider": "minimax"},
+ content_type="application/json",
+ )
+ data = resp.get_json()
+ self.assertTrue(data.get("success"))
+ self.assertEqual(analyzer.config["agent_llm_model"], "MiniMax-M2.7")
+ self.assertEqual(analyzer.config["graph_llm_model"], "MiniMax-M2.7")
+
+ @patch("web_interface.TradingGraph")
+ def test_update_provider_invalid(self, mock_tg_class):
+ """POST /api/update-provider with invalid provider should fail."""
+ mock_tg = MagicMock()
+ mock_tg.config = DEFAULT_CONFIG.copy()
+ mock_tg_class.return_value = mock_tg
+
+ from web_interface import app, analyzer
+ analyzer.config = DEFAULT_CONFIG.copy()
+ analyzer.trading_graph = mock_tg
+
+ client = app.test_client()
+ resp = client.post(
+ "/api/update-provider",
+ json={"provider": "invalid"},
+ content_type="application/json",
+ )
+ data = resp.get_json()
+ self.assertIn("error", data)
+
+ @patch("web_interface.TradingGraph")
+ def test_update_api_key_minimax(self, mock_tg_class):
+ """POST /api/update-api-key with minimax should set env var."""
+ mock_tg = MagicMock()
+ mock_tg.config = DEFAULT_CONFIG.copy()
+ mock_tg_class.return_value = mock_tg
+
+ from web_interface import app, analyzer
+ analyzer.config = DEFAULT_CONFIG.copy()
+ analyzer.trading_graph = mock_tg
+
+ client = app.test_client()
+ resp = client.post(
+ "/api/update-api-key",
+ json={"api_key": "test-mm-key", "provider": "minimax"},
+ content_type="application/json",
+ )
+ data = resp.get_json()
+ self.assertTrue(data.get("success"))
+ self.assertEqual(os.environ.get("MINIMAX_API_KEY"), "test-mm-key")
+
+ @patch("web_interface.TradingGraph")
+ def test_get_api_key_status_minimax(self, mock_tg_class):
+ """GET /api/get-api-key-status?provider=minimax should work."""
+ mock_tg = MagicMock()
+ mock_tg.config = DEFAULT_CONFIG.copy()
+ mock_tg_class.return_value = mock_tg
+
+ from web_interface import app, analyzer
+ config = DEFAULT_CONFIG.copy()
+ config["minimax_api_key"] = "test-minimax-key-12345"
+ analyzer.config = config
+ analyzer.trading_graph = mock_tg
+
+ client = app.test_client()
+ resp = client.get("/api/get-api-key-status?provider=minimax")
+ data = resp.get_json()
+ self.assertTrue(data.get("has_key"))
+ self.assertIn("masked_key", data)
+
+ @patch("web_interface.TradingGraph")
+ def test_get_api_key_status_minimax_missing(self, mock_tg_class):
+ """GET /api/get-api-key-status?provider=minimax with no key."""
+ mock_tg = MagicMock()
+ mock_tg.config = DEFAULT_CONFIG.copy()
+ mock_tg_class.return_value = mock_tg
+
+ from web_interface import app, analyzer
+ config = DEFAULT_CONFIG.copy()
+ config["minimax_api_key"] = ""
+ analyzer.config = config
+ analyzer.trading_graph = mock_tg
+
+ os.environ.pop("MINIMAX_API_KEY", None)
+
+ client = app.test_client()
+ resp = client.get("/api/get-api-key-status?provider=minimax")
+ data = resp.get_json()
+ self.assertFalse(data.get("has_key"))
+
+
+class TestProviderSwitchBackToOpenAI(unittest.TestCase):
+ """Test that switching from MiniMax back to OpenAI resets model names."""
+
+ @patch("web_interface.TradingGraph")
+ def test_switch_minimax_to_openai(self, mock_tg_class):
+ """Switching from minimax to openai should reset model names."""
+ mock_tg = MagicMock()
+ mock_tg.config = DEFAULT_CONFIG.copy()
+ mock_tg_class.return_value = mock_tg
+
+ from web_interface import app, analyzer
+ config = DEFAULT_CONFIG.copy()
+ config["agent_llm_model"] = "MiniMax-M2.7"
+ config["graph_llm_model"] = "MiniMax-M2.7"
+ analyzer.config = config
+ analyzer.trading_graph = mock_tg
+
+ client = app.test_client()
+ resp = client.post(
+ "/api/update-provider",
+ json={"provider": "openai"},
+ content_type="application/json",
+ )
+ data = resp.get_json()
+ self.assertTrue(data.get("success"))
+ self.assertEqual(analyzer.config["agent_llm_model"], "gpt-4o-mini")
+ self.assertEqual(analyzer.config["graph_llm_model"], "gpt-4o")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/trading_graph.py b/trading_graph.py
index 2057d02..df1834f 100644
--- a/trading_graph.py
+++ b/trading_graph.py
@@ -113,11 +113,11 @@ def _get_api_key(self, provider: str = "openai") -> str:
elif provider == "qwen":
# First check if API key is provided in config
api_key = self.config.get("qwen_api_key")
-
+
# If not in config, check environment variable
if not api_key:
api_key = os.environ.get("DASHSCOPE_API_KEY")
-
+
# Validate the API key
if not api_key:
raise ValueError(
@@ -125,14 +125,36 @@ def _get_api_key(self, provider: str = "openai") -> str:
"1. Set environment variable: export DASHSCOPE_API_KEY='your-key-here'\n"
"2. Update the config with: config['qwen_api_key'] = 'your-key-here'\n"
)
-
+
if api_key == "":
raise ValueError(
"Please provide your actual Qwen API key. "
"You can get one from: https://dashscope.console.aliyun.com/"
)
+ elif provider == "minimax":
+ # First check if API key is provided in config
+ api_key = self.config.get("minimax_api_key")
+
+ # If not in config, check environment variable
+ if not api_key:
+ api_key = os.environ.get("MINIMAX_API_KEY")
+
+ # Validate the API key
+ if not api_key:
+ raise ValueError(
+ "MiniMax API key not found. Please set it using one of these methods:\n"
+ "1. Set environment variable: export MINIMAX_API_KEY='your-key-here'\n"
+ "2. Update the config with: config['minimax_api_key'] = 'your-key-here'\n"
+ "3. Use the web interface to update the API key"
+ )
+
+ if api_key == "":
+ raise ValueError(
+ "Please provide your actual MiniMax API key. "
+ "You can get one from: https://platform.minimaxi.com/"
+ )
else:
- raise ValueError(f"Unsupported provider: {provider}. Must be 'openai', 'anthropic', or 'qwen'")
+ raise ValueError(f"Unsupported provider: {provider}. Must be 'openai', 'anthropic', 'qwen', or 'minimax'")
return api_key
@@ -141,17 +163,17 @@ def _create_llm(
) -> BaseChatModel:
"""
Create an LLM instance based on the provider.
-
+
Args:
- provider: The provider name ("openai", "anthropic", or "qwen")
- model: The model name (e.g., "gpt-4o", "claude-3-5-sonnet-20241022", "qwen-vl-max-latest")
+ provider: The provider name ("openai", "anthropic", "qwen", or "minimax")
+ model: The model name (e.g., "gpt-4o", "claude-3-5-sonnet-20241022", "qwen-vl-max-latest", "MiniMax-M2.7")
temperature: The temperature setting for the model
-
+
Returns:
BaseChatModel: An instance of the appropriate LLM class
"""
api_key = self._get_api_key(provider)
-
+
if provider == "openai":
return ChatOpenAI(
model=model,
@@ -174,8 +196,18 @@ def _create_llm(
api_key=api_key,
max_retries=4,
)
+ elif provider == "minimax":
+ # MiniMax uses an OpenAI-compatible API at https://api.minimax.io/v1
+ # Temperature must be in (0.0, 1.0] for MiniMax
+ clamped_temp = max(0.01, min(temperature, 1.0))
+ return ChatOpenAI(
+ model=model,
+ temperature=clamped_temp,
+ api_key=api_key,
+ openai_api_base="https://api.minimax.io/v1",
+ )
else:
- raise ValueError(f"Unsupported provider: {provider}. Must be 'openai', 'anthropic', or 'qwen'")
+ raise ValueError(f"Unsupported provider: {provider}. Must be 'openai', 'anthropic', 'qwen', or 'minimax'")
# def _set_tool_nodes(self) -> Dict[str, ToolNode]:
# """
@@ -251,11 +283,17 @@ def update_api_key(self, api_key: str, provider: str = "openai"):
elif provider == "qwen":
# Update the config with the new API key
self.config["qwen_api_key"] = api_key
-
+
# Also update the environment variable for consistency
os.environ["DASHSCOPE_API_KEY"] = api_key
+ elif provider == "minimax":
+ # Update the config with the new API key
+ self.config["minimax_api_key"] = api_key
+
+ # Also update the environment variable for consistency
+ os.environ["MINIMAX_API_KEY"] = api_key
else:
- raise ValueError(f"Unsupported provider: {provider}. Must be 'openai', 'anthropic', or 'qwen'")
+ raise ValueError(f"Unsupported provider: {provider}. Must be 'openai', 'anthropic', 'qwen', or 'minimax'")
# Refresh the LLMs with the new API key
self.refresh_llms()
diff --git a/web_interface.py b/web_interface.py
index e4b3ad4..8002b15 100644
--- a/web_interface.py
+++ b/web_interface.py
@@ -327,6 +327,8 @@ def run_analysis(
provider_name = "OpenAI"
elif provider == "anthropic":
provider_name = "Anthropic"
+ elif provider == "minimax":
+ provider_name = "MiniMax"
else:
provider_name = "Qwen"
@@ -526,7 +528,7 @@ def validate_api_key(self, provider: str = None) -> Dict[str, Any]:
)
provider_name = "Anthropic"
- else: # qwen
+ elif provider == "qwen":
from langchain_qwq import ChatQwen
api_key = os.environ.get("DASHSCOPE_API_KEY") or self.config.get("qwen_api_key", "")
if not api_key:
@@ -534,12 +536,29 @@ def validate_api_key(self, provider: str = None) -> Dict[str, Any]:
"valid": False,
"error": "❌ Invalid API Key: The Qwen API key is not set. Please update it in the Settings section.",
}
-
+
# Make a simple test call using LangChain
llm = ChatQwen(model="qwen-flash", api_key=api_key)
_ = llm.invoke([("user", "Hello")])
-
+
provider_name = "Qwen"
+ else: # minimax
+ from openai import OpenAI as _OpenAI
+ api_key = os.environ.get("MINIMAX_API_KEY") or self.config.get("minimax_api_key", "")
+ if not api_key:
+ return {
+ "valid": False,
+ "error": "❌ Invalid API Key: The MiniMax API key is not set. Please update it in the Settings section.",
+ }
+
+ client = _OpenAI(api_key=api_key, base_url="https://api.minimax.io/v1")
+ _ = client.chat.completions.create(
+ model="MiniMax-M2.7-highspeed",
+ messages=[{"role": "user", "content": "Hello"}],
+ max_tokens=5,
+ )
+
+ provider_name = "MiniMax"
return {"valid": True, "message": f"{provider_name} API key is valid"}
except Exception as e:
@@ -552,6 +571,8 @@ def validate_api_key(self, provider: str = None) -> Dict[str, Any]:
provider_name = "OpenAI"
elif provider == "anthropic":
provider_name = "Anthropic"
+ elif provider == "minimax":
+ provider_name = "MiniMax"
else:
provider_name = "Qwen"
@@ -870,8 +891,8 @@ def update_provider():
data = request.get_json()
provider = data.get("provider", "openai")
- if provider not in ["openai", "anthropic", "qwen"]:
- return jsonify({"error": "Provider must be 'openai', 'anthropic', or 'qwen'"})
+ if provider not in ["openai", "anthropic", "qwen", "minimax"]:
+ return jsonify({"error": "Provider must be 'openai', 'anthropic', 'qwen', or 'minimax'"})
print(f"Updating provider to: {provider}")
@@ -894,12 +915,18 @@ def update_provider():
analyzer.config["agent_llm_model"] = "qwen3-max"
if not analyzer.config["graph_llm_model"].startswith("qwen"):
analyzer.config["graph_llm_model"] = "qwen3-vl-plus"
-
+ elif provider == "minimax":
+ # Set default MiniMax models if not already set to MiniMax models
+ if not analyzer.config["agent_llm_model"].startswith("MiniMax"):
+ analyzer.config["agent_llm_model"] = "MiniMax-M2.7"
+ if not analyzer.config["graph_llm_model"].startswith("MiniMax"):
+ analyzer.config["graph_llm_model"] = "MiniMax-M2.7"
+
else:
# Set default OpenAI models if not already set to OpenAI models
- if analyzer.config["agent_llm_model"].startswith(("claude", "qwen")):
+ if analyzer.config["agent_llm_model"].startswith(("claude", "qwen", "MiniMax")):
analyzer.config["agent_llm_model"] = "gpt-4o-mini"
- if analyzer.config["graph_llm_model"].startswith(("claude", "qwen")):
+ if analyzer.config["graph_llm_model"].startswith(("claude", "qwen", "MiniMax")):
analyzer.config["graph_llm_model"] = "gpt-4o"
analyzer.trading_graph.config.update(analyzer.config)
@@ -928,8 +955,8 @@ def update_api_key():
if not new_api_key:
return jsonify({"error": "API key is required"})
- if provider not in ["openai", "anthropic", "qwen"]:
- return jsonify({"error": "Provider must be 'openai', 'anthropic', or 'qwen'"})
+ if provider not in ["openai", "anthropic", "qwen", "minimax"]:
+ return jsonify({"error": "Provider must be 'openai', 'anthropic', 'qwen', or 'minimax'"})
print(f"Updating {provider} API key to: {new_api_key[:8]}...{new_api_key[-4:]}")
@@ -940,6 +967,8 @@ def update_api_key():
os.environ["ANTHROPIC_API_KEY"] = new_api_key
elif provider == "qwen":
os.environ["DASHSCOPE_API_KEY"] = new_api_key
+ elif provider == "minimax":
+ os.environ["MINIMAX_API_KEY"] = new_api_key
# Update the API key in the trading graph
analyzer.trading_graph.update_api_key(new_api_key, provider=provider)
@@ -974,6 +1003,11 @@ def get_api_key_status():
# Fallback to config if not in environment
if not api_key and hasattr(analyzer, 'config'):
api_key = analyzer.config.get("qwen_api_key", "")
+ elif provider == "minimax":
+ api_key = os.environ.get("MINIMAX_API_KEY", "")
+ # Fallback to config if not in environment
+ if not api_key and hasattr(analyzer, 'config'):
+ api_key = analyzer.config.get("minimax_api_key", "")
else:
api_key = ""