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, isGemmaModel,
isGenerateImageModels, isGenerateImageModels,
isMaxTemperatureOneModel, isMaxTemperatureOneModel,
isNotSupportedTextDelta,
isNotSupportSystemMessageModel, isNotSupportSystemMessageModel,
isNotSupportTemperatureAndTopP, isNotSupportTemperatureAndTopP,
isNotSupportTextDeltaModel,
isSupportedFlexServiceTier, isSupportedFlexServiceTier,
isSupportedModel, isSupportedModel,
isSupportFlexServiceTierModel, isSupportFlexServiceTierModel,
@ -215,12 +215,51 @@ describe('model utils', () => {
it('aggregates boolean helpers based on regex rules', () => { it('aggregates boolean helpers based on regex rules', () => {
expect(isAnthropicModel(createModel({ id: 'claude-3.5' }))).toBe(true) expect(isAnthropicModel(createModel({ id: 'claude-3.5' }))).toBe(true)
expect(isQwenMTModel(createModel({ id: 'qwen-mt-large' }))).toBe(true) expect(isQwenMTModel(createModel({ id: 'qwen-mt-plus' }))).toBe(true)
expect(isNotSupportedTextDelta(createModel({ id: 'qwen-mt-large' }))).toBe(true)
expect(isNotSupportSystemMessageModel(createModel({ id: 'gemma-moe' }))).toBe(true) expect(isNotSupportSystemMessageModel(createModel({ id: 'gemma-moe' }))).toBe(true)
expect(isOpenAIOpenWeightModel(createModel({ id: 'gpt-oss-free' }))).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', () => { it('evaluates GPT-5 family helpers', () => {
expect(isGPT5SeriesModel(createModel({ id: 'gpt-5-preview' }))).toBe(true) expect(isGPT5SeriesModel(createModel({ id: 'gpt-5-preview' }))).toBe(true)
expect(isGPT5SeriesModel(createModel({ id: 'gpt-5.1-preview' }))).toBe(false) 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') return modelId.startsWith('claude')
} }
export const isNotSupportedTextDelta = (model: Model): boolean => { const NOT_SUPPORT_TEXT_DELTA_MODEL_REGEX = new RegExp('qwen-mt-(?:turbo|plus)')
return isQwenMTModel(model)
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 => { export const isNotSupportSystemMessageModel = (model: Model): boolean => {

View File

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

View File

@ -6,7 +6,7 @@ import {
groupQwenModels, groupQwenModels,
isEmbeddingModel, isEmbeddingModel,
isFunctionCallingModel, isFunctionCallingModel,
isNotSupportedTextDelta, isNotSupportTextDeltaModel,
isReasoningModel, isReasoningModel,
isRerankModel, isRerankModel,
isVisionModel, isVisionModel,
@ -136,13 +136,13 @@ const PopupContainer: React.FC<Props> = ({ providerId, resolve }) => {
addModel({ addModel({
...model, ...model,
endpoint_type: endpointTypes.includes('image-generation') ? 'image-generation' : endpointTypes[0], endpoint_type: endpointTypes.includes('image-generation') ? 'image-generation' : endpointTypes[0],
supported_text_delta: !isNotSupportedTextDelta(model) supported_text_delta: !isNotSupportTextDeltaModel(model)
}) })
} else { } else {
NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider, model }) NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider, model })
} }
} else { } 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 { TopView } from '@renderer/components/TopView'
import { endpointTypeOptions } from '@renderer/config/endpointTypes' 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 { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth'
import { useProvider } from '@renderer/hooks/useProvider' import { useProvider } from '@renderer/hooks/useProvider'
import type { EndpointType, Model, Provider } from '@renderer/types' 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 endpoint_type: isNewApiProvider(provider) ? values.endpointType : undefined
} }
addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) }) addModel({ ...model, supported_text_delta: !isNotSupportTextDeltaModel(model) })
return true return true
} }

View File

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

View File

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