From ab78ef71d9e223e2b4de421f70a20b3243ae637f Mon Sep 17 00:00:00 2001 From: Phantom Date: Sun, 19 Oct 2025 19:02:16 +0800 Subject: [PATCH] feat(models): add doubao_after_251015 reasoning model type and support (#10826) * feat(models): add doubao_after_251015 model type and support Add new model type 'doubao_after_251015' with reasoning effort levels and update regex patterns to handle version 251015 and later * fix(sdk): add warning for reasoning_effort field and update reasoning logic Add warning comment about reasoning_effort field being overwritten for openai-compatible providers Update reasoning effort logic to handle Doubao seed models after 251015 and standardize field naming * fix(reasoning): update Doubao model regex patterns and tests Update regex patterns for Doubao model validation to correctly handle version constraints Add comprehensive test cases for model validation functions (cherry picked from commit 06b1ae0cb8e07dc81abcfb40c888ac8df788d262) --- src/renderer/src/aiCore/utils/reasoning.ts | 9 +- .../src/config/__test__/reasoning.test.ts | 166 ++++++++++++++++++ src/renderer/src/config/models/reasoning.ts | 13 +- src/renderer/src/types/index.ts | 1 + src/renderer/src/types/sdk.ts | 1 + 5 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 src/renderer/src/config/__test__/reasoning.test.ts 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