/** * 智能模型路由器 * 根据用户意图选择最佳AI模型 * * 核心逻辑:图片编辑场景优先使用 alibaba-image (灵魂4.5) */ import { isAiModelMultimodal } from "@/lib/db/ai-model-flags"; import { isImageGenerationModelDescriptor, modelSupportsTextToImageDescriptor, is3DModelDescriptor, isVideoModelDescriptor, } from "@/lib/ai/model-classification"; import { isKnownReferenceImageModel } from "@/lib/ai/reference-image-capability"; type EnabledModelWithProvider = Awaited< ReturnType >[number]; // 使用动态导入避免循环依赖 // import { getEnabledAiModelsWithProvider } from "@/lib/db/queries"; // 路由结果类型 export interface RouteResult { id: string; displayName: string; providerType: string; modelId: string; reason: string; } function hasVisionCapability(model: { supportsVision?: boolean | null; capabilities?: unknown; defaultParams?: unknown; modelId?: unknown; }, providerType?: string | null) { return ( Boolean(model.supportsVision) || isKnownReferenceImageModel(model, { type: providerType }) || isAiModelMultimodal(model) ); } const CHAT_PROVIDER_TYPES = [ "alibaba", "zhipu", "openai", "custom", "local", "gemini", "huanke-gemini", "volcano-multimodal", "vip98k", ] as const; const IMAGE_ONLY_PROVIDER_TYPES = new Set([ "alibaba-image", "bfl-image", "ruxa-image", "fal-image", "snapgen-image", "openai-image", "bananaimage", "visionary", "atlascloud", "stepfun-image", "midjourney", "gemini-image", ]); function isVideoProviderType( providerType: string, model?: { modelId?: string | null; capabilities?: unknown; supportsImageGen?: boolean | null; } ) { return isVideoModelDescriptor({ providerType, modelId: model?.modelId, capabilities: model?.capabilities, supportsImageGen: model?.supportsImageGen, }); } function isConversationalCandidate( item: EnabledModelWithProvider, options?: { requireVision?: boolean }, ) { const providerType = String(item.provider.type || ""); if (!item.model.isEnabled || item.model.isSubModel) return false; if (isVideoProviderType(providerType, item.model)) return false; if ( is3DModelDescriptor({ providerType, modelId: item.model.modelId, capabilities: item.model.capabilities, }) ) { return false; } if ( isImageGenerationModelDescriptor({ providerType, modelId: item.model.modelId, capabilities: item.model.capabilities, supportsImageGen: item.model.supportsImageGen, }) ) { return false; } const requireVision = Boolean(options?.requireVision); const matchesPreferredProvider = CHAT_PROVIDER_TYPES.includes( providerType as (typeof CHAT_PROVIDER_TYPES)[number], ); const hasVision = hasVisionCapability(item.model, providerType); if (matchesPreferredProvider) { return requireVision ? hasVision : true; } if (IMAGE_ONLY_PROVIDER_TYPES.has(providerType)) { return false; } if (requireVision) { return hasVision; } return !item.model.supportsImageGen; } function matchesIntentRequirements( item: EnabledModelWithProvider, params: { intent: string; requireVision?: boolean; requireImageGen?: boolean; }, ) { if (!item.model.isEnabled || item.model.isSubModel) return false; if (params.intent === "chat") { return isConversationalCandidate(item); } if (params.intent === "vision") { return ( !isVideoProviderType(String(item.provider.type || ""), item.model) && hasVisionCapability(item.model, item.provider.type) ); } if (params.intent === "video_gen") { return isVideoProviderType(String(item.provider.type || ""), item.model); } if (params.requireVision && !hasVisionCapability(item.model, item.provider.type)) return false; if ( params.requireImageGen && !(params.intent === "image_gen" ? modelSupportsTextToImageDescriptor({ providerType: item.provider.type, modelId: item.model.modelId, capabilities: item.model.capabilities, defaultParams: item.model.defaultParams, supportsImageGen: item.model.supportsImageGen, }) : isImageGenerationModelDescriptor({ providerType: item.provider.type, modelId: item.model.modelId, capabilities: item.model.capabilities, supportsImageGen: item.model.supportsImageGen, })) ) { return false; } return true; } // 意图到模型的映射配置 // providerTypes 按优先级排序,第一个是首选 const INTENT_MODEL_MAP: Record< string, { providerTypes: string[]; requireVision?: boolean; requireImageGen?: boolean; description: string; } > = { // 图片编辑:优先 alibaba-image(灵魂4.5,支持提示词扩展) image_edit: { providerTypes: ["alibaba-image", "bfl-image", "ruxa-image", "fal-image", "snapgen-image", "openai-image", "bananaimage", "visionary", "atlascloud", "vip98k", "stepfun-image", "runway", "krea", "gemini", "huanke-gemini"], requireVision: true, requireImageGen: true, description: "图片编辑", }, // 视觉理解:优先千问3 VL(成本低效果好) vision: { providerTypes: ["alibaba", "zhipu", "openai", "custom", "gemini", "huanke-gemini", "volcano-multimodal"], requireVision: true, description: "图片理解", }, // 图片生成:优先灵魂4.5 image_gen: { providerTypes: ["alibaba-image", "bfl-image", "ruxa-image", "fal-image", "snapgen-image", "openai-image", "bananaimage", "visionary", "atlascloud", "vip98k", "stepfun-image", "runway", "krea", "gemini", "huanke-gemini", "volcano-vision"], requireImageGen: true, description: "图片生成", }, // 视频生成 video_gen: { providerTypes: ["doubao-video", "volcano-video", "qianwen-video", "fal-video", "ruxa-video", "runway", "krea", "vip98k", "custom"], description: "视频生成", }, // 普通对话:优先千问3 chat: { providerTypes: ["alibaba", "zhipu", "openai", "custom", "local", "gemini", "huanke-gemini", "volcano-multimodal", "vip98k"], description: "对话", }, }; // 子意图的特殊路由配置(覆盖默认配置) const SUB_INTENT_OVERRIDES: Record< string, { providerTypes?: string[]; reason?: string; } > = { // 抠图:alibaba-image 效果最好 remove_background: { providerTypes: ["alibaba-image", "bfl-image", "ruxa-image", "fal-image", "snapgen-image", "openai-image", "bananaimage", "visionary", "atlascloud", "vip98k", "stepfun-image", "runway", "krea", "gemini"], reason: "抠图首选灵魂4.5", }, // 换背景:alibaba-image 支持场景生成 replace_background: { providerTypes: ["alibaba-image", "bfl-image", "ruxa-image", "fal-image", "snapgen-image", "openai-image", "bananaimage", "visionary", "atlascloud", "vip98k", "stepfun-image", "runway", "krea", "gemini"], reason: "换背景首选灵魂4.5", }, // 图片增强 enhance: { providerTypes: ["alibaba-image", "bfl-image", "ruxa-image", "fal-image", "snapgen-image", "openai-image", "bananaimage", "visionary", "atlascloud", "vip98k", "stepfun-image", "runway", "krea", "gemini", "volcano-vision"], reason: "增强首选灵魂4.5", }, // 风格迁移:gemini 可能更有创意 style_transfer: { providerTypes: ["gemini", "alibaba-image", "bfl-image", "ruxa-image", "fal-image", "snapgen-image", "openai-image", "bananaimage", "visionary", "atlascloud", "vip98k", "stepfun-image", "runway", "krea"], reason: "风格迁移优选Gemini", }, }; /** * 路由到最佳模型 */ export async function routeToModel(params: { intent: string; subIntent?: string; hasImage: boolean; }): Promise { const { intent, subIntent, hasImage } = params; console.log("[Model Router] Routing:", { intent, subIntent, hasImage }); // 使用动态导入避免循环依赖 const { getEnabledAiModelsWithProvider } = await import("@/lib/db/queries"); const { isAiModelAgentAuto } = await import("@/lib/db/ai-model-flags"); const { db } = await import("@/lib/db"); const { siteSettings } = await import("@/lib/db/schema"); // 获取所有可用模型 const allModelsRaw = await getEnabledAiModelsWithProvider(); const nonAgentModels = allModelsRaw.filter( (item) => !isAiModelAgentAuto(item.model) ); const allModels = nonAgentModels.length > 0 ? nonAgentModels : allModelsRaw; console.log("[Model Router] Available models:", allModels.length); // 获取意图对应的配置 let config = INTENT_MODEL_MAP[intent] || INTENT_MODEL_MAP["chat"]; // 检查是否有子意图的特殊配置 if (subIntent && SUB_INTENT_OVERRIDES[subIntent]) { const override = SUB_INTENT_OVERRIDES[subIntent]; if (override.providerTypes) { config = { ...config, providerTypes: override.providerTypes }; } } // 🆕 检查是否有配置的默认生图模型(仅在能力匹配时使用) const [settings] = await db.select().from(siteSettings).limit(1); const configuredDefaultModelId = settings?.canvasAutoModeDefaultModelId; if (configuredDefaultModelId && intent !== "chat") { const configuredModel = allModels.find( (m) => m.model.id === configuredDefaultModelId && m.model.isEnabled && !m.model.isSubModel ); if (configuredModel) { const isImageIntent = intent === "image_edit" || intent === "image_gen"; const matchesVision = !config.requireVision || hasVisionCapability(configuredModel.model, configuredModel.provider.type); const configuredModelSupportsRequestedImageGeneration = intent === "image_gen" ? modelSupportsTextToImageDescriptor({ providerType: configuredModel.provider.type, modelId: configuredModel.model.modelId, capabilities: configuredModel.model.capabilities, defaultParams: configuredModel.model.defaultParams, supportsImageGen: configuredModel.model.supportsImageGen, }) : configuredModel.model.supportsImageGen; const matchesImageGen = !config.requireImageGen || configuredModelSupportsRequestedImageGeneration; const allowImageIntentWithImageGenOnly = isImageIntent && configuredModelSupportsRequestedImageGeneration; if ((matchesVision && matchesImageGen) || allowImageIntentWithImageGenOnly) { const reason = allowImageIntentWithImageGenOnly && !matchesVision ? `管理员配置的默认模型(按生图能力优先)→ ${configuredModel.model.displayName}` : `管理员配置的默认模型 → ${configuredModel.model.displayName}`; console.log("[Model Router] ✅ Using configured default model:", configuredModel.model.displayName); if (allowImageIntentWithImageGenOnly && !matchesVision) { console.log( "[Model Router] ℹ️ Configured default model does not support vision, but image intent can reuse Agent analysis and keep admin default image model." ); } return { id: configuredModel.model.id, displayName: configuredModel.model.displayName, providerType: configuredModel.provider.type, modelId: configuredModel.model.modelId, reason, }; } console.log("[Model Router] ⚠️ Configured default model does not match intent requirements, falling back"); } else { console.log("[Model Router] ⚠️ Configured default model not found or disabled, falling back to intent routing"); } } else if (configuredDefaultModelId && intent === "chat") { console.log("[Model Router] ℹ️ Skip configured default image model for chat intent"); } // 按优先级查找匹配的模型 for (const providerType of config.providerTypes) { const candidates = allModels.filter((m) => { // 匹配 provider 类型 if (m.provider.type !== providerType) return false; return matchesIntentRequirements(m, { intent, requireVision: config.requireVision, requireImageGen: config.requireImageGen, }); }); if (candidates.length > 0) { // 优先选择默认模型 const defaultModel = candidates.find((c) => c.model.isDefault); const selected = defaultModel || candidates[0]; const result: RouteResult = { id: selected.model.id, displayName: selected.model.displayName, providerType: selected.provider.type, modelId: selected.model.modelId, reason: subIntent ? `${config.description}/${subIntent} → ${selected.model.displayName}` : `${config.description} → ${selected.model.displayName}`, }; console.log("[Model Router] ✅ Selected:", result); return result; } } const fallbackCandidates = allModels.filter((item) => { if (intent === "chat") { return isConversationalCandidate(item, { requireVision: false }); } if (intent === "vision") { return isConversationalCandidate(item, { requireVision: true }); } return ( !item.model.isSubModel && item.model.isEnabled && !isVideoProviderType(String(item.provider.type || ""), item.model) && ((intent === "image_gen" ? modelSupportsTextToImageDescriptor({ providerType: item.provider.type, modelId: item.model.modelId, capabilities: item.model.capabilities, defaultParams: item.model.defaultParams, supportsImageGen: item.model.supportsImageGen, }) : item.model.supportsImageGen) || hasVisionCapability(item.model, item.provider.type)) ); }); if (fallbackCandidates.length > 0) { const fallback = fallbackCandidates[0]; console.log("[Model Router] Using intent-safe fallback:", fallback.model.displayName); return { id: fallback.model.id, displayName: fallback.model.displayName, providerType: fallback.provider.type, modelId: fallback.model.modelId, reason: `fallback (${config.description})`, }; } // 最终兜底:返回任意可用模型 const anyModel = (intent === "chat" ? allModels.find( (m) => !m.model.isSubModel && m.model.isEnabled && !isVideoProviderType(String(m.provider.type || ""), m.model) && !m.model.supportsImageGen, ) : undefined) || allModels.find((m) => !m.model.isSubModel && m.model.isEnabled); if (anyModel) { console.log("[Model Router] Using any available model:", anyModel.model.displayName); return { id: anyModel.model.id, displayName: anyModel.model.displayName, providerType: anyModel.provider.type, modelId: anyModel.model.modelId, reason: "fallback (any)", }; } // 如果没有任何模型,抛出错误 throw new Error("No available models for routing"); } /** * 获取意图的推荐模型列表(用于 UI 展示) */ export function getRecommendedModelsForIntent(intent: string): string[] { const config = INTENT_MODEL_MAP[intent]; return config ? config.providerTypes : INTENT_MODEL_MAP["chat"].providerTypes; } /** * 检查模型是否适合指定意图 */ export function isModelSuitableForIntent( providerType: string, intent: string, supportsVision: boolean, supportsImageGen: boolean ): boolean { const config = INTENT_MODEL_MAP[intent]; if (!config) return true; // 未知意图,允许任何模型 // 检查 provider 类型是否匹配 if (!config.providerTypes.includes(providerType)) { return false; } // 检查能力要求 if (config.requireVision && !supportsVision) return false; if (config.requireImageGen && !supportsImageGen) return false; return true; }