feat: add MiniMax as first-class LLM provider

Add MiniMax AI as the 7th LLM provider, supporting chat (M2.7, M2.5,
M2.5-highspeed) and embedding (embo-01) models via OpenAI-compatible API.

Changes:
- Add MINIMAX enum to ChatModeType
- Add MinimaxServiceImpl chat provider (OpenAI-compat streaming)
- Add MinimaxEmbeddingProvider for vector embeddings
- Add SQL migration for provider and model registration
- Add 14 unit tests + 3 integration tests
- Update README/README_EN with MiniMax in provider list
This commit is contained in:
octopus
2026-03-21 16:14:19 +08:00
parent 1a10104751
commit 5d14eb20af
11 changed files with 344 additions and 3 deletions

View File

@@ -31,7 +31,7 @@
| 模块 | 现有能力
|:----------:|---
| **模型管理** | 多模型接入(OpenAI/DeepSeek/通义/智谱)、多模态理解、Coze/DIFY/FastGPT平台集成
| **模型管理** | 多模型接入(OpenAI/DeepSeek/通义/智谱/MiniMax)、多模态理解、Coze/DIFY/FastGPT平台集成
| **知识管理** | 本地RAG + 向量库(Milvus/Weaviate) + 文档解析
| **工具管理** | Mcp协议集成、Skills能力 + 可扩展工具生态
| **流程编排** | 可视化工作流设计器、节点拖拽编排、SSE流式执行,目前已经支持模型调用,邮件发送,人工审核等节点

View File

@@ -34,7 +34,7 @@
| Module | Current Capabilities |
|:---:|---|
| **Model Management** | Multi-model integration (OpenAI/DeepSeek/Tongyi/Zhipu), multi-modal understanding, Coze/DIFY/FastGPT platform integration |
| **Model Management** | Multi-model integration (OpenAI/DeepSeek/Tongyi/Zhipu/MiniMax), multi-modal understanding, Coze/DIFY/FastGPT platform integration |
| **Knowledge Base** | Local RAG + Vector DB (Milvus/Weaviate) + Document parsing |
| **Tool Management** | MCP protocol integration, Skills capability + Extensible tool ecosystem |
| **Workflow Orchestration** | Visual workflow designer, drag-and-drop node orchestration, SSE streaming execution, currently supports model calls, email sending, manual review nodes |

View File

