fix(models): qwen-mt-flash supports text delta (#11448)

refactor(models): improve text delta support check for qwen-mt models

Replace direct qwen-mt model check with regex pattern matching
Add comprehensive test cases for isNotSupportTextDeltaModel
Update all references to use new function name
This commit is contained in:
Phantom 2025-11-25 22:22:18 +08:00 committed by GitHub
parent fd3b7f717d
commit 69d31a1e2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 58 additions and 16 deletions

View File

@ -24,9 +24,9 @@ import {
isGemmaModel,
isGenerateImageModels,
isMaxTemperatureOneModel,
isNotSupportedTextDelta,
isNotSupportSystemMessageModel,
isNotSupportTemperatureAndTopP,
isNotSupportTextDeltaModel,
isSupportedFlexServiceTier,
isSupportedModel,
isSupportFlexServiceTierModel,
@ -215,12 +215,51 @@ describe('model utils', () => {
it('aggregates boolean helpers based on regex rules', () => {
expect(isAnthropicModel(createModel({ id: 'claude-3.5' }))).toBe(true)
expect(isQwenMTModel(createModel({ id: 'qwen-mt-large' }))).toBe(true)
expect(isNotSupportedTextDelta(createModel({ id: 'qwen-mt-large' }))).toBe(true)
expect(isQwenMTModel(createModel({ id: 'qwen-mt-plus' }))).toBe(true)
expect(isNotSupportSystemMessageModel(createModel({ id: 'gemma-moe' }))).toBe(true)
expect(isOpenAIOpenWeightModel(createModel({ id: 'gpt-oss-free' }))).toBe(true)
})
describe('isNotSupportedTextDelta', () => {
it('returns true for qwen-mt-turbo and qwen-mt-plus models', () => {
// qwen-mt series that don't support text delta
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-mt-turbo' }))).toBe(true)
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-mt-plus' }))).toBe(true)
expect(isNotSupportTextDeltaModel(createModel({ id: 'Qwen-MT-Turbo' }))).toBe(true)
expect(isNotSupportTextDeltaModel(createModel({ id: 'QWEN-MT-PLUS' }))).toBe(true)
})
it('returns false for qwen-mt-flash and other models', () => {
// qwen-mt-flash supports text delta
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-mt-flash' }))).toBe(false)
expect(isNotSupportTextDeltaModel(createModel({ id: 'Qwen-MT-Flash' }))).toBe(false)
// Legacy qwen models without mt prefix (support text delta)
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-turbo' }))).toBe(false)
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-plus' }))).toBe(false)
// Other qwen models
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-max' }))).toBe(false)
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen2.5-72b' }))).toBe(false)
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-vl-plus' }))).toBe(false)
// Non-qwen models
expect(isNotSupportTextDeltaModel(createModel({ id: 'gpt-4o' }))).toBe(false)
expect(isNotSupportTextDeltaModel(createModel({ id: 'claude-3.5' }))).toBe(false)
expect(isNotSupportTextDeltaModel(createModel({ id: 'glm-4-plus' }))).toBe(false)
})
it('handles models with version suffixes', () => {
// qwen-mt models with version suffixes
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-mt-turbo-1201' }))).toBe(true)
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-mt-plus-0828' }))).toBe(true)
// Legacy qwen models with version suffixes (support text delta)
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-turbo-0828' }))).toBe(false)
expect(isNotSupportTextDeltaModel(createModel({ id: 'qwen-plus-latest' }))).toBe(false)
})
})
it('evaluates GPT-5 family helpers', () => {
expect(isGPT5SeriesModel(createModel({ id: 'gpt-5-preview' }))).toBe(true)
expect(isGPT5SeriesModel(createModel({ id: 'gpt-5.1-preview' }))).toBe(false)

View File

@ -111,8 +111,11 @@ export const isAnthropicModel = (model?: Model): boolean => {
return modelId.startsWith('claude')
}
export const isNotSupportedTextDelta = (model: Model): boolean => {
return isQwenMTModel(model)
const NOT_SUPPORT_TEXT_DELTA_MODEL_REGEX = new RegExp('qwen-mt-(?:turbo|plus)')
export const isNotSupportTextDeltaModel = (model: Model): boolean => {
const modelId = getLowerBaseModelName(model.id)
return NOT_SUPPORT_TEXT_DELTA_MODEL_REGEX.test(modelId)
}
export const isNotSupportSystemMessageModel = (model: Model): boolean => {

View File

@ -1,5 +1,5 @@
import { TopView } from '@renderer/components/TopView'
import { isNotSupportedTextDelta } from '@renderer/config/models'
import { isNotSupportTextDeltaModel } from '@renderer/config/models'
import { useProvider } from '@renderer/hooks/useProvider'
import type { Model, Provider } from '@renderer/types'
import { getDefaultGroupName } from '@renderer/utils'
@ -58,7 +58,7 @@ const PopupContainer: React.FC<Props> = ({ title, provider, resolve }) => {
group: values.group ?? getDefaultGroupName(id)
}
addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) })
addModel({ ...model, supported_text_delta: !isNotSupportTextDeltaModel(model) })
return true
}

View File

@ -6,7 +6,7 @@ import {
groupQwenModels,
isEmbeddingModel,
isFunctionCallingModel,
isNotSupportedTextDelta,
isNotSupportTextDeltaModel,
isReasoningModel,
isRerankModel,
isVisionModel,
@ -136,13 +136,13 @@ const PopupContainer: React.FC<Props> = ({ providerId, resolve }) => {
addModel({
...model,
endpoint_type: endpointTypes.includes('image-generation') ? 'image-generation' : endpointTypes[0],
supported_text_delta: !isNotSupportedTextDelta(model)
supported_text_delta: !isNotSupportTextDeltaModel(model)
})
} else {
NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider, model })
}
} else {
addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) })
addModel({ ...model, supported_text_delta: !isNotSupportTextDeltaModel(model) })
}
}
},

