import "server-only"; import { createOpenAI } from "@ai-sdk/openai"; import { extractReasoningMiddleware, wrapLanguageModel, type LanguageModel, } from "ai"; import { getEnabledAiModelsWithProvider, getAiProviderById, getAiModelById, } from "@/lib/db/queries"; import type { AiProvider } from "@/lib/db/schema"; import { createLocalQwen } from "./providers/local-qwen"; import { createGeminiImage } from "./providers/gemini-image"; import { createHuankeGemini } from "./providers/huanke-gemini"; import { createQianwenVideo } from "./providers/qianwen-video"; import { createVolcanoVision } from "./providers/volcano-vision"; import { createAlibabaImage } from "./providers/alibaba-image"; import { createBflImage } from "./providers/bfl-image"; import { createRuxaImage } from "./providers/ruxa-image"; import { createFalImage } from "./providers/fal-image"; import { createOpenAIImage } from "./providers/openai-image"; import { createMidjourney } from "./providers/midjourney"; import { createFalVideo } from "./providers/fal-video"; import { createRuxaVideo } from "./providers/ruxa-video"; import { createRunway } from "./providers/runway"; import { createVolcanoMultimodal } from "./providers/volcano-multimodal"; import { getProviderDefaultBaseUrl } from "./provider-defaults"; // 缓存已创建的 provider 实例 const providerCache = new Map(); function isVolcanoMultimodalBaseUrl(baseUrl?: string | null) { if (!baseUrl) return false; return /ark\.cn-beijing\.volces\.com/i.test(baseUrl); } /** * 根据提供商类型创建对应的 AI Provider 实例 * 不同供应商使用OpenAI兼容的API格式,但可能有细微差异 * * 支持的供应商类型: * - alibaba: 阿里云 DashScope (通义千问等) * - openai: 官方 OpenAI API * - custom: OpenAI兼容的第三方API (GPT-5 Nano、其他兼容服务等) */ function getOrCreateProvider(provider: Pick) { const resolvedBaseUrl = provider.baseUrl?.trim() || getProviderDefaultBaseUrl(provider.type) || ""; const cacheKey = `${provider.id}-${resolvedBaseUrl}`; if (providerCache.has(cacheKey)) { return providerCache.get(cacheKey); } let providerInstance: ReturnType; // 根据供应商类型创建不同的配置 // 虽然都使用 createOpenAI,但通过 baseURL 和 apiKey 的不同来区分不同供应商 switch (provider.type) { case "alibaba": { // 阿里云 DashScope API 配置 // 阿里云提供 OpenAI 兼容的接口:https://dashscope.aliyuncs.com/compatible-mode/v1 providerInstance = createOpenAI({ apiKey: provider.apiKey, baseURL: resolvedBaseUrl, }); console.log(`[Provider] Created Alibaba DashScope provider: ${resolvedBaseUrl}`); break; } case "openai": { // 标准 OpenAI API 配置 // 官方 API 地址:https://api.openai.com/v1 providerInstance = createOpenAI({ apiKey: provider.apiKey, baseURL: resolvedBaseUrl || "https://api.openai.com/v1", }); console.log(`[Provider] Created OpenAI provider: ${resolvedBaseUrl || "https://api.openai.com/v1"}`); break; } case "zhipu": { providerInstance = createOpenAI({ apiKey: provider.apiKey, baseURL: resolvedBaseUrl || "https://open.bigmodel.cn/api/paas/v4", }); console.log( `[Provider] Created Zhipu Open Platform provider: ${resolvedBaseUrl || "https://open.bigmodel.cn/api/paas/v4"}` ); break; } case "local": { // 本地部署的模型(如千问2.5-VL) // 使用自定义的本地模型接口,不兼容 OpenAI 格式 // 本地模型不使用缓存,因为类型不兼容 OpenAIProvider const localProvider = createLocalQwen({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created local model provider: ${resolvedBaseUrl}`); return localProvider as any; } case "gemini": { // Gemini 图像生成中转服务 // 使用特殊的响应格式处理(Markdown + inline_data) const geminiProvider = createGeminiImage({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created Gemini image generation provider: ${resolvedBaseUrl}`); return geminiProvider as any; } case "huanke-gemini": { // 环科 Gemini 原生格式 API // 使用 /v1beta/models/{model}:generateContent 端点 const huankeGeminiProvider = createHuankeGemini({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created Huanke Gemini image generation provider: ${resolvedBaseUrl}`); return huankeGeminiProvider as any; } case "qianwen-video": { // 千问视频生成服务(通义万相 I2V) // 支持图片转视频功能,异步任务轮询 const qianwenVideoProvider = createQianwenVideo({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created Qianwen video generation provider: ${resolvedBaseUrl}`); return qianwenVideoProvider as any; } case "doubao-video": { providerInstance = createOpenAI({ apiKey: provider.apiKey, baseURL: resolvedBaseUrl || "https://ark.cn-beijing.volces.com/api/v3", }); console.log( `[Provider] Created Doubao video provider: ${resolvedBaseUrl || "https://ark.cn-beijing.volces.com/api/v3"}` ); break; } case "fal-video": { // FAL 视频生成服务(Kling O3 等) const falVideoProvider = createFalVideo({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created Fal Video generation provider: ${resolvedBaseUrl}`); return falVideoProvider as any; } case "ruxa-video": { // RUXA 视频生成服务(Veo 3.1) const ruxaVideoProvider = createRuxaVideo({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created Ruxa Video generation provider: ${resolvedBaseUrl}`); return ruxaVideoProvider as any; } case "volcano-vision": { // 火山引擎视觉图片生成服务(Seedream) // 支持文生图、图生图、多图融合等功能 const volcanoVisionProvider = createVolcanoVision({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created Volcano Vision image generation provider: ${resolvedBaseUrl}`); return volcanoVisionProvider as any; } case "alibaba-image": { // 阿里云千问图片生成/编辑服务 // 支持 qwen-image-edit-plus(1-6张输出)和 qwen-image-edit(1张输出) const alibabaImageProvider = createAlibabaImage({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created Alibaba Image generation provider: ${resolvedBaseUrl}`); return alibabaImageProvider as any; } case "bfl-image": { const bflImageProvider = createBflImage({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created BFL Image generation provider: ${resolvedBaseUrl}`); return bflImageProvider as any; } case "ruxa-image": { // RUXA 图片编辑服务 const ruxaImageProvider = createRuxaImage({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created Ruxa Image generation provider: ${resolvedBaseUrl}`); return ruxaImageProvider as any; } case "fal-image": { // FAL 图片编辑服务(Nano Banana Pro) const falImageProvider = createFalImage({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created Fal Image generation provider: ${resolvedBaseUrl}`); return falImageProvider as any; } case "openai-image": { const openAIImageProvider = createOpenAIImage({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created OpenAI Image generation provider: ${resolvedBaseUrl}`); return openAIImageProvider as any; } case "midjourney": { const midjourneyProvider = createMidjourney({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created Midjourney generation provider: ${resolvedBaseUrl}`); return midjourneyProvider as any; } case "runway": { const runwayProvider = createRunway({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log(`[Provider] Created Runway provider: ${resolvedBaseUrl}`); return runwayProvider as any; } case "custom": default: { if (isVolcanoMultimodalBaseUrl(resolvedBaseUrl)) { const volcanoMultimodalProvider = createVolcanoMultimodal({ baseURL: resolvedBaseUrl, apiKey: provider.apiKey, }); console.log( `[Provider] Created Volcano Multimodal provider via Responses API: ${resolvedBaseUrl}` ); return volcanoMultimodalProvider as any; } // OpenAI 兼容的第三方 API(如 GPT-5 Nano、Claude via OpenAI proxy 等) // 这些服务提供与 OpenAI API 完全兼容的接口 // 请求格式遵循 OpenAI Chat Completions 标准:/v1/chat/completions providerInstance = createOpenAI({ apiKey: provider.apiKey, baseURL: resolvedBaseUrl || provider.baseUrl, }); console.log( `[Provider] Created custom OpenAI-compatible provider: ${resolvedBaseUrl || provider.baseUrl}` ); break; } } providerCache.set(cacheKey, providerInstance); return providerInstance; } // 获取动态配置的语言模型 export async function getDynamicModel( modelId: string, overrideModelId?: string | null ): Promise { try { const model = await getAiModelById({ id: modelId }); if (!model || !model.isEnabled) { console.warn(`[getDynamicModel] Model ${modelId} not found or disabled`); return null; } const provider = await getAiProviderById({ id: model.providerId }); if (!provider || !provider.isEnabled) { console.warn(`[getDynamicModel] Provider for model ${modelId} not found or disabled`); return null; } // 根据供应商类型创建对应的 provider 实例 const providerInstance = getOrCreateProvider({ id: provider.id, type: provider.type, baseUrl: provider.baseUrl, apiKey: provider.apiKey, }); if (!providerInstance) { console.error(`[getDynamicModel] Failed to create provider instance for ${provider.type}`); return null; } const resolvedModelId = String(overrideModelId || "").trim() || model.modelId; // 创建基础模型 console.log(`[getDynamicModel] Creating model: ${resolvedModelId} (${model.displayName}) using provider: ${provider.name} (${provider.type})`); // 根据 provider 类型选择正确的方法和模型ID // customProvider (gemini, local) 使用 .languageModel() // OpenAI-compatible 使用 .chat() let baseModel; if (provider.type === "gemini") { // Gemini 图片生成使用 chat() 方法(与 local-qwen 相同的调用方式) console.log("[getDynamicModel] Getting Gemini chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Gemini model retrieved:", baseModel ? "success" : "failed"); // 调试日志 console.log("[getDynamicModel] Gemini model type:", typeof baseModel); // 调试 console.log("[getDynamicModel] Gemini model keys:", baseModel ? Object.keys(baseModel).join(', ') : 'N/A'); // 调试 } else if (provider.type === "local") { // 本地模型使用 chat() 方法 console.log("[getDynamicModel] Getting local chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Local model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } else if (provider.type === "qianwen-video") { // 千问视频生成使用 chat() 方法 console.log("[getDynamicModel] Getting Qianwen Video chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Qianwen Video model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } else if (provider.type === "fal-video") { // FAL 视频生成使用 chat() 方法 console.log("[getDynamicModel] Getting Fal Video chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Fal Video model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } else if (provider.type === "ruxa-video") { // RUXA 视频生成使用 chat() 方法 console.log("[getDynamicModel] Getting Ruxa Video chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Ruxa Video model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } else if (provider.type === "volcano-vision") { // 火山引擎视觉生成使用 chat() 方法 console.log("[getDynamicModel] Getting Volcano Vision chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Volcano Vision model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } else if (provider.type === "alibaba-image") { // 阿里云千问图片生成使用 chat() 方法 console.log("[getDynamicModel] Getting Alibaba Image chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Alibaba Image model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } else if (provider.type === "bfl-image") { console.log("[getDynamicModel] Getting BFL Image chat model..."); baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] BFL Image model retrieved:", baseModel ? "success" : "failed"); } else if (provider.type === "ruxa-image") { // RUXA 图片编辑生成使用 chat() 方法 console.log("[getDynamicModel] Getting Ruxa Image chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Ruxa Image model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } else if (provider.type === "fal-image") { // FAL 图片编辑生成使用 chat() 方法 console.log("[getDynamicModel] Getting Fal Image chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Fal Image model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } else if (provider.type === "openai-image") { // OpenAI Images 兼容图片生成/编辑使用 chat() 方法 console.log("[getDynamicModel] Getting OpenAI Image chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] OpenAI Image model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } else if (provider.type === "midjourney") { console.log("[getDynamicModel] Getting Midjourney chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Midjourney model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } else if (provider.type === "runway") { console.log("[getDynamicModel] Getting Runway chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Runway model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } else if (provider.type === "huanke-gemini") { // 环科 Gemini 原生格式使用 chat() 方法 console.log("[getDynamicModel] Getting Huanke Gemini chat model..."); // 调试日志 baseModel = providerInstance.chat(resolvedModelId); console.log("[getDynamicModel] Huanke Gemini model retrieved:", baseModel ? "success" : "failed"); // 调试日志 console.log("[getDynamicModel] Huanke Gemini model type:", typeof baseModel); // 调试 console.log("[getDynamicModel] Huanke Gemini model keys:", baseModel ? Object.keys(baseModel).join(', ') : 'N/A'); // 调试 } else { // OpenAI-compatible providers console.log("[getDynamicModel] Getting OpenAI-compatible model..."); // 调试日志 baseModel = (providerInstance as any).chat(resolvedModelId); console.log("[getDynamicModel] OpenAI model retrieved:", baseModel ? "success" : "failed"); // 调试日志 } // 如果模型名称包含特定关键词,可以添加推理中间件 // 例如,为推理模型添加思考过程提取 if (model.displayName.toLowerCase().includes("reasoning") || model.displayName.toLowerCase().includes("推理")) { console.log("[getDynamicModel] Applying reasoning middleware..."); // 调试日志 return wrapLanguageModel({ model: baseModel, middleware: extractReasoningMiddleware({ tagName: "think" }), }); } console.log("[getDynamicModel] Returning base model"); // 调试日志 return baseModel; } catch (error) { console.error("[getDynamicModel] Failed to get dynamic model:", error); return null; } } // 通过模型的数据库 ID 获取语言模型实例 export async function getLanguageModelById( modelId: string, overrideModelId?: string | null ): Promise { return getDynamicModel(modelId, overrideModelId); } // 获取所有可用的模型列表(用于前端展示) export async function getAvailableModels() { try { const modelsWithProvider = await getEnabledAiModelsWithProvider(); return modelsWithProvider.map(({ model, provider }) => ({ id: model.id, modelId: model.modelId, displayName: model.displayName, description: model.description, supportsVision: model.supportsVision, supportsImageGen: model.supportsImageGen, isDefault: model.isDefault, providerName: provider.name, providerType: provider.type, })); } catch (error) { console.error("Failed to get available models:", error); return []; } } // 获取系统模型(标题生成、文档生成等) // 这些模型使用数据库中标记为默认的模型,如果没有则回退到环境变量配置 export async function getSystemModel( purpose: "title" | "artifact" ): Promise { try { // 首先尝试从数据库获取默认模型 const modelsWithProvider = await getEnabledAiModelsWithProvider(); // 找到默认模型 const defaultModelData = modelsWithProvider.find( ({ model }) => model.isDefault ); if (defaultModelData) { const { model, provider } = defaultModelData; console.log(`[getSystemModel] Using default model for ${purpose}: ${model.displayName} (${provider.name})`); const providerInstance = getOrCreateProvider({ id: provider.id, type: provider.type, baseUrl: provider.baseUrl, apiKey: provider.apiKey, }); if (providerInstance) { return providerInstance.chat(model.modelId); } } // 如果没有找到数据库配置,回退到环境变量配置 console.warn(`[getSystemModel] No default model found in database for ${purpose}, falling back to env config`); if (process.env.DASHSCOPE_API_KEY && process.env.DASHSCOPE_BASE_URL) { const fallbackProvider = createOpenAI({ apiKey: process.env.DASHSCOPE_API_KEY, baseURL: process.env.DASHSCOPE_BASE_URL, }); return fallbackProvider.chat("qwen-turbo"); } console.error(`[getSystemModel] No model configuration found for ${purpose}`); return null; } catch (error) { console.error(`[getSystemModel] Failed to get system model for ${purpose}:`, error); return null; } }