@@ -0,0 +1,23 @@
-- ----------------------------
-- Add MiniMax provider
-- ----------------------------
INSERT INTO `chat_provider` (`id`, `provider_name`, `provider_code`, `provider_icon`, `provider_desc`, `api_host`, `status`, `sort_order`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`)
VALUES (2010000000000000001, 'MiniMax', 'minimax', NULL, 'MiniMax大模型服务支持M2.7、M2.5等模型', 'https://api.minimax.io/v1', '0', 6, NULL, NOW(), '1', '1', NOW(), 'MiniMax厂商', NULL, '0', NULL, 0);
-- ----------------------------
-- Add MiniMax chat models
-- ----------------------------
INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_dimension`, `model_show`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`)
VALUES (2010000000000000002, 'chat', 'MiniMax-M2.7', 'minimax', 'MiniMax-M2.7', NULL, 'Y', 'https://api.minimax.io/v1', '', NULL, 1, NOW(), 1, NOW(), 'MiniMax最新旗舰模型M2.7支持1M上下文窗口', 0);
INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_dimension`, `model_show`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`)
VALUES (2010000000000000003, 'chat', 'MiniMax-M2.5', 'minimax', 'MiniMax-M2.5', NULL, 'Y', 'https://api.minimax.io/v1', '', NULL, 1, NOW(), 1, NOW(), 'MiniMax M2.5模型204K上下文窗口', 0);
INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_dimension`, `model_show`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`)
VALUES (2010000000000000004, 'chat', 'MiniMax-M2.5-highspeed', 'minimax', 'MiniMax-M2.5-highspeed', NULL, 'Y', 'https://api.minimax.io/v1', '', NULL, 1, NOW(), 1, NOW(), 'MiniMax M2.5高速版204K上下文窗口更低延迟', 0);
-- ----------------------------
-- Add MiniMax embedding model
-- ----------------------------
INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_dimension`, `model_show`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`)
VALUES (2010000000000000005, 'vector', 'embo-01', 'minimax', 'embo-01', 1536, 'N', 'https://api.minimax.io/v1', '', NULL, 1, NOW(), 1, NOW(), 'MiniMax embo-01嵌入模型1536维度', 0);

View File

@@ -146,6 +146,23 @@
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -15,7 +15,8 @@ public enum ChatModeType {
DEEP_SEEK("deepseek", "深度求索"),
QIAN_WEN("qianwen", "通义千问"),
OPEN_AI("openai", "openai"),
PPIO("ppio", "ppio");
PPIO("ppio", "ppio"),
MINIMAX("minimax", "MiniMax");
private final String code;
private final String description;

View File

@@ -0,0 +1,40 @@
package org.ruoyi.service.chat.impl.provider;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.enums.ChatModeType;
import org.ruoyi.service.chat.AbstractChatService;
import org.springframework.stereotype.Service;
/**
* MiniMax服务调用
* <p>
* MiniMax提供OpenAI兼容的API接口支持MiniMax-M2.7、MiniMax-M2.5等模型。
* API地址https://api.minimax.io/v1
*
* @author octopus
* @date 2026/3/21
*/
@Service
@Slf4j
public class MinimaxServiceImpl implements AbstractChatService {
@Override
public StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) {
return OpenAiStreamingChatModel.builder()
.baseUrl(chatModelVo.getApiHost())
.apiKey(chatModelVo.getApiKey())
.modelName(chatModelVo.getModelName())
.returnThinking(chatRequest.getEnableThinking())
.build();
}
@Override
public String getProviderName() {
return ChatModeType.MINIMAX.getCode();
}
}

View File

@@ -0,0 +1,17 @@
package org.ruoyi.service.embed.impl;
import org.springframework.stereotype.Component;
/**
* MiniMax嵌入模型兼容OpenAI接口
* <p>
* 支持embo-01模型1536维度向量。
* API地址https://api.minimax.io/v1
*
* @author octopus
* @date 2026/3/21
*/
@Component("minimax")
public class MinimaxEmbeddingProvider extends OpenAiEmbeddingProvider {
}

View File

@@ -0,0 +1,42 @@
package org.ruoyi.enums;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for ChatModeType enum
*/
class ChatModeTypeTest {
@Test
void minimaxEnumExists() {
ChatModeType minimax = ChatModeType.MINIMAX;
assertNotNull(minimax);
}
@Test
void minimaxCode_isMinimax() {
assertEquals("minimax", ChatModeType.MINIMAX.getCode());
}
@Test
void minimaxDescription_isMiniMax() {
assertEquals("MiniMax", ChatModeType.MINIMAX.getDescription());
}
@Test
void allProviders_haveUniqueCode() {
ChatModeType[] values = ChatModeType.values();
long uniqueCodes = java.util.Arrays.stream(values)
.map(ChatModeType::getCode)
.distinct()
.count();
assertEquals(values.length, uniqueCodes, "All providers must have unique codes");
}
@Test
void valueOf_minimax() {
assertEquals(ChatModeType.MINIMAX, ChatModeType.valueOf("MINIMAX"));
}
}

View File

@@ -0,0 +1,70 @@
package org.ruoyi.integration;
import dev.langchain4j.model.chat.StreamingChatModel;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.service.chat.impl.provider.MinimaxServiceImpl;
import static org.junit.jupiter.api.Assertions.*;
/**
* Integration tests for MiniMax provider.
* These tests require a valid MINIMAX_API_KEY environment variable.
*/
@EnabledIfEnvironmentVariable(named = "MINIMAX_API_KEY", matches = ".+")
class MinimaxIntegrationTest {
private MinimaxServiceImpl minimaxService;
private String apiKey;
@BeforeEach
void setUp() {
minimaxService = new MinimaxServiceImpl();
apiKey = System.getenv("MINIMAX_API_KEY");
}
@Test
void buildStreamingChatModel_withRealApiKey_M27() {
ChatModelVo modelVo = new ChatModelVo();
modelVo.setApiHost("https://api.minimax.io/v1");
modelVo.setApiKey(apiKey);
modelVo.setModelName("MiniMax-M2.7");
ChatRequest request = new ChatRequest();
request.setEnableThinking(false);
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
assertNotNull(model, "Should create streaming model with real API key");
}
@Test
void buildStreamingChatModel_withRealApiKey_M25() {
ChatModelVo modelVo = new ChatModelVo();
modelVo.setApiHost("https://api.minimax.io/v1");
modelVo.setApiKey(apiKey);
modelVo.setModelName("MiniMax-M2.5");
ChatRequest request = new ChatRequest();
request.setEnableThinking(false);
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
assertNotNull(model, "Should create streaming model with M2.5");
}
@Test
void buildStreamingChatModel_withRealApiKey_M25Highspeed() {
ChatModelVo modelVo = new ChatModelVo();
modelVo.setApiHost("https://api.minimax.io/v1");
modelVo.setApiKey(apiKey);
modelVo.setModelName("MiniMax-M2.5-highspeed");
ChatRequest request = new ChatRequest();
request.setEnableThinking(false);
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
assertNotNull(model, "Should create streaming model with M2.5-highspeed");
}
}

View File

@@ -0,0 +1,76 @@
package org.ruoyi.service.chat.impl.provider;
import dev.langchain4j.model.chat.StreamingChatModel;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.enums.ChatModeType;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for MinimaxServiceImpl
*/
class MinimaxServiceImplTest {
private MinimaxServiceImpl minimaxService;
@BeforeEach
void setUp() {
minimaxService = new MinimaxServiceImpl();
}
@Test
void getProviderName_returnsMinimaxCode() {
assertEquals("minimax", minimaxService.getProviderName());
assertEquals(ChatModeType.MINIMAX.getCode(), minimaxService.getProviderName());
}
@Test
void buildStreamingChatModel_returnsNonNull() {
ChatModelVo modelVo = new ChatModelVo();
modelVo.setApiHost("https://api.minimax.io/v1");
modelVo.setApiKey("test-api-key");
modelVo.setModelName("MiniMax-M2.7");
ChatRequest request = new ChatRequest();
request.setEnableThinking(false);
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
assertNotNull(model);
}
@Test
void buildStreamingChatModel_withThinkingEnabled() {
ChatModelVo modelVo = new ChatModelVo();
modelVo.setApiHost("https://api.minimax.io/v1");
modelVo.setApiKey("test-api-key");
modelVo.setModelName("MiniMax-M2.5");
ChatRequest request = new ChatRequest();
request.setEnableThinking(true);
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
assertNotNull(model);
}
@Test
void buildStreamingChatModel_withHighspeedModel() {
ChatModelVo modelVo = new ChatModelVo();
modelVo.setApiHost("https://api.minimax.io/v1");
modelVo.setApiKey("test-api-key");
modelVo.setModelName("MiniMax-M2.5-highspeed");
ChatRequest request = new ChatRequest();
request.setEnableThinking(false);
StreamingChatModel model = minimaxService.buildStreamingChatModel(modelVo, request);
assertNotNull(model);
}
@Test
void implementsAbstractChatService() {
assertInstanceOf(org.ruoyi.service.chat.AbstractChatService.class, minimaxService);
}
}

View File

@@ -0,0 +1,55 @@
package org.ruoyi.service.embed.impl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.enums.ModalityType;
import org.ruoyi.service.embed.BaseEmbedModelService;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for MinimaxEmbeddingProvider
*/
class MinimaxEmbeddingProviderTest {
private MinimaxEmbeddingProvider provider;
@BeforeEach
void setUp() {
provider = new MinimaxEmbeddingProvider();
}
@Test
void implementsBaseEmbedModelService() {
assertInstanceOf(BaseEmbedModelService.class, provider);
}
@Test
void extendsOpenAiEmbeddingProvider() {
assertInstanceOf(OpenAiEmbeddingProvider.class, provider);
}
@Test
void getSupportedModalities_returnsText() {
Set<ModalityType> modalities = provider.getSupportedModalities();
assertNotNull(modalities);
assertTrue(modalities.contains(ModalityType.TEXT));
assertEquals(1, modalities.size());
}
@Test
void configure_setsModelConfig() {
ChatModelVo config = new ChatModelVo();
config.setApiHost("https://api.minimax.io/v1");
config.setApiKey("test-api-key");
config.setModelName("embo-01");
config.setModelDimension(1536);
provider.configure(config);
// configure sets internal state; verify no exception thrown
assertNotNull(provider);
}
}