Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions docs/liteflow-react-agent-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,50 @@ protected ModelSpec<?> model() {

凭据来源:`liteflow.agent.openai-compatible.myvendor.api-key` / `base-url`。自定义厂商没有内置默认地址,通常需要配置 `base-url`。

**MiniMax compatible endpoints and current models:**

```java
@Override
protected ModelSpec<?> model() {
return Minimax.of("MiniMax-M3");
}
```

`Minimax.of(...)` uses `liteflow.agent.openai-compatible.minimax` and defaults to the global OpenAI-compatible endpoint. Both `MiniMax-M3` and `MiniMax-M2.7` can also use the Anthropic-compatible adapter:

```java
@Override
protected ModelSpec<?> model() {
return AnthropicCompatible.custom("minimax", "MiniMax-M2.7");
}
```

`AnthropicCompatible.custom(...)` uses `liteflow.agent.anthropic-compatible.minimax`. Select the endpoint for the account region through the matching `base-url` setting:

| Region | OpenAI-compatible base URL | Anthropic-compatible base URL |
| --- | --- | --- |
| Global | `https://api.minimax.io/v1` | `https://api.minimax.io/anthropic` |
| CN | `https://api.minimaxi.com/v1` | `https://api.minimaxi.com/anthropic` |

The Anthropic-compatible base URLs intentionally end in `/anthropic`; the client appends `/v1/messages` when sending a request.

The context window is the combined input and output limit. Current model capabilities are:

| Model | Context window | Input modalities | Thinking |
| --- | ---: | --- | --- |
| `MiniMax-M3` | 1,000,000 | text, image, video | adaptive or disabled; enabled by default |
| `MiniMax-M2.7` | 204,800 | text | always on |

Current pay-as-you-go pricing is in USD per one million tokens:

| Model | Service tier and input range | Input | Output | Cache read | Cache write |
| --- | --- | ---: | ---: | ---: | ---: |
| `MiniMax-M3` | standard, up to 512,000 input tokens | 0.30 | 1.20 | 0.06 | N/A |
| `MiniMax-M3` | standard, over 512,000 input tokens | 0.60 | 2.40 | 0.12 | N/A |
| `MiniMax-M3` | priority, up to 512,000 input tokens | 0.45 | 1.80 | 0.09 | N/A |
| `MiniMax-M3` | priority, over 512,000 input tokens | 0.90 | 3.60 | 0.18 | N/A |
| `MiniMax-M2.7` | standard | 0.30 | 1.20 | 0.06 | 0.375 |

**Anthropic Claude:**

```java
Expand Down Expand Up @@ -480,7 +524,13 @@ liteflow:
kimi:
api-key: ${KIMI_API_KEY}
base-url: https://api.moonshot.cn/v1
minimax:
api-key: ${MINIMAX_API_KEY}
base-url: https://api.minimax.io/v1
anthropic-compatible:
minimax:
api-key: ${MINIMAX_API_KEY}
base-url: https://api.minimax.io/anthropic
gateway:
api-key: ${ANTHROPIC_GATEWAY_API_KEY}
base-url: https://anthropic-gateway.example.com
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public final class Minimax {
private static final String CONFIG_KEY = "minimax";
private static final String BASE_URL = "https://api.minimax.chat/v1";
private static final String BASE_URL = "https://api.minimax.io/v1";
private Minimax() {}

public static OpenAICompatibleSpec of(String modelName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ public static OpenAIChatModel glm(String apiKey, String modelName) {
return OpenAIModelFactory.custom(apiKey, "https://open.bigmodel.cn/api/paas/v4", modelName);
}
public static OpenAIChatModel minimax(String apiKey, String modelName) {
return OpenAIModelFactory.custom(apiKey, "https://api.minimax.chat/v1", modelName);
return OpenAIModelFactory.custom(apiKey, "https://api.minimax.io/v1", modelName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package com.yomahub.liteflow.test.agent.platform.minimax;

import com.yomahub.liteflow.agent.anthropic.AnthropicCompatible;
import com.yomahub.liteflow.agent.openai.Minimax;
import com.yomahub.liteflow.property.agent.AgentConfig;
import com.yomahub.liteflow.property.agent.PlatformCredential;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.model.ExecutionConfig;
import io.agentscope.core.model.GenerateOptions;
import io.agentscope.core.model.Model;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;

public class MinimaxEndpointTest {

private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(10);

@Test
public void testOpenAICompatibleRequestPath() throws Exception {
try (RequestCapture capture = new RequestCapture()) {
AgentConfig config = new AgentConfig();
config.getOpenaiCompatible()
.put("minimax", credential(capture.baseUrl("/v1")));

Model model = Minimax.of("MiniMax-M3").stream(false).resolve(config);

invoke(model);
Assertions.assertEquals("/v1/chat/completions", capture.awaitPath());
}
}

@Test
public void testAnthropicCompatibleRequestPath() throws Exception {
try (RequestCapture capture = new RequestCapture()) {
AgentConfig config = new AgentConfig();
config.getAnthropicCompatible()
.put("minimax", credential(capture.baseUrl("/anthropic")));

Model model = AnthropicCompatible.custom("minimax", "MiniMax-M2.7")
.stream(false)
.resolve(config);

invoke(model);
Assertions.assertEquals("/anthropic/v1/messages", capture.awaitPath());
}
}

private static PlatformCredential credential(String baseUrl) {
PlatformCredential credential = new PlatformCredential();
credential.setApiKey("test-key");
credential.setBaseUrl(baseUrl);
return credential;
}

private static void invoke(Model model) {
Msg message = Msg.builder()
.name("user")
.role(MsgRole.USER)
.textContent("ping")
.build();
GenerateOptions options = GenerateOptions.builder()
.executionConfig(ExecutionConfig.builder()
.timeout(REQUEST_TIMEOUT)
.maxAttempts(1)
.build())
.build();

model.stream(List.of(message), List.of(), options)
.onErrorResume(error -> Flux.empty())
.blockLast(REQUEST_TIMEOUT);
}

private static final class RequestCapture implements AutoCloseable {

private final ServerSocket serverSocket;
private final CompletableFuture<String> requestPath;

private RequestCapture() throws IOException {
serverSocket = new ServerSocket(0, 1, InetAddress.getByName("127.0.0.1"));
requestPath = CompletableFuture.supplyAsync(this::captureRequest);
}

private String baseUrl(String path) {
return "http://127.0.0.1:" + serverSocket.getLocalPort() + path;
}

private String awaitPath() throws Exception {
return requestPath.get(REQUEST_TIMEOUT.toSeconds(), TimeUnit.SECONDS);
}

private String captureRequest() {
try (Socket socket = serverSocket.accept();
BufferedReader reader = new BufferedReader(new InputStreamReader(
socket.getInputStream(), StandardCharsets.UTF_8));
OutputStream output = socket.getOutputStream()) {
String requestLine = reader.readLine();
if (requestLine == null) {
throw new IOException("Missing HTTP request line");
}

String line;
while ((line = reader.readLine()) != null && !line.isEmpty()) {
// Consume request headers before returning the test response.
}

byte[] response = ("HTTP/1.1 400 Bad Request\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 2\r\n"
+ "Connection: close\r\n"
+ "\r\n{}")
.getBytes(StandardCharsets.UTF_8);
output.write(response);
output.flush();

String[] parts = requestLine.split(" ", 3);
if (parts.length < 2) {
throw new IOException("Invalid HTTP request line: " + requestLine);
}
return parts[1];
} catch (IOException error) {
throw new IllegalStateException("Failed to capture HTTP request", error);
}
}

@Override
public void close() throws IOException {
serverSocket.close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class MinimaxPlatformAgentCmp extends ReActAgentComponent {

@Override
protected ModelSpec<?> model() {
String model = LiveTestEnv.resolveOrDefault(LiveTestEnv.MINIMAX_MODEL, "MiniMax-Text-01");
String model = LiveTestEnv.resolveOrDefault(LiveTestEnv.MINIMAX_MODEL, "MiniMax-M3");
return Minimax.of(model).temperature(0.1).maxTokens(64);
}

Expand Down