Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import type { Assistant, Model, Provider } from '@renderer/types'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import { OpenAIAPIClient } from '../openai/OpenAIApiClient'

// Mock dependencies
vi.mock('@renderer/config/models', () => ({
isSupportedReasoningEffortOpenAIModel: vi.fn((model: Model) => {
const modelId = model.id.toLowerCase()
return (
modelId.includes('gpt-5') ||
(modelId.includes('o1') && !modelId.includes('o1-preview') && !modelId.includes('o1-mini')) ||
modelId.includes('o3') ||
modelId.includes('o4')
)
}),
isSupportedReasoningEffortGrokModel: vi.fn((model: Model) => {
return model.id.toLowerCase().includes('grok')
}),
isSupportedReasoningEffortPerplexityModel: vi.fn((model: Model) => {
return model.id.toLowerCase().includes('sonar-deep-research')
}),
isSupportedReasoningEffortModel: vi.fn((model: Model) => {
const modelId = model.id.toLowerCase()
return (
modelId.includes('gpt-5') ||
modelId.includes('o1') ||
modelId.includes('o3') ||
modelId.includes('o4') ||
modelId.includes('grok') ||
modelId.includes('sonar-deep-research')
)
}),
isReasoningModel: vi.fn(() => true),
isOpenAIDeepResearchModel: vi.fn(() => false),
isSupportedThinkingTokenZhipuModel: vi.fn(() => false),
isDeepSeekHybridInferenceModel: vi.fn(() => false),
isSupportedThinkingTokenGeminiModel: vi.fn(() => false),
isSupportedThinkingTokenQwenModel: vi.fn(() => false),
isSupportedThinkingTokenHunyuanModel: vi.fn(() => false),
isSupportedThinkingTokenClaudeModel: vi.fn(() => false),
isSupportedThinkingTokenDoubaoModel: vi.fn(() => false),
isQwenReasoningModel: vi.fn(() => false),
isGrokReasoningModel: vi.fn(() => false),
isOpenAIReasoningModel: vi.fn(() => false),
isSupportedThinkingTokenModel: vi.fn(() => false),
isQwenAlwaysThinkModel: vi.fn(() => false),
isDoubaoThinkingAutoModel: vi.fn(() => false),
getThinkModelType: vi.fn(() => 'default'),
GEMINI_FLASH_MODEL_REGEX: /gemini.*flash/i,
MODEL_SUPPORTED_REASONING_EFFORT: {
default: ['low', 'medium', 'high'],
grok: ['low', 'high'],
perplexity: ['low', 'medium', 'high'],
gpt5: ['minimal', 'low', 'medium', 'high']
},
findTokenLimit: vi.fn()
}))

vi.mock('@renderer/config/providers', () => ({
isSupportEnableThinkingProvider: vi.fn(() => false)
}))

vi.mock('@renderer/hooks/useSettings', () => ({
getStoreSetting: vi.fn(() => ({
summaryText: 'off'
}))
}))

vi.mock('@renderer/types', () => ({
SystemProviderIds: {
groq: 'groq',
openrouter: 'openrouter',
dashscope: 'dashscope',
doubao: 'doubao',
silicon: 'silicon',
ppio: 'ppio',
poe: 'poe'
},
EFFORT_RATIO: {
minimal: 0.1,
low: 0.3,
medium: 0.5,
high: 0.8,
auto: 1
}
}))

describe('OpenAIAPIClient - Reasoning Effort', () => {
let client: OpenAIAPIClient
let provider: Provider
let assistant: Assistant

beforeEach(() => {
provider = {
id: 'copilot',
name: 'Github Copilot',
type: 'openai',
apiKey: 'test-key',
apiHost: 'https://api.githubcopilot.com/',
models: []
}

client = new OpenAIAPIClient(provider)

assistant = {
id: 'test-assistant',
name: 'Test Assistant',
emoji: '🤖',
prompt: 'You are a helpful assistant',
topics: [],
messages: [],
type: 'assistant',
regularPhrases: [],
settings: {
reasoning_effort: 'medium'
}
}
})

describe('GPT-5 models through GitHub Copilot', () => {
it('should return reasoning object format for gpt-5-mini', () => {
const model: Model = {
id: 'gpt-5-mini',
name: 'GPT-5 Mini',
provider: 'copilot',
group: 'openai'
}

const result = client.getReasoningEffort(assistant, model)

// Should use base class implementation which returns { reasoning: { effort, summary } }
expect(result).toHaveProperty('reasoning')
expect(result.reasoning).toHaveProperty('effort', 'medium')
expect(result.reasoning).toHaveProperty('summary')
expect(result).not.toHaveProperty('reasoning_effort')
})

it('should return reasoning object format for o1-2024-12-17', () => {
const model: Model = {
id: 'o1-2024-12-17',
name: 'O1',
provider: 'copilot',
group: 'openai'
}

const result = client.getReasoningEffort(assistant, model)

expect(result).toHaveProperty('reasoning')
expect(result.reasoning).toHaveProperty('effort', 'medium')
expect(result).not.toHaveProperty('reasoning_effort')
})

it('should return reasoning object format for o3-mini', () => {
const model: Model = {
id: 'o3-mini',
name: 'O3 Mini',
provider: 'copilot',
group: 'openai'
}

const result = client.getReasoningEffort(assistant, model)

expect(result).toHaveProperty('reasoning')
expect(result.reasoning).toHaveProperty('effort', 'medium')
expect(result).not.toHaveProperty('reasoning_effort')
})
})

describe('Non-OpenAI reasoning models', () => {
it('should return reasoning_effort format for Grok models', () => {
const model: Model = {
id: 'grok-3-mini',
name: 'Grok 3 Mini',
provider: 'grok',
group: 'xai'
}

const result = client.getReasoningEffort(assistant, model)

// Should use reasoning_effort for non-OpenAI models
expect(result).toHaveProperty('reasoning_effort', 'medium')
expect(result).not.toHaveProperty('reasoning')
})

it('should return reasoning_effort format for Perplexity models', () => {
const model: Model = {
id: 'sonar-deep-research',
name: 'Sonar Deep Research',
provider: 'perplexity',
group: 'perplexity'
}

const result = client.getReasoningEffort(assistant, model)

expect(result).toHaveProperty('reasoning_effort', 'medium')
expect(result).not.toHaveProperty('reasoning')
})
})

describe('When reasoning_effort is not set', () => {
beforeEach(() => {
assistant.settings = {}
})

it('should return empty object for GPT-5 models', () => {
const model: Model = {
id: 'gpt-5-mini',
name: 'GPT-5 Mini',
provider: 'copilot',
group: 'openai'
}

const result = client.getReasoningEffort(assistant, model)

expect(result).toEqual({})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,13 @@ export class OpenAIAPIClient extends OpenAIBaseClient<

// Grok models/Perplexity models/OpenAI models
if (isSupportedReasoningEffortModel(model)) {
// For OpenAI models (GPT-5, o1, o3, o4, etc), use the base class implementation
// which returns the correct { reasoning: { effort, summary } } format
if (isSupportedReasoningEffortOpenAIModel(model)) {
return super.getReasoningEffort(assistant, model);
}

// For non-OpenAI models (Grok, Perplexity, etc), use reasoning_effort parameter
// 检查模型是否支持所选选项
const modelType = getThinkModelType(model)
const supportedOptions = MODEL_SUPPORTED_REASONING_EFFORT[modelType]
Expand Down