View File

@ -1,6 +1,6 @@
import { TopView } from '@renderer/components/TopView'
import { endpointTypeOptions } from '@renderer/config/endpointTypes'
import { isNotSupportedTextDelta } from '@renderer/config/models'
import { isNotSupportTextDeltaModel } from '@renderer/config/models'
import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth'
import { useProvider } from '@renderer/hooks/useProvider'
import type { EndpointType, Model, Provider } from '@renderer/types'
@ -65,7 +65,7 @@ const PopupContainer: React.FC<Props> = ({ title, provider, resolve, model, endp
endpoint_type: isNewApiProvider(provider) ? values.endpointType : undefined
}
addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) })
addModel({ ...model, supported_text_delta: !isNotSupportTextDeltaModel(model) })
return true
}

View File

@ -1,6 +1,6 @@
import { TopView } from '@renderer/components/TopView'
import { endpointTypeOptions } from '@renderer/config/endpointTypes'
import { isNotSupportedTextDelta } from '@renderer/config/models'
import { isNotSupportTextDeltaModel } from '@renderer/config/models'
import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth'
import { useProvider } from '@renderer/hooks/useProvider'
import type { EndpointType, Model, Provider } from '@renderer/types'
@ -48,7 +48,7 @@ const PopupContainer: React.FC<Props> = ({ title, provider, resolve, batchModels
addModel({
...model,
endpoint_type: values.endpointType,
supported_text_delta: !isNotSupportedTextDelta(model)
supported_text_delta: !isNotSupportTextDeltaModel(model)
})
})
return true

View File

@ -5,7 +5,7 @@ import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import {
glm45FlashModel,
isFunctionCallingModel,
isNotSupportedTextDelta,
isNotSupportTextDeltaModel,
SYSTEM_MODELS
} from '@renderer/config/models'
import { BUILTIN_OCR_PROVIDERS, BUILTIN_OCR_PROVIDERS_MAP, DEFAULT_OCR_PROVIDER } from '@renderer/config/ocr'
@ -1988,7 +1988,7 @@ const migrateConfig = {
const updateModelTextDelta = (model?: Model) => {
if (model) {
model.supported_text_delta = true
if (isNotSupportedTextDelta(model)) {
if (isNotSupportTextDeltaModel(model)) {
model.supported_text_delta = false
}
}