From 65257eb3d5dc4b68f9a11a66125f20424078e7b3 Mon Sep 17 00:00:00 2001 From: SuYao Date: Sat, 26 Jul 2025 17:33:46 +0800 Subject: [PATCH] Fix/qwen-mt (#8527) * feat(ModelList): add support for 'supported_text_delta' in model configuration - Introduced a new boolean property 'supported_text_delta' to the model configuration, allowing models to indicate support for text delta outputs. - Updated the AddModelPopup and ModelEditContent components to handle this new property, including UI elements for user interaction. - Enhanced migration logic to set default values for existing models based on compatibility checks. - Added corresponding translations for the new property in the i18n files. * feat(OpenAIApiClient): enhance support for Qwen MT model and system message handling - Added support for Qwen MT model in OpenAIApiClient, including translation options based on target language. - Updated system message handling to accommodate models that do not support system messages. - Introduced utility functions to identify Qwen MT models and their compatibility with text delta outputs. - Enhanced TextChunkMiddleware to handle text accumulation based on model capabilities. - Updated model configuration to include Qwen MT in the list of excluded models for function calling. * feat(i18n): add translations for 'supported_text_delta' in multiple languages - Introduced new translation entries for the 'supported_text_delta' property in English, Japanese, Russian, and Traditional Chinese localization files. - Updated the corresponding labels and tooltips to enhance user experience across different languages. * refactor(ModelEditContent): reposition 'supported_text_delta' input for improved UI layout - Moved the 'supported_text_delta' Form.Item from its previous location to enhance the user interface and maintain consistency in the model editing experience. - Ensured that the switch input for 'supported_text_delta' is now displayed in a more logical order within the form. * fix(TextChunkMiddleware): update condition for supported_text_delta check - Changed the condition in TextChunkMiddleware to explicitly check for 'supported_text_delta' being false, improving clarity in the logic. - Updated test cases to reflect the new structure of model configurations, ensuring 'supported_text_delta' is consistently set to true for relevant models. * feat(migrate): add support for 'supported_text_delta' in assistant models - Updated migration logic to set 'supported_text_delta' for both default and specific models within assistants. - Implemented checks to ensure compatibility with text delta outputs, enhancing model configuration consistency. * feat(ModelList): add 'supported_text_delta' to model addition logic - Enhanced the model addition process in EditModelsPopup, NewApiAddModelPopup, and NewApiBatchAddModelPopup to include the 'supported_text_delta' property. - Ensured consistency across components by setting 'supported_text_delta' to true when adding models, improving compatibility with text delta outputs. * feat(migrate): streamline model text delta support in migration logic - Refactored migration logic to utilize a new helper function for updating the 'supported_text_delta' property across various model types, enhancing code clarity and reducing redundancy. - Ensured that all relevant models, including those in assistants and LLM providers, are correctly configured for text delta compatibility during migration. * feat(OpenAIApiClient): integrate language mapping for Qwen MT model translations - Updated the OpenAIApiClient to utilize a new utility function for mapping target languages to Qwen MT model names, enhancing translation accuracy. - Refactored migration logic to ensure default tool use mode is set for assistants lacking this configuration, improving user experience. - Added a new utility function for language mapping in the utils module, supporting better integration with translation features. * feat(ModelList): update model addition logic to determine 'supported_text_delta' - Integrated a new utility function to assess model compatibility with text delta outputs during the model addition process in AddModelPopup. - Simplified the logic for setting the 'supported_text_delta' property, enhancing clarity and ensuring accurate model configuration. * feat(ModelList): unify model addition logic for 'supported_text_delta' - Refactored model addition across AddModelPopup, EditModelsPopup, NewApiAddModelPopup, and NewApiBatchAddModelPopup to consistently determine 'supported_text_delta' using a utility function. - Simplified the logic for setting 'supported_text_delta', enhancing clarity and ensuring accurate model configuration across components. --- .../aiCore/clients/openai/OpenAIApiClient.ts | 19 +++++++++-- .../middleware/core/TextChunkMiddleware.ts | 7 ++-- .../components/ModelList/AddModelPopup.tsx | 3 +- .../components/ModelList/EditModelsPopup.tsx | 6 ++-- .../components/ModelList/ModelEditContent.tsx | 10 +++++- .../ModelList/NewApiAddModelPopup.tsx | 3 +- .../ModelList/NewApiBatchAddModelPopup.tsx | 4 ++- src/renderer/src/config/models.ts | 24 +++++++++++++- src/renderer/src/i18n/locales/en-us.json | 4 +++ src/renderer/src/i18n/locales/ja-jp.json | 4 +++ src/renderer/src/i18n/locales/ru-ru.json | 4 +++ src/renderer/src/i18n/locales/zh-cn.json | 4 +++ src/renderer/src/i18n/locales/zh-tw.json | 4 +++ src/renderer/src/services/ApiService.ts | 3 +- src/renderer/src/services/AssistantService.ts | 15 +++++++-- .../src/services/__tests__/ApiService.test.ts | 30 +++++++++++------ src/renderer/src/store/migrate.ts | 33 +++++++++++++++++-- src/renderer/src/types/index.ts | 5 +++ src/renderer/src/utils/index.ts | 12 ++++++- .../action/components/ActionTranslate.tsx | 25 +++----------- 20 files changed, 171 insertions(+), 48 deletions(-) diff --git a/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts b/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts index 1a19724c9f..eeaa1c7bad 100644 --- a/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts +++ b/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts @@ -6,6 +6,8 @@ import { getOpenAIWebSearchParams, isDoubaoThinkingAutoModel, isGrokReasoningModel, + isNotSupportSystemMessageModel, + isQwenMTModel, isQwenReasoningModel, isReasoningModel, isSupportedReasoningEffortGrokModel, @@ -32,6 +34,7 @@ import { Model, Provider, ToolCallResponse, + TranslateAssistant, WebSearchSource } from '@renderer/types' import { ChunkType, TextStartChunk, ThinkingStartChunk } from '@renderer/types/chunk' @@ -44,6 +47,7 @@ import { OpenAISdkRawOutput, ReasoningEffortOptionalParams } from '@renderer/types/sdk' +import { mapLanguageToQwenMTModel } from '@renderer/utils' import { addImageFileToContents } from '@renderer/utils/formats' import { isEnabledToolUse, @@ -472,6 +476,16 @@ export class OpenAIAPIClient extends OpenAIBaseClient< streamOutput = true } + const extra_body: Record = {} + + if (isQwenMTModel(model)) { + const targetLanguage = (assistant as TranslateAssistant).targetLanguage + extra_body.translation_options = { + source_lang: 'auto', + target_lang: mapLanguageToQwenMTModel(targetLanguage!) + } + } + // 1. 处理系统消息 let systemMessage = { role: 'system', content: assistant.prompt || '' } @@ -515,7 +529,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient< // 4. 最终请求消息 let reqMessages: OpenAISdkMessageParam[] - if (!systemMessage.content) { + if (!systemMessage.content || isNotSupportSystemMessageModel(model)) { reqMessages = [...userMessages] } else { reqMessages = [systemMessage, ...userMessages].filter(Boolean) as OpenAISdkMessageParam[] @@ -541,7 +555,8 @@ export class OpenAIAPIClient extends OpenAIBaseClient< // 只在对话场景下应用自定义参数,避免影响翻译、总结等其他业务逻辑 ...(coreRequest.callType === 'chat' ? this.getCustomParameters(assistant) : {}), // OpenRouter usage tracking - ...(this.provider.id === 'openrouter' ? { usage: { include: true } } : {}) + ...(this.provider.id === 'openrouter' ? { usage: { include: true } } : {}), + ...(isQwenMTModel(model) ? extra_body : {}) } // Create the appropriate parameters object based on whether streaming is enabled diff --git a/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts b/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts index 82e8a7cdaa..41157bf504 100644 --- a/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts @@ -45,8 +45,11 @@ export const TextChunkMiddleware: CompletionsMiddleware = transform(chunk: GenericChunk, controller) { logger.silly('chunk', chunk) if (chunk.type === ChunkType.TEXT_DELTA) { - accumulatedTextContent += chunk.text - + if (model.supported_text_delta === false) { + accumulatedTextContent = chunk.text + } else { + accumulatedTextContent += chunk.text + } // 处理 onResponse 回调 - 发送增量文本更新 if (params.onResponse) { params.onResponse(accumulatedTextContent, false) diff --git a/src/renderer/src/components/ModelList/AddModelPopup.tsx b/src/renderer/src/components/ModelList/AddModelPopup.tsx index d11db2f4cd..0d8932aba3 100644 --- a/src/renderer/src/components/ModelList/AddModelPopup.tsx +++ b/src/renderer/src/components/ModelList/AddModelPopup.tsx @@ -1,4 +1,5 @@ import { TopView } from '@renderer/components/TopView' +import { isNotSupportedTextDelta } from '@renderer/config/models' import { useProvider } from '@renderer/hooks/useProvider' import { Model, Provider } from '@renderer/types' import { getDefaultGroupName } from '@renderer/utils' @@ -56,7 +57,7 @@ const PopupContainer: React.FC = ({ title, provider, resolve }) => { group: values.group ?? getDefaultGroupName(id) } - addModel(model) + addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) }) return true } diff --git a/src/renderer/src/components/ModelList/EditModelsPopup.tsx b/src/renderer/src/components/ModelList/EditModelsPopup.tsx index 00a6ad479f..01c3ea3a70 100644 --- a/src/renderer/src/components/ModelList/EditModelsPopup.tsx +++ b/src/renderer/src/components/ModelList/EditModelsPopup.tsx @@ -13,6 +13,7 @@ import { groupQwenModels, isEmbeddingModel, isFunctionCallingModel, + isNotSupportedTextDelta, isReasoningModel, isRerankModel, isVisionModel, @@ -144,13 +145,14 @@ const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { if (model.supported_endpoint_types && model.supported_endpoint_types.length > 0) { addModel({ ...model, - endpoint_type: model.supported_endpoint_types[0] + endpoint_type: model.supported_endpoint_types[0], + supported_text_delta: !isNotSupportedTextDelta(model) }) } else { NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider, model }) } } else { - addModel(model) + addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) }) } } }, diff --git a/src/renderer/src/components/ModelList/ModelEditContent.tsx b/src/renderer/src/components/ModelList/ModelEditContent.tsx index eb1d198fab..f4d8811309 100644 --- a/src/renderer/src/components/ModelList/ModelEditContent.tsx +++ b/src/renderer/src/components/ModelList/ModelEditContent.tsx @@ -11,7 +11,7 @@ import { import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth' import { Model, ModelCapability, ModelType, Provider } from '@renderer/types' import { getDefaultGroupName, getDifference, getUnion } from '@renderer/utils' -import { Button, Checkbox, Divider, Flex, Form, Input, InputNumber, message, Modal, Select } from 'antd' +import { Button, Checkbox, Divider, Flex, Form, Input, InputNumber, message, Modal, Select, Switch } from 'antd' import { ChevronDown, ChevronUp } from 'lucide-react' import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -33,6 +33,7 @@ const ModelEditContent: FC = ({ provider, model, onUpdate const [currencySymbol, setCurrencySymbol] = useState(model.pricing?.currencySymbol || '$') const [isCustomCurrency, setIsCustomCurrency] = useState(!symbols.includes(model.pricing?.currencySymbol || '$')) const [modelCapabilities, setModelCapabilities] = useState(model.capabilities || []) + const [supportedTextDelta, setSupportedTextDelta] = useState(model.supported_text_delta) const labelWidth = useDynamicLabelWidth([t('settings.models.add.endpoint_type.label')]) @@ -45,6 +46,7 @@ const ModelEditContent: FC = ({ provider, model, onUpdate group: values.group || model.group, endpoint_type: provider.id === 'new-api' ? values.endpointType : model.endpoint_type, capabilities: modelCapabilities, + supported_text_delta: supportedTextDelta, pricing: { input_per_million_tokens: Number(values.input_per_million_tokens) || 0, output_per_million_tokens: Number(values.output_per_million_tokens) || 0, @@ -338,6 +340,12 @@ const ModelEditContent: FC = ({ provider, model, onUpdate ) })()} + + setSupportedTextDelta(checked)} /> + {t('models.price.price')}