Compare commits

...

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
ab1a9f56f5 🎨 style: add missing semicolon for consistency
Co-authored-by: GeorgeDong32 <98630204+GeorgeDong32@users.noreply.github.com>
2025-11-04 12:50:54 +00:00
copilot-swe-agent[bot]
de1e2a94bd test: add tests for OpenAIAPIClient reasoning effort parameter format
Co-authored-by: GeorgeDong32 <98630204+GeorgeDong32@users.noreply.github.com>
2025-11-04 12:48:40 +00:00
copilot-swe-agent[bot]
044eac0cf9 🐛 fix: use correct reasoning parameter format for GitHub Copilot GPT-5 models
Co-authored-by: GeorgeDong32 <98630204+GeorgeDong32@users.noreply.github.com>
2025-11-04 12:44:34 +00:00
copilot-swe-agent[bot]
7fceb434b8 Initial plan 2025-11-04 12:29:14 +00:00
2 changed files with 226 additions and 0 deletions

View File

@@ -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({})
})
})
})

View File

@@ -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]