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 06b1ae0cb8)
This commit is contained in:
Phantom 2025-10-19 19:02:16 +08:00 committed by dev
parent 0d86e16e28
commit ab78ef71d9
5 changed files with 187 additions and 3 deletions

View File

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

View File

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

View File

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

View File

@ -85,6 +85,7 @@ const ThinkModelTypes = [
'qwen_thinking',
'doubao',
'doubao_no_auto',
'doubao_after_251015',
'hunyuan',
'zhipu',
'perplexity',

View File

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