项目文件夹

文件
2026-07-19 19:02:26 +08:00

240 行
6.5 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import MiniMaxLLMProvider from '@/core/llm-manager/llm-providers/minimax-llm-provider'
import type { ResolvedLLMTarget } from '@/core/llm-manager/llm-routing'
import type { CompletionParams } from '@/core/llm-manager/types'
import { LLMDuties, LLMProviders } from '@/core/llm-manager/types'
vi.mock('@/config', () => ({
CONFIG_MANAGER: {
getProviderAPIKeyEnv: vi.fn(() => null),
getProviderAPIKey: vi.fn(() => 'test-minimax-key'),
getProviderBaseURL: vi.fn(
() => process.env['TEST_MINIMAX_BASE_URL'] || ''
)
}
}))
vi.mock('@/helpers/log-helper', () => ({
LogHelper: {
title: vi.fn(),
success: vi.fn(),
info: vi.fn(),
debug: vi.fn(),
warning: vi.fn(),
error: vi.fn()
}
}))
interface ProviderWithPrivateCallOptions {
buildCallOptions(
prompt: string,
completionParams: CompletionParams
): Record<string, unknown>
}
type TestProvider = MiniMaxLLMProvider & ProviderWithPrivateCallOptions
function createProvider(model: string): TestProvider {
const target: ResolvedLLMTarget = {
provider: LLMProviders.MiniMax,
model,
label: `minimax/${model}`,
isLocal: false,
isEnabled: true,
isResolved: true
}
return new MiniMaxLLMProvider(target) as TestProvider
}
function createCompletionParams(
overrides: Partial<CompletionParams> = {}
): CompletionParams {
return {
dutyType: LLMDuties.ReAct,
systemPrompt: 'Plan the next step.',
...overrides
}
}
describe('MiniMaxLLMProvider', () => {
beforeEach(() => {
vi.stubEnv('LEON_MINIMAX_API_KEY', 'test-minimax-key')
vi.stubEnv('TEST_MINIMAX_BASE_URL', '')
})
afterEach(() => {
vi.unstubAllEnvs()
vi.unstubAllGlobals()
})
it('uses adaptive thinking and separated reasoning for MiniMax-M3', () => {
const provider = createProvider('MiniMax-M3')
const options = provider.buildCallOptions(
'Answer normally.',
createCompletionParams({ reasoningMode: 'on' })
)
expect(options['providerOptions']).toEqual({
minimax: {
reasoning_split: true,
thinking: { type: 'adaptive' }
}
})
})
it('disables MiniMax-M3 thinking when requested', () => {
const provider = createProvider('MiniMax-M3')
const options = provider.buildCallOptions(
'Answer directly.',
createCompletionParams({ reasoningMode: 'off' })
)
expect(options['providerOptions']).toEqual({
minimax: {
reasoning_split: true,
thinking: { type: 'disabled' }
}
})
})
it('keeps MiniMax-M2.7 thinking enabled', () => {
const provider = createProvider('MiniMax-M2.7')
const options = provider.buildCallOptions(
'Answer directly.',
createCompletionParams({ reasoningMode: 'off' })
)
expect(options['providerOptions']).toEqual({
minimax: {
reasoning_split: true
}
})
})
it('keeps MiniMax-M2.7 thinking enabled with Anthropic compatibility', () => {
vi.stubEnv(
'TEST_MINIMAX_BASE_URL',
'https://api.minimax.io/anthropic'
)
const provider = createProvider('MiniMax-M2.7')
const options = provider.buildCallOptions(
'Answer directly.',
createCompletionParams({ reasoningMode: 'off' })
)
expect(options['providerOptions']).toEqual({
anthropic: {
sendReasoning: true
}
})
})
it.each([
{
name: 'global OpenAI-compatible',
baseURL: 'https://api.minimax.io/v1',
expectedRequestURL: 'https://api.minimax.io/v1/chat/completions',
flavor: 'openai-compatible'
},
{
name: 'China OpenAI-compatible',
baseURL: 'https://api.minimaxi.com/v1',
expectedRequestURL: 'https://api.minimaxi.com/v1/chat/completions',
flavor: 'openai-compatible'
},
{
name: 'global Anthropic-compatible',
baseURL: 'https://api.minimax.io/anthropic',
expectedRequestURL: 'https://api.minimax.io/anthropic/v1/messages',
flavor: 'anthropic'
},
{
name: 'China Anthropic-compatible',
baseURL: 'https://api.minimaxi.com/anthropic',
expectedRequestURL: 'https://api.minimaxi.com/anthropic/v1/messages',
flavor: 'anthropic'
}
])('sends a valid request to the $name endpoint', async ({
baseURL,
expectedRequestURL,
flavor
}) => {
vi.stubEnv('TEST_MINIMAX_BASE_URL', baseURL)
const fetchMock = vi.fn(
async (input: string | URL | Request, init?: RequestInit) => {
void input
void init
return new Response(
JSON.stringify(
flavor === 'anthropic'
? {
id: 'msg-test',
type: 'message',
role: 'assistant',
model: 'MiniMax-M3',
content: [{ type: 'text', text: 'Done.' }],
stop_reason: 'end_turn',
stop_sequence: null,
usage: {
input_tokens: 1,
output_tokens: 1
}
}
: {
id: 'chatcmpl-test',
created: 0,
model: 'MiniMax-M3',
choices: [
{
index: 0,
message: {
role: 'assistant',
content: 'Done.'
},
finish_reason: 'stop'
}
],
usage: {
prompt_tokens: 1,
completion_tokens: 1,
total_tokens: 2
}
}
),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
)
}
)
vi.stubGlobal('fetch', fetchMock)
const provider = createProvider('MiniMax-M3')
await provider.runChatCompletion(
'Answer directly.',
createCompletionParams({
reasoningMode: flavor === 'anthropic' ? 'on' : 'off'
})
)
const [requestURL, requestInit] = fetchMock.mock.calls[0]!
const requestBody = JSON.parse(String(requestInit?.body)) as Record<
string,
unknown
>
expect(String(requestURL)).toBe(expectedRequestURL)
expect(requestBody['thinking']).toEqual({
type: flavor === 'anthropic' ? 'adaptive' : 'disabled'
})
expect(requestBody['reasoning_split']).toBe(
flavor === 'openai-compatible' ? true : undefined
)
expect(requestBody['reasoning_effort']).toBeUndefined()
})
})