Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/main/presenter/configPresenter/modelConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ export class ModelConfigHelper {
reasoning: config.reasoning || false,
type: config.type || ModelType.Chat,
thinkingBudget: config.thinkingBudget,
enableSearch: config.enableSearch || false,
forcedSearch: config.forcedSearch || false,
searchStrategy: config.searchStrategy || 'turbo',
reasoningEffort: config.reasoningEffort,
verbosity: config.verbosity,
maxCompletionTokens: config.maxCompletionTokens
Expand All @@ -164,6 +167,9 @@ export class ModelConfigHelper {
reasoning: false,
type: ModelType.Chat,
thinkingBudget: undefined,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo',
reasoningEffort: undefined,
verbosity: undefined,
maxCompletionTokens: undefined
Expand Down
140 changes: 129 additions & 11 deletions src/main/presenter/configPresenter/providerModelSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export interface ProviderModelSetting {
verbosity?: 'low' | 'medium' | 'high'
maxCompletionTokens?: number // GPT-5 系列使用此参数替代 maxTokens
thinkingBudget?: number // 思维预算参数
enableSearch?: boolean // 是否支持联网搜索
forcedSearch?: boolean // 是否强制联网搜索
searchStrategy?: 'turbo' | 'max' // 搜索策略
}

// 为每个提供商创建映射对象,使用models数组包装模型配置
Expand Down Expand Up @@ -2219,7 +2222,10 @@ export const providerModelSettings: Record<string, { models: ProviderModelSettin
match: ['qwen-turbo-latest'],
vision: false,
functionCall: true,
reasoning: false
reasoning: false,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo'
},
{
id: 'qwen-turbo-2024-11-01',
Expand Down Expand Up @@ -2276,27 +2282,75 @@ export const providerModelSettings: Record<string, { models: ProviderModelSettin
functionCall: true,
reasoning: false
},
{
id: 'qwen-turbo-2025-07-15',
name: 'Qwen Turbo 2025 07 15',
temperature: 0.7,
contextLength: 131072,
maxTokens: 16384,
match: ['qwen-turbo-2025-07-15'],
vision: false,
functionCall: true,
reasoning: false,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo'
},
{
id: 'qwen-turbo',
name: 'Qwen Turbo',
temperature: 0.7,
contextLength: 1000000,
maxTokens: 8192,
contextLength: 131072,
maxTokens: 16384,
match: ['qwen-turbo'],
vision: false,
functionCall: true,
reasoning: false
reasoning: false,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo'
},
{
id: 'qwen-flash',
name: 'Qwen Flash',
temperature: 0.7,
contextLength: 1000000,
maxTokens: 32768,
match: ['qwen-flash'],
vision: false,
functionCall: true,
reasoning: false,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo'
},
{
id: 'qwen-flash-2025-07-28',
name: 'Qwen Flash 2025 07 28',
temperature: 0.7,
contextLength: 1000000,
maxTokens: 32768,
match: ['qwen-flash-2025-07-28'],
vision: false,
functionCall: true,
reasoning: false,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo'
},
{
id: 'qwen-plus-latest',
name: 'Qwen Plus Latest',
temperature: 0.7,
contextLength: 131072,
maxTokens: 16384,
contextLength: 1000000,
maxTokens: 32768,
match: ['qwen-plus-latest'],
vision: false,
functionCall: true,
reasoning: false
reasoning: false,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo'
},
{
id: 'qwen-plus-2024-12-20',
Expand Down Expand Up @@ -2397,16 +2451,47 @@ export const providerModelSettings: Record<string, { models: ProviderModelSettin
functionCall: true,
reasoning: false
},
{
id: 'qwen-plus-2025-07-14',
name: 'Qwen Plus 2025 07 14',
temperature: 0.7,
contextLength: 131072,
maxTokens: 8192,
match: ['qwen-plus-2025-07-14'],
vision: false,
functionCall: true,
reasoning: false,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo'
},
{
id: 'qwen-plus-2025-07-28',
name: 'Qwen Plus 2025 07 28',
temperature: 0.7,
contextLength: 1000000,
maxTokens: 32768,
match: ['qwen-plus-2025-07-28'],
vision: false,
functionCall: true,
reasoning: false,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo'
},
{
id: 'qwen-plus',
name: 'Qwen Plus',
temperature: 0.7,
contextLength: 131072,
maxTokens: 8192,
maxTokens: 16384,
match: ['qwen-plus'],
vision: false,
functionCall: true,
reasoning: false
reasoning: false,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo'
},
{
id: 'qwen-max-latest',
Expand Down Expand Up @@ -2472,7 +2557,10 @@ export const providerModelSettings: Record<string, { models: ProviderModelSettin
match: ['qwen-max'],
vision: false,
functionCall: true,
reasoning: false
reasoning: false,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo'
},
// Qwen3系列模型
{
Expand Down Expand Up @@ -2594,6 +2682,33 @@ export const providerModelSettings: Record<string, { models: ProviderModelSettin
functionCall: true,
reasoning: true,
thinkingBudget: 20000
},
// QwQ 系列模型
{
id: 'qwq-plus',
name: 'QwQ Plus',
temperature: 0.7,
maxTokens: 8192,
contextLength: 131072,
match: ['qwq-plus'],
vision: false,
functionCall: false,
reasoning: true,
enableSearch: false,
forcedSearch: false,
searchStrategy: 'turbo'
},
{
id: 'qwq-plus-latest',
name: 'QwQ Plus Latest',
temperature: 0.7,
maxTokens: 8192,
contextLength: 131072,
match: ['qwq-plus-latest'],
vision: false,
functionCall: false,
reasoning: true,
enableSearch: false
}
]
},
Expand Down Expand Up @@ -2968,7 +3083,10 @@ export function getProviderSpecificModelConfig(
reasoningEffort: config.reasoningEffort,
verbosity: config.verbosity,
maxCompletionTokens: config.maxCompletionTokens,
thinkingBudget: config.thinkingBudget
thinkingBudget: config.thinkingBudget,
enableSearch: config.enableSearch || false,
forcedSearch: config.forcedSearch || false,
searchStrategy: config.searchStrategy || 'turbo'
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { OpenAICompatibleProvider } from './openAICompatibleProvider'
export class DashscopeProvider extends OpenAICompatibleProvider {
// 支持 enable_thinking 参数的模型列表(双模式模型)
private static readonly ENABLE_THINKING_MODELS: string[] = [
// 开源版
'qwen3-235b-a22b',
'qwen3-32b',
'qwen3-30b-a3b',
Expand All @@ -23,6 +24,20 @@ export class DashscopeProvider extends OpenAICompatibleProvider {
'qwen3-0.6b'
]

// 支持 enable_search 参数的模型列表(联网搜索)
private static readonly ENABLE_SEARCH_MODELS: string[] = [
'qwen-max',
'qwen-plus',
'qwen-plus-latest',
'qwen-plus-2025-07-14',
'qwen-flash',
'qwen-flash-2025-07-28',
'qwen-turbo',
'qwen-turbo-latest',
'qwen-turbo-2025-07-15',
'qwq-plus'
]

constructor(provider: LLM_PROVIDER, configPresenter: IConfigPresenter) {
super(provider, configPresenter)
}
Expand All @@ -40,7 +55,19 @@ export class DashscopeProvider extends OpenAICompatibleProvider {
}

/**
* 重写 coreStream 方法以支持 DashScope 的 enable_thinking 参数
* 检查模型是否支持 enable_search 参数
* @param modelId 模型ID
* @returns boolean 是否支持 enable_search
*/
private supportsEnableSearch(modelId: string): boolean {
const normalizedModelId = modelId.toLowerCase()
return DashscopeProvider.ENABLE_SEARCH_MODELS.some((supportedModel) =>
normalizedModelId.includes(supportedModel)
)
}

/**
* 重写 coreStream 方法以支持 DashScope 的 enable_thinking 和 enable_search 参数
*/
async *coreStream(
messages: ChatMessage[],
Expand All @@ -54,24 +81,41 @@ export class DashscopeProvider extends OpenAICompatibleProvider {
if (!modelId) throw new Error('Model ID is required')

const shouldAddEnableThinking = this.supportsEnableThinking(modelId) && modelConfig?.reasoning
const shouldAddEnableSearch = this.supportsEnableSearch(modelId) && modelConfig?.enableSearch

if (shouldAddEnableThinking) {
if (shouldAddEnableThinking || shouldAddEnableSearch) {
// 原始的 create 方法
const originalCreate = this.openai.chat.completions.create.bind(this.openai.chat.completions)
// 替换 create 方法以添加 enable_thinking 参数
// 替换 create 方法以添加 enable_thinking 和 enable_search 参数
this.openai.chat.completions.create = ((params: any, options?: any) => {
const modifiedParams = {
...params,
enable_thinking: true
const modifiedParams = { ...params }

if (shouldAddEnableThinking) {
modifiedParams.enable_thinking = true
if (modelConfig?.thinkingBudget) {
modifiedParams.thinking_budget = modelConfig.thinkingBudget
}
}
if (modelConfig?.thinkingBudget) {
modifiedParams.thinking_budget = modelConfig.thinkingBudget

if (shouldAddEnableSearch) {
modifiedParams.enable_search = true
if (modelConfig?.forcedSearch) {
modifiedParams.forced_search = true
}
if (modelConfig?.searchStrategy) {
modifiedParams.search_strategy = modelConfig.searchStrategy
}
}

return originalCreate(modifiedParams, options)
}) as any

Comment on lines +86 to 112
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Avoid monkey-patching the SDK method — race condition and cross-request leakage.

Overriding this.openai.chat.completions.create on a shared client is not concurrency-safe. Parallel streams can stomp each other’s overrides, yielding wrong params or leaving the client in a patched state. Also, errors in the inner call currently bubble without structured logging.

Proposed safer approach hierarchy (prefer top-most feasible):

  1. Refactor OpenAICompatibleProvider to let subclasses inject extra request params (e.g., protected buildExtraParams(...): object). Then override here and eliminate monkey-patch entirely.

  2. If (1) is not immediately feasible, at minimum:

    • Serialize the patch per provider instance (simple lock) to prevent concurrent overrides.
    • Add a catch to log structured error context, then rethrow.
    • Keep the try/finally restore.

Diff within this block:

-    if (shouldAddEnableThinking || shouldAddEnableSearch) {
+    if (shouldAddEnableThinking || shouldAddEnableSearch) {
+      // Prevent concurrent patching on the shared client
+      if ((this as any)._createPatchInProgress) {
+        // Fallback: skip overrides rather than risking cross-request leakage
+        yield* super.coreStream(messages, modelId, modelConfig, temperature, maxTokens, mcpTools)
+        return
+      }
+      ;(this as any)._createPatchInProgress = true
       // 原始的 create 方法
       const originalCreate = this.openai.chat.completions.create.bind(this.openai.chat.completions)
       // 替换 create 方法以添加 enable_thinking 和 enable_search 参数
       this.openai.chat.completions.create = ((params: any, options?: any) => {
         const modifiedParams = { ...params }
 
         if (shouldAddEnableThinking) {
           modifiedParams.enable_thinking = true
           if (modelConfig?.thinkingBudget) {
             modifiedParams.thinking_budget = modelConfig.thinkingBudget
           }
         }
 
         if (shouldAddEnableSearch) {
           modifiedParams.enable_search = true
           if (modelConfig?.forcedSearch) {
             modifiedParams.forced_search = true
           }
           if (modelConfig?.searchStrategy) {
             modifiedParams.search_strategy = modelConfig.searchStrategy
           }
         }
 
         return originalCreate(modifiedParams, options)
       }) as any
 
       try {
         const effectiveModelConfig = {
           ...modelConfig,
           reasoning: false,
           enableSearch: false
         }
-        yield* super.coreStream(
+        try {
+          yield* super.coreStream(
             messages,
             modelId,
             effectiveModelConfig,
             temperature,
             maxTokens,
             mcpTools
-        )
+          )
+        } catch (err: any) {
+          // Structured logging; avoid sensitive values
+          console.error(JSON.stringify({
+            level: 'ERROR',
+            ts: new Date().toISOString(),
+            where: 'DashscopeProvider.coreStream',
+            code: 'DASHSCOPE_SEARCH_PATCH_ERROR',
+            modelId,
+            enableThinking: !!shouldAddEnableThinking,
+            enableSearch: !!shouldAddEnableSearch,
+            message: err?.message || 'unknown error',
+            stack: err?.stack
+          }))
+          throw err
+        }
       } finally {
         this.openai.chat.completions.create = originalCreate
+        ;(this as any)._createPatchInProgress = false
       }

And add this class field (outside the patch block, near other fields) to make intent explicit:

// NOTE: serialized to avoid concurrent monkey-patches on shared client
private _createPatchInProgress?: boolean

Long-term, please replace the monkey-patch with an overridable param-construction hook in the base provider. I can sketch that change if helpful.

Also applies to: 114-119

try {
const effectiveModelConfig = { ...modelConfig, reasoning: false }
const effectiveModelConfig = {
...modelConfig,
reasoning: false,
enableSearch: false
}
yield* super.coreStream(
messages,
modelId,
Expand Down
Loading