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
51 changes: 51 additions & 0 deletions docs/users/configuration/model-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,57 @@ This auth type supports not only OpenAI's official API but also any OpenAI-compa
}
```

### MiniMax (`openai`)

MiniMax provides OpenAI-compatible chat models. Configure using the `openai` auth type with MiniMax's base URL. MiniMax requires temperature to be in the range `(0.0, 1.0]` — the provider enforces this automatically.

| Model ID | Description |
| ------------------------ | -------------------------------------------------------- |
| `MiniMax-M2.7` | Peak performance, ultimate value — handles complex tasks |
| `MiniMax-M2.7-highspeed` | Same performance, faster and more agile |

```json
{
"env": {
"MINIMAX_API_KEY": "your-minimax-api-key"
},
"modelProviders": {
"openai": [
{
"id": "MiniMax-M2.7",
"name": "MiniMax-M2.7",
"envKey": "MINIMAX_API_KEY",
"baseUrl": "https://api.minimax.io/v1",
"generationConfig": {
"contextWindowSize": 200000,
"samplingParams": {
"temperature": 1.0,
"max_tokens": 65536
}
}
},
{
"id": "MiniMax-M2.7-highspeed",
"name": "MiniMax-M2.7-highspeed",
"envKey": "MINIMAX_API_KEY",
"baseUrl": "https://api.minimax.io/v1",
"generationConfig": {
"contextWindowSize": 200000,
"samplingParams": {
"temperature": 1.0,
"max_tokens": 65536
}
}
}
]
}
}
```

> [!note]
>
> **Domestic mirror**: Users in mainland China can replace `api.minimax.io` with `api.minimaxi.com` (note the extra `i`).

### Local Self-Hosted Models (via OpenAI-compatible API)

Most local inference servers (vLLM, Ollama, LM Studio, etc.) provide an OpenAI-compatible API endpoint. Configure them using the `openai` auth type with a local `baseUrl`:
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/core/openaiContentGenerator/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export const DEFAULT_DASHSCOPE_BASE_URL =
'https://dashscope.aliyuncs.com/compatible-mode/v1';
export const DEFAULT_DEEPSEEK_BASE_URL = 'https://api.deepseek.com/v1';
export const DEFAULT_OPEN_ROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
export const DEFAULT_MINIMAX_BASE_URL = 'https://api.minimax.io/v1';
10 changes: 10 additions & 0 deletions packages/core/src/core/openaiContentGenerator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { OpenAIContentGenerator } from './openaiContentGenerator.js';
import {
DashScopeOpenAICompatibleProvider,
DeepSeekOpenAICompatibleProvider,
MiniMaxOpenAICompatibleProvider,
ModelScopeOpenAICompatibleProvider,
OpenRouterOpenAICompatibleProvider,
type OpenAICompatibleProvider,
Expand All @@ -26,6 +27,7 @@ export {
type OpenAICompatibleProvider,
DashScopeOpenAICompatibleProvider,
DeepSeekOpenAICompatibleProvider,
MiniMaxOpenAICompatibleProvider,
OpenRouterOpenAICompatibleProvider,
} from './provider/index.js';

Expand Down Expand Up @@ -87,6 +89,14 @@ export function determineProvider(
);
}

// Check for MiniMax provider
if (MiniMaxOpenAICompatibleProvider.isMiniMaxProvider(config)) {
return new MiniMaxOpenAICompatibleProvider(
contentGeneratorConfig,
cliConfig,
);
}

// Default provider for standard OpenAI-compatible APIs
return new DefaultOpenAICompatibleProvider(contentGeneratorConfig, cliConfig);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { ModelScopeOpenAICompatibleProvider } from './modelscope.js';
export { DashScopeOpenAICompatibleProvider } from './dashscope.js';
export { DeepSeekOpenAICompatibleProvider } from './deepseek.js';
export { OpenRouterOpenAICompatibleProvider } from './openrouter.js';
export { MiniMaxOpenAICompatibleProvider } from './minimax.js';
export { DefaultOpenAICompatibleProvider } from './default.js';
export type {
OpenAICompatibleProvider,
Expand Down
227 changes: 227 additions & 0 deletions packages/core/src/core/openaiContentGenerator/provider/minimax.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/**
* @license
* Copyright 2025 Qwen
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, it, expect, vi, beforeEach } from 'vitest';
import type OpenAI from 'openai';
import { MiniMaxOpenAICompatibleProvider } from './minimax.js';
import type { ContentGeneratorConfig } from '../../contentGenerator.js';
import type { Config } from '../../../config/config.js';

// Mock OpenAI client to avoid real network calls
vi.mock('openai', () => ({
default: vi.fn().mockImplementation((config) => ({
config,
})),
}));

describe('MiniMaxOpenAICompatibleProvider', () => {
let provider: MiniMaxOpenAICompatibleProvider;
let mockContentGeneratorConfig: ContentGeneratorConfig;
let mockCliConfig: Config;

beforeEach(() => {
vi.clearAllMocks();

mockContentGeneratorConfig = {
apiKey: 'test-minimax-api-key',
baseUrl: 'https://api.minimax.io/v1',
model: 'MiniMax-M2.7',
} as ContentGeneratorConfig;

mockCliConfig = {
getCliVersion: vi.fn().mockReturnValue('1.0.0'),
getProxy: vi.fn().mockReturnValue(undefined),
} as unknown as Config;

provider = new MiniMaxOpenAICompatibleProvider(
mockContentGeneratorConfig,
mockCliConfig,
);
});

describe('isMiniMaxProvider', () => {
it('returns true when baseUrl hostname is api.minimax.io', () => {
const result = MiniMaxOpenAICompatibleProvider.isMiniMaxProvider(
mockContentGeneratorConfig,
);
expect(result).toBe(true);
});

it('returns true when baseUrl hostname is api.minimaxi.com', () => {
const config = {
...mockContentGeneratorConfig,
baseUrl: 'https://api.minimaxi.com/v1',
} as ContentGeneratorConfig;
expect(MiniMaxOpenAICompatibleProvider.isMiniMaxProvider(config)).toBe(
true,
);
});

it('returns false for non-MiniMax baseUrl', () => {
const config = {
...mockContentGeneratorConfig,
baseUrl: 'https://api.openai.com/v1',
} as ContentGeneratorConfig;
expect(MiniMaxOpenAICompatibleProvider.isMiniMaxProvider(config)).toBe(
false,
);
});

it('returns false when baseUrl is undefined', () => {
const config = {
...mockContentGeneratorConfig,
baseUrl: undefined,
} as ContentGeneratorConfig;
expect(MiniMaxOpenAICompatibleProvider.isMiniMaxProvider(config)).toBe(
false,
);
});

it('returns false for a URL that contains api.minimax.io as a path component', () => {
const config = {
...mockContentGeneratorConfig,
baseUrl: 'https://proxy.example.com/api.minimax.io/v1',
} as ContentGeneratorConfig;
expect(MiniMaxOpenAICompatibleProvider.isMiniMaxProvider(config)).toBe(
false,
);
});

it('returns false for a URL with api.minimax.io embedded in another hostname', () => {
const config = {
...mockContentGeneratorConfig,
baseUrl: 'https://evil.api.minimax.io.malicious.com/v1',
} as ContentGeneratorConfig;
expect(MiniMaxOpenAICompatibleProvider.isMiniMaxProvider(config)).toBe(
false,
);
});
});

describe('getDefaultGenerationConfig', () => {
it('returns temperature 1.0 as default', () => {
expect(provider.getDefaultGenerationConfig()).toEqual({
temperature: 1.0,
});
});
});

describe('buildRequest', () => {
const userPromptId = 'prompt-123';

it('removes response_format from request', () => {
const request = {
model: 'MiniMax-M2.7',
messages: [{ role: 'user' as const, content: 'Hello' }],
response_format: { type: 'json_object' as const },
} as OpenAI.Chat.ChatCompletionCreateParams;

const result = provider.buildRequest(request, userPromptId);
expect(result).not.toHaveProperty('response_format');
});

it('sets temperature to 1.0 when temperature is 0', () => {
const request: OpenAI.Chat.ChatCompletionCreateParams = {
model: 'MiniMax-M2.7',
messages: [{ role: 'user', content: 'Hello' }],
temperature: 0,
};

const result = provider.buildRequest(request, userPromptId);
expect(result.temperature).toBe(1.0);
});

it('sets temperature to 1.0 when temperature is undefined', () => {
const request: OpenAI.Chat.ChatCompletionCreateParams = {
model: 'MiniMax-M2.7',
messages: [{ role: 'user', content: 'Hello' }],
};

const result = provider.buildRequest(request, userPromptId);
expect(result.temperature).toBe(1.0);
});

it('sets temperature to 1.0 when temperature is null', () => {
const request = {
model: 'MiniMax-M2.7',
messages: [{ role: 'user' as const, content: 'Hello' }],
temperature: null,
} as unknown as OpenAI.Chat.ChatCompletionCreateParams;

const result = provider.buildRequest(request, userPromptId);
expect(result.temperature).toBe(1.0);
});

it('preserves valid temperature values', () => {
const request: OpenAI.Chat.ChatCompletionCreateParams = {
model: 'MiniMax-M2.7',
messages: [{ role: 'user', content: 'Hello' }],
temperature: 0.7,
};

const result = provider.buildRequest(request, userPromptId);
expect(result.temperature).toBe(0.7);
});

it('clamps temperature above 1.0 down to 1.0', () => {
const request: OpenAI.Chat.ChatCompletionCreateParams = {
model: 'MiniMax-M2.7',
messages: [{ role: 'user', content: 'Hello' }],
temperature: 1.5,
};

const result = provider.buildRequest(request, userPromptId);
expect(result.temperature).toBe(1.0);
});

it('preserves temperature exactly at 1.0', () => {
const request: OpenAI.Chat.ChatCompletionCreateParams = {
model: 'MiniMax-M2.7',
messages: [{ role: 'user', content: 'Hello' }],
temperature: 1.0,
};

const result = provider.buildRequest(request, userPromptId);
expect(result.temperature).toBe(1.0);
});

it('preserves messages unchanged', () => {
const request: OpenAI.Chat.ChatCompletionCreateParams = {
model: 'MiniMax-M2.7',
messages: [
{ role: 'user', content: 'Hello' },
{ role: 'assistant', content: 'Hi there' },
],
};

const result = provider.buildRequest(request, userPromptId);
expect(result.messages).toHaveLength(2);
expect(result.messages?.[0].content).toBe('Hello');
});
});

describe('buildClient', () => {
it('uses DEFAULT_MINIMAX_BASE_URL when no baseUrl configured', () => {
const configWithoutUrl = {
apiKey: 'test-key',
model: 'MiniMax-M2.7',
} as ContentGeneratorConfig;

const providerWithoutUrl = new MiniMaxOpenAICompatibleProvider(
configWithoutUrl,
mockCliConfig,
);

const client = providerWithoutUrl.buildClient();
expect(client).toBeDefined();
});

it('uses configured baseUrl when provided', () => {
const client = provider.buildClient();
expect(client).toBeDefined();
});
});
});
Loading
Loading