From 60cc1dee9688ac7d2ee8e1b3364a66c96a43353f Mon Sep 17 00:00:00 2001 From: jwcrystal <121911854+jwcrystal@users.noreply.github.com> Date: Fri, 9 May 2025 22:21:04 +0800 Subject: [PATCH] fix(Qwen3): Add Qwen3 Model Thinking Mode Switch in Thinking Button(#5781) * feat(Qwen3): Add Qwen3 Model Thinking Mode Switch - Add a thinking mode switch for the Qwen3 model on the settings page, - Enabled: Generates thinking content. Disabled: Does not generate thinking content. This feature is implemented by adding new settings items and corresponding logic. * docs(i18n): Add multilingual translations for Qwen3 model's thinking mode * refactor(Qwen3): Remove Qwen thinking mode related code from SettingTab - Remove Qwen thinking mode related state and logic from the SettingsTab component - Integarte Qwen 3 thinking mode switch login the ThinkingButton component to simplify the code and improve maintainability * refactor(OpenAICompatibleProvider): Extract qwen3 handling logic to ModelMessageService Move the postsuffix handling logic in OpenAICompatibleProvider to ModelMessageService to improve code maintainability and reusability * docs(i18n): Remove Qwen3 model-related translations Remove the translation content of the unused Qwen3 model's thinking mode and its prompts --- .../pages/home/Inputbar/ThinkingButton.tsx | 22 +++++-- .../AiProvider/OpenAICompatibleProvider.ts | 16 ++++- .../src/services/ModelMessageService.ts | 62 ++++++++++++++++++- src/renderer/src/types/index.ts | 1 + 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx b/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx index 623b4a1de2..702c1d2823 100644 --- a/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx @@ -6,7 +6,11 @@ import { MdiLightbulbOn90 } from '@renderer/components/Icons/SVGIcon' import { useQuickPanel } from '@renderer/components/QuickPanel' -import { isSupportedReasoningEffortGrokModel, isSupportedThinkingTokenGeminiModel } from '@renderer/config/models' +import { + isSupportedReasoningEffortGrokModel, + isSupportedThinkingTokenGeminiModel, + isSupportedThinkingTokenQwenModel +} from '@renderer/config/models' import { useAssistant } from '@renderer/hooks/useAssistant' import { Assistant, Model, ReasoningEffortOptions } from '@renderer/types' import { Tooltip } from 'antd' @@ -30,7 +34,8 @@ interface Props { const MODEL_SUPPORTED_OPTIONS: Record = { default: ['off', 'low', 'medium', 'high'], grok: ['off', 'low', 'high'], - gemini: ['off', 'low', 'medium', 'high', 'auto'] + gemini: ['off', 'low', 'medium', 'high', 'auto'], + qwen: ['off', 'low', 'medium', 'high', 'auto'] } // 选项转换映射表:当选项不支持时使用的替代选项 @@ -49,6 +54,7 @@ const ThinkingButton: FC = ({ ref, model, assistant, ToolbarButton }): Re const isGrokModel = isSupportedReasoningEffortGrokModel(model) const isGeminiModel = isSupportedThinkingTokenGeminiModel(model) + const isQwenModel = isSupportedThinkingTokenQwenModel(model) const currentReasoningEffort = useMemo(() => { return assistant.settings?.reasoning_effort || 'off' @@ -58,8 +64,9 @@ const ThinkingButton: FC = ({ ref, model, assistant, ToolbarButton }): Re const modelType = useMemo(() => { if (isGeminiModel) return 'gemini' if (isGrokModel) return 'grok' + if (isQwenModel) return 'qwen' return 'default' - }, [isGeminiModel, isGrokModel]) + }, [isGeminiModel, isGrokModel, isQwenModel]) // 获取当前模型支持的选项 const supportedOptions = useMemo(() => { @@ -73,7 +80,8 @@ const ThinkingButton: FC = ({ ref, model, assistant, ToolbarButton }): Re const fallbackOption = OPTION_FALLBACK[currentReasoningEffort as ThinkingOption] updateAssistantSettings({ - reasoning_effort: fallbackOption === 'off' ? undefined : fallbackOption + reasoning_effort: fallbackOption === 'off' ? undefined : fallbackOption, + qwenThinkMode: fallbackOption === 'off' }) } }, [currentReasoningEffort, supportedOptions, updateAssistantSettings, model.id]) @@ -103,12 +111,14 @@ const ThinkingButton: FC = ({ ref, model, assistant, ToolbarButton }): Re // 然后更新设置 if (!isEnabled) { updateAssistantSettings({ - reasoning_effort: undefined + reasoning_effort: undefined, + qwenThinkMode: false }) return } updateAssistantSettings({ - reasoning_effort: option + reasoning_effort: option, + qwenThinkMode: true }) return }, diff --git a/src/renderer/src/providers/AiProvider/OpenAICompatibleProvider.ts b/src/renderer/src/providers/AiProvider/OpenAICompatibleProvider.ts index 444612eab0..eb1809797f 100644 --- a/src/renderer/src/providers/AiProvider/OpenAICompatibleProvider.ts +++ b/src/renderer/src/providers/AiProvider/OpenAICompatibleProvider.ts @@ -25,7 +25,7 @@ import { filterEmptyMessages, filterUserRoleStartMessages } from '@renderer/services/MessagesService' -import { processReqMessages } from '@renderer/services/ModelMessageService' +import { processPostsuffixQwen3Model, processReqMessages } from '@renderer/services/ModelMessageService' import store from '@renderer/store' import { Assistant, @@ -401,6 +401,20 @@ export default class OpenAICompatibleProvider extends BaseOpenAiProvider { const { signal } = abortController await this.checkIsCopilot() + const lastUserMsg = userMessages.findLast((m) => m.role === 'user') + if (lastUserMsg) { + const postsuffix = '/no_think' + // qwenThinkMode === true 表示思考模式啓用,此時不應添加 /no_think,如果存在則移除 + const qwenThinkModeEnabled = assistant.settings?.qwenThinkMode === true + const currentContent = lastUserMsg.content // content 類型:string | ChatCompletionContentPart[] | null + + lastUserMsg.content = processPostsuffixQwen3Model( + currentContent, + postsuffix, + qwenThinkModeEnabled + ) as ChatCompletionContentPart[] + } + //当 systemMessage 内容为空时不发送 systemMessage let reqMessages: ChatCompletionMessageParam[] if (!systemMessage.content) { diff --git a/src/renderer/src/services/ModelMessageService.ts b/src/renderer/src/services/ModelMessageService.ts index 0ef3001f20..4e9c1d5729 100644 --- a/src/renderer/src/services/ModelMessageService.ts +++ b/src/renderer/src/services/ModelMessageService.ts @@ -1,5 +1,5 @@ import { Model } from '@renderer/types' -import { ChatCompletionMessageParam } from 'openai/resources' +import { ChatCompletionContentPart, ChatCompletionContentPartText, ChatCompletionMessageParam } from 'openai/resources' export function processReqMessages( model: Model, @@ -40,3 +40,63 @@ function interleaveUserAndAssistantMessages(messages: ChatCompletionMessageParam return processedMessages } + +// Process postsuffix for Qwen3 model +export function processPostsuffixQwen3Model( + // content 類型:string | ChatCompletionContentPart[] | null + content: string | ChatCompletionContentPart[] | null, + postsuffix: string, + qwenThinkModeEnabled: boolean +): string | ChatCompletionContentPart[] | null { + if (typeof content === 'string') { + if (qwenThinkModeEnabled) { + // 思考模式启用,移除 postsuffix + if (content.endsWith(postsuffix)) { + return content.substring(0, content.length - postsuffix.length).trimEnd() + } + } else { + // 思考模式未启用,添加 postsuffix + if (!content.endsWith(postsuffix)) { + return content + postsuffix + } + } + } else if (Array.isArray(content)) { + let lastTextPartIndex = -1 + for (let i = content.length - 1; i >= 0; i--) { + if (content[i].type === 'text') { + lastTextPartIndex = i + break + } + } + + if (lastTextPartIndex !== -1) { + const textPart = content[lastTextPartIndex] as ChatCompletionContentPartText + if (qwenThinkModeEnabled) { + // 思考模式启用,移除 postsuffix + if (textPart.text.endsWith(postsuffix)) { + textPart.text = textPart.text.substring(0, textPart.text.length - postsuffix.length).trimEnd() + // 可選:如果 textPart.text 變為空,可以考慮是否移除該 part + } + } else { + // 思考模式未启用,添加 postsuffix + if (!textPart.text.endsWith(postsuffix)) { + textPart.text += postsuffix + } + } + } else { + // 數組中沒有文本部分 + if (!qwenThinkModeEnabled) { + // 思考模式未啓用,需要添加 postsuffix + // 如果沒有文本部分,則添加一個新的文本部分 + content.push({ type: 'text', text: postsuffix }) + } + } + } else { + // currentContent 是 null + if (!qwenThinkModeEnabled) { + // 思考模式未启用,需要添加 postsuffix + return content + } + } + return content +} diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 95b3756980..db716beb3d 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -60,6 +60,7 @@ export type AssistantSettings = { defaultModel?: Model customParameters?: AssistantSettingCustomParameters[] reasoning_effort?: ReasoningEffortOptions + qwenThinkMode?: boolean } export type Agent = Omit & {