From 0f3b562dc7dca58f27376466e6a8640ce49417cc Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 19 Feb 2026 02:07:38 +0800 Subject: [PATCH] fix(agent): relax vision model detection for image analysis --- lib/ai/auto-agent-graph.ts | 45 +++++++++++++++++++---- lib/ai/smart-analyzer.ts | 74 ++++++++++++++++++++++++++++++-------- 2 files changed, 97 insertions(+), 22 deletions(-) diff --git a/lib/ai/auto-agent-graph.ts b/lib/ai/auto-agent-graph.ts index 06308904a..7eb4eec99 100644 --- a/lib/ai/auto-agent-graph.ts +++ b/lib/ai/auto-agent-graph.ts @@ -487,8 +487,44 @@ type EnabledModelWithProvider = Awaited< ReturnType >[number]; +function isLikelyVisionCapableModel(item: EnabledModelWithProvider) { + if (!item.model.isEnabled || item.model.isSubModel) return false; + if (item.model.supportsVision) return true; + if (isAiModelMultimodal(item.model)) return true; + + const providerType = item.provider.type || ""; + const modelText = `${item.model.displayName || ""} ${item.model.modelId || ""}`.toLowerCase(); + + if (providerType === "volcano-multimodal") return true; + + if ( + providerType === "zhipu" && + /(glm[-_.]?4v|glm[-_.]?4\.?6v|vision|多模态)/i.test(modelText) + ) { + return true; + } + + if ( + providerType === "alibaba" && + /(qwen|vl|vision|通义|多模态)/i.test(modelText) + ) { + return true; + } + + if ( + ["custom", "openai", "local"].includes(providerType) && + /(doubao[-_.]?seed[-_.]?2[-_.]?0|seed[-_.]?2[-_.]?0|qwen[-_.]?vl|glm[-_.]?4v|gpt[-_.]?4o|vision|vl|多模态)/i.test( + modelText, + ) + ) { + return true; + } + + return false; +} + function pickQwenVisionModel(models: EnabledModelWithProvider[]) { - const visionModels = models.filter((item) => item.model.supportsVision && item.model.isEnabled); + const visionModels = models.filter((item) => isLikelyVisionCapableModel(item)); const zhipuVision = visionModels.find( (item) => item.provider.type === "zhipu" || @@ -514,12 +550,7 @@ function pickAgentVisionModel( agentModel: EnabledModelWithProvider | undefined, models: EnabledModelWithProvider[], ) { - if ( - agentModel && - agentModel.model.isEnabled && - agentModel.model.supportsVision && - isAiModelMultimodal(agentModel.model) - ) { + if (agentModel && isLikelyVisionCapableModel(agentModel)) { return agentModel; } diff --git a/lib/ai/smart-analyzer.ts b/lib/ai/smart-analyzer.ts index 0698cd557..91fa20832 100644 --- a/lib/ai/smart-analyzer.ts +++ b/lib/ai/smart-analyzer.ts @@ -568,6 +568,56 @@ interface HistoryMessage { content: string; } +function isLikelyVisionCapableModel(modelWithProvider: { + model: { + isEnabled: boolean; + isSubModel: boolean; + supportsVision?: boolean; + displayName?: string | null; + modelId?: string | null; + capabilities?: unknown; + defaultParams?: unknown; + }; + provider: { + type?: string | null; + }; +}) { + const model = modelWithProvider.model; + const providerType = String(modelWithProvider.provider?.type || ""); + if (!model?.isEnabled || model.isSubModel) return false; + if (model.supportsVision) return true; + if (isAiModelMultimodal(model)) return true; + + const modelText = `${model.displayName || ""} ${model.modelId || ""}`.toLowerCase(); + + if (providerType === "volcano-multimodal") return true; + + if ( + providerType === "zhipu" && + /(glm[-_.]?4v|glm[-_.]?4\.?6v|vision|多模态)/i.test(modelText) + ) { + return true; + } + + if ( + providerType === "alibaba" && + /(qwen|vl|vision|通义|多模态)/i.test(modelText) + ) { + return true; + } + + if ( + ["custom", "openai", "local"].includes(providerType) && + /(doubao[-_.]?seed[-_.]?2[-_.]?0|seed[-_.]?2[-_.]?0|qwen[-_.]?vl|glm[-_.]?4v|gpt[-_.]?4o|vision|vl|多模态)/i.test( + modelText, + ) + ) { + return true; + } + + return false; +} + /** * 分析用户意图 */ @@ -657,17 +707,13 @@ async function analyzWithLLM( ); const zhipuVisionModel = allModels.find( (m) => - m.model.supportsVision && - m.model.isEnabled && - !m.model.isSubModel && + isLikelyVisionCapableModel(m) && isLikelyZhipuVisionModel(m.model.displayName, m.model.modelId, m.provider.type), ); const qwenVisionModel = allModels.find( (m) => m.provider.type === "alibaba" && - m.model.supportsVision && - m.model.isEnabled && - !m.model.isSubModel && + isLikelyVisionCapableModel(m) && /(qwen|通义|vl)/i.test(`${m.model.displayName} ${m.model.modelId}`) ); const fallbackVisionModel = @@ -675,25 +721,23 @@ async function analyzWithLLM( allModels.find( (m) => m.provider.type === "alibaba" && - m.model.supportsVision && - m.model.isEnabled && - !m.model.isSubModel, + isLikelyVisionCapableModel(m), ); - const preferredVisionModel = zhipuVisionModel || fallbackVisionModel; + const preferredVisionModel = + zhipuVisionModel || + fallbackVisionModel || + allModels.find((m) => isLikelyVisionCapableModel(m)); const usableAgentModel = agentModel && isReasoningCompatibleProvider(agentModel.provider.type) ? agentModel : undefined; const multimodalAgentVisionModel = agentModel && - agentModel.model.isEnabled && - !agentModel.model.isSubModel && - agentModel.model.supportsVision && - isAiModelMultimodal(agentModel.model) + isLikelyVisionCapableModel(agentModel) ? agentModel : undefined; const selectedAnalyzerModel = hasImage - ? multimodalAgentVisionModel || preferredVisionModel + ? multimodalAgentVisionModel || preferredVisionModel || usableAgentModel : usableAgentModel || allModels.find((m) => m.provider.type === "zhipu" && !m.model.isSubModel) || preferredVisionModel;