520 lines
21 KiB
TypeScript
520 lines
21 KiB
TypeScript
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 { createKrea } from "./providers/krea";
|
||
import { createVolcanoMultimodal } from "./providers/volcano-multimodal";
|
||
import { getProviderDefaultBaseUrl } from "./provider-defaults";
|
||
import {
|
||
createMoonshotKimiFetch,
|
||
isMoonshotOfficialBaseUrl,
|
||
} from "./detail-agent-kimi-profile";
|
||
|
||
// 缓存已创建的 provider 实例
|
||
const providerCache = new Map<string, any>();
|
||
|
||
function isVolcanoMultimodalBaseUrl(baseUrl?: string | null) {
|
||
if (!baseUrl) return false;
|
||
return /ark\.cn-beijing\.volces\.com/i.test(baseUrl);
|
||
}
|
||
|
||
function getOpenAICompatibleFetch(baseUrl?: string | null) {
|
||
return isMoonshotOfficialBaseUrl(baseUrl) ? createMoonshotKimiFetch() : undefined;
|
||
}
|
||
|
||
/**
|
||
* 根据提供商类型创建对应的 AI Provider 实例
|
||
* 不同供应商使用OpenAI兼容的API格式,但可能有细微差异
|
||
*
|
||
* 支持的供应商类型:
|
||
* - alibaba: 阿里云 DashScope (通义千问等)
|
||
* - openai: 官方 OpenAI API
|
||
* - custom: OpenAI兼容的第三方API (GPT-5 Nano、其他兼容服务等)
|
||
*/
|
||
function getOrCreateProvider(provider: Pick<AiProvider, "id" | "type" | "baseUrl" | "apiKey">) {
|
||
const resolvedBaseUrl =
|
||
provider.baseUrl?.trim() || getProviderDefaultBaseUrl(provider.type) || "";
|
||
const cacheKey = `${provider.id}-${resolvedBaseUrl}`;
|
||
|
||
if (providerCache.has(cacheKey)) {
|
||
return providerCache.get(cacheKey);
|
||
}
|
||
|
||
let providerInstance: ReturnType<typeof createOpenAI>;
|
||
|
||
// 根据供应商类型创建不同的配置
|
||
// 虽然都使用 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,
|
||
fetch: getOpenAICompatibleFetch(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",
|
||
fetch: getOpenAICompatibleFetch(resolvedBaseUrl),
|
||
});
|
||
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",
|
||
fetch: getOpenAICompatibleFetch(resolvedBaseUrl),
|
||
});
|
||
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",
|
||
fetch: getOpenAICompatibleFetch(resolvedBaseUrl),
|
||
});
|
||
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 "krea": {
|
||
const kreaProvider = createKrea({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
console.log(`[Provider] Created Krea provider: ${resolvedBaseUrl}`);
|
||
return kreaProvider 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,
|
||
fetch: getOpenAICompatibleFetch(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<LanguageModel | null> {
|
||
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 === "krea") {
|
||
console.log("[getDynamicModel] Getting Krea chat model...");
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
console.log("[getDynamicModel] Krea 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<LanguageModel | null> {
|
||
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<LanguageModel | null> {
|
||
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;
|
||
}
|
||
}
|