diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index d7f17e00d5..732fee384d 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -5,6 +5,7 @@ import { GEMINI_FLASH_MODEL_REGEX, getThinkModelType, isDeepSeekHybridInferenceModel, + isDoubaoSeedAfter251015, isDoubaoThinkingAutoModel, isGrok4FastReasoningModel, isGrokReasoningModel, @@ -170,6 +171,10 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin // Doubao 思考模式支持 if (isSupportedThinkingTokenDoubaoModel(model)) { + if (isDoubaoSeedAfter251015(model)) { + return { reasoningEffort } + } + // Comment below this line seems weird. reasoning is high instead of null/undefined. Who wrote this? // reasoningEffort 为空,默认开启 enabled if (reasoningEffort === 'high') { return { thinking: { type: 'enabled' } } @@ -226,12 +231,12 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin const supportedOptions = MODEL_SUPPORTED_REASONING_EFFORT[modelType] if (supportedOptions.includes(reasoningEffort)) { return { - reasoning_effort: reasoningEffort + reasoningEffort } } else { // 如果不支持,fallback到第一个支持的值 return { - reasoning_effort: supportedOptions[0] + reasoningEffort: supportedOptions[0] } } } diff --git a/src/renderer/src/config/__test__/reasoning.test.ts b/src/renderer/src/config/__test__/reasoning.test.ts new file mode 100644 index 0000000000..96fad861f3 --- /dev/null +++ b/src/renderer/src/config/__test__/reasoning.test.ts @@ -0,0 +1,166 @@ +import { describe, expect, it, vi } from 'vitest' + +import { isDoubaoSeedAfter251015, isDoubaoThinkingAutoModel } from '../models/reasoning' + +// FIXME: Idk why it's imported. Maybe circular dependency somewhere +vi.mock('@renderer/services/AssistantService.ts', () => ({ + getDefaultAssistant: () => { + return { + id: 'default', + name: 'default', + emoji: '😀', + prompt: '', + topics: [], + messages: [], + type: 'assistant', + regularPhrases: [], + settings: {} + } + } +})) + +describe('Doubao Models', () => { + describe('isDoubaoThinkingAutoModel', () => { + it('should return false for invalid models', () => { + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-251015', + name: 'doubao-seed-1-6-251015', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-lite-251015', + name: 'doubao-seed-1-6-lite-251015', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-thinking-250715', + name: 'doubao-seed-1-6-thinking-250715', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-flash', + name: 'doubao-seed-1-6-flash', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-thinking', + name: 'doubao-seed-1-6-thinking', + provider: '', + group: '' + }) + ).toBe(false) + }) + + it('should return true for valid models', () => { + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-250615', + name: 'doubao-seed-1-6-250615', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isDoubaoThinkingAutoModel({ + id: 'Doubao-Seed-1.6', + name: 'Doubao-Seed-1.6', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-1-5-thinking-pro-m', + name: 'doubao-1-5-thinking-pro-m', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1.6-lite', + name: 'doubao-seed-1.6-lite', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-1-5-thinking-pro-m-12345', + name: 'doubao-1-5-thinking-pro-m-12345', + provider: '', + group: '' + }) + ).toBe(true) + }) + }) + + describe('isDoubaoSeedAfter251015', () => { + it('should return true for models matching the pattern', () => { + expect( + isDoubaoSeedAfter251015({ + id: 'doubao-seed-1-6-251015', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isDoubaoSeedAfter251015({ + id: 'doubao-seed-1-6-lite-251015', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return false for models not matching the pattern', () => { + expect( + isDoubaoSeedAfter251015({ + id: 'doubao-seed-1-6-250615', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoSeedAfter251015({ + id: 'Doubao-Seed-1.6', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoSeedAfter251015({ + id: 'doubao-1-5-thinking-pro-m', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoSeedAfter251015({ + id: 'doubao-seed-1-6-lite-251016', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + }) + }) +}) diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index 1ef0303f61..a7e825ef4f 100644 --- a/src/renderer/src/config/models/reasoning.ts +++ b/src/renderer/src/config/models/reasoning.ts @@ -31,6 +31,7 @@ export const MODEL_SUPPORTED_REASONING_EFFORT: ReasoningEffortConfig = { qwen_thinking: ['low', 'medium', 'high'] as const, doubao: ['auto', 'high'] as const, doubao_no_auto: ['high'] as const, + doubao_after_251015: ['minimal', 'low', 'medium', 'high'] as const, hunyuan: ['auto'] as const, zhipu: ['auto'] as const, perplexity: ['low', 'medium', 'high'] as const, @@ -51,6 +52,7 @@ export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = { qwen_thinking: MODEL_SUPPORTED_REASONING_EFFORT.qwen_thinking, doubao: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao] as const, doubao_no_auto: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao_no_auto] as const, + doubao_after_251015: MODEL_SUPPORTED_REASONING_EFFORT.doubao_after_251015, hunyuan: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.hunyuan] as const, zhipu: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.zhipu] as const, perplexity: MODEL_SUPPORTED_REASONING_EFFORT.perplexity, @@ -85,6 +87,8 @@ export const getThinkModelType = (model: Model): ThinkingModelType => { } else if (isSupportedThinkingTokenDoubaoModel(model)) { if (isDoubaoThinkingAutoModel(model)) { thinkingModelType = 'doubao' + } else if (isDoubaoSeedAfter251015(model)) { + thinkingModelType = 'doubao_after_251015' } else { thinkingModelType = 'doubao_no_auto' } @@ -308,14 +312,21 @@ export const DOUBAO_THINKING_MODEL_REGEX = /doubao-(?:1[.-]5-thinking-vision-pro|1[.-]5-thinking-pro-m|seed-1[.-]6(?:-flash)?(?!-(?:thinking)(?:-|$)))(?:-[\w-]+)*/i // 支持 auto 的 Doubao 模型 doubao-seed-1.6-xxx doubao-seed-1-6-xxx doubao-1-5-thinking-pro-m-xxx +// Auto thinking is no longer supported after version 251015, see https://console.volcengine.com/ark/region:ark+cn-beijing/model/detail?Id=doubao-seed-1-6 export const DOUBAO_THINKING_AUTO_MODEL_REGEX = - /doubao-(1-5-thinking-pro-m|seed-1[.-]6)(?!-(?:flash|thinking)(?:-|$))(?:-[\w-]+)*/i + /doubao-(1-5-thinking-pro-m|seed-1[.-]6)(?!-(?:flash|thinking)(?:-|$))(?:-lite)?(?!-251015)(?:-\d+)?$/i export function isDoubaoThinkingAutoModel(model: Model): boolean { const modelId = getLowerBaseModelName(model.id) return DOUBAO_THINKING_AUTO_MODEL_REGEX.test(modelId) || DOUBAO_THINKING_AUTO_MODEL_REGEX.test(model.name) } +export function isDoubaoSeedAfter251015(model: Model): boolean { + const pattern = new RegExp(/doubao-seed-1-6-(?:lite-)?251015/i) + const result = pattern.test(model.id) + return result +} + export function isSupportedThinkingTokenDoubaoModel(model?: Model): boolean { if (!model) { return false diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 76f44204ab..5690a6c557 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -85,6 +85,7 @@ const ThinkModelTypes = [ 'qwen_thinking', 'doubao', 'doubao_no_auto', + 'doubao_after_251015', 'hunyuan', 'zhipu', 'perplexity', diff --git a/src/renderer/src/types/sdk.ts b/src/renderer/src/types/sdk.ts index 54a7896be4..e18891f2bf 100644 --- a/src/renderer/src/types/sdk.ts +++ b/src/renderer/src/types/sdk.ts @@ -78,6 +78,7 @@ export type ReasoningEffortOptionalParams = { thinking?: { type: 'disabled' | 'enabled' | 'auto'; budget_tokens?: number } reasoning?: { max_tokens?: number; exclude?: boolean; effort?: string; enabled?: boolean } | OpenAI.Reasoning reasoningEffort?: OpenAI.Chat.Completions.ChatCompletionCreateParams['reasoning_effort'] | 'none' | 'auto' + // WARN: This field will be overwrite to undefined by aisdk if the provider is openai-compatible. Use reasoningEffort instead. reasoning_effort?: OpenAI.Chat.Completions.ChatCompletionCreateParams['reasoning_effort'] | 'none' | 'auto' enable_thinking?: boolean thinking_budget?: number