600 lines
21 KiB
TypeScript
600 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 { createLogger } from "@/lib/observability/logger";
|
||
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 { createSnapgenImage } from "./providers/snapgen-image";
|
||
import { createOpenAIImage } from "./providers/openai-image";
|
||
import { createBananaImage } from "./providers/bananaimage";
|
||
import { createStepfunImage } from "./providers/stepfun-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 { createBltcyVideo } from "./providers/bltcy-video";
|
||
import { getProviderDefaultBaseUrl } from "./provider-defaults";
|
||
import {
|
||
createMoonshotKimiFetch,
|
||
isMoonshotOfficialBaseUrl,
|
||
} from "./detail-agent-kimi-profile";
|
||
import { isBltcyVideoBaseUrl } from "./bltcy-video-models";
|
||
import { resolveModelRuntimeId } from "./model-runtime";
|
||
|
||
// 缓存已创建的 provider 实例
|
||
const providerCache = new Map<string, any>();
|
||
const logger = createLogger("dynamic-provider");
|
||
|
||
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;
|
||
}
|
||
|
||
function logProviderCreated(providerType: string, baseUrl?: string | null) {
|
||
logger.debug("provider created", { providerType, baseUrl });
|
||
}
|
||
|
||
function logChatModelResult(providerType: string, baseModel: unknown) {
|
||
logger.debug("chat model resolved", {
|
||
providerType,
|
||
success: Boolean(baseModel),
|
||
modelType: typeof baseModel,
|
||
modelKeyCount:
|
||
baseModel && typeof baseModel === "object" ? Object.keys(baseModel).length : 0,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 根据提供商类型创建对应的 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),
|
||
});
|
||
logProviderCreated("alibaba", 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),
|
||
});
|
||
logProviderCreated(
|
||
"openai",
|
||
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),
|
||
});
|
||
logProviderCreated(
|
||
"zhipu",
|
||
resolvedBaseUrl || "https://open.bigmodel.cn/api/paas/v4"
|
||
);
|
||
break;
|
||
}
|
||
|
||
case "local": {
|
||
// 本地部署的模型(如千问2.5-VL)
|
||
// 使用自定义的本地模型接口,不兼容 OpenAI 格式
|
||
// 本地模型不使用缓存,因为类型不兼容 OpenAIProvider
|
||
const localProvider = createLocalQwen({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("local", resolvedBaseUrl);
|
||
return localProvider as any;
|
||
}
|
||
|
||
case "gemini": {
|
||
// Gemini 图像生成中转服务
|
||
// 使用特殊的响应格式处理(Markdown + inline_data)
|
||
const geminiProvider = createGeminiImage({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("gemini", resolvedBaseUrl);
|
||
return geminiProvider as any;
|
||
}
|
||
|
||
case "grsai-gpt-image": {
|
||
const grsaiGptImageProvider = createGeminiImage({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("grsai-gpt-image", resolvedBaseUrl);
|
||
return grsaiGptImageProvider as any;
|
||
}
|
||
|
||
case "grsai-video": {
|
||
logger.warn("provider adapter unavailable", {
|
||
providerType: "grsai-video",
|
||
baseUrl: resolvedBaseUrl,
|
||
});
|
||
return null;
|
||
}
|
||
|
||
case "huanke-gemini": {
|
||
// 环科 Gemini 原生格式 API
|
||
// 使用 /v1beta/models/{model}:generateContent 端点
|
||
const huankeGeminiProvider = createHuankeGemini({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("huanke-gemini", resolvedBaseUrl);
|
||
return huankeGeminiProvider as any;
|
||
}
|
||
|
||
case "qianwen-video": {
|
||
// 千问视频生成服务(通义万相 I2V)
|
||
// 支持图片转视频功能,异步任务轮询
|
||
const qianwenVideoProvider = createQianwenVideo({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("qianwen-video", 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),
|
||
});
|
||
logProviderCreated(
|
||
"doubao-video",
|
||
resolvedBaseUrl || "https://ark.cn-beijing.volces.com/api/v3"
|
||
);
|
||
break;
|
||
}
|
||
|
||
case "fal-video": {
|
||
// FAL 视频生成服务(Kling O3 等)
|
||
const falVideoProvider = createFalVideo({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("fal-video", resolvedBaseUrl);
|
||
return falVideoProvider as any;
|
||
}
|
||
|
||
case "ruxa-video": {
|
||
// RUXA 视频生成服务(Veo 3.1)
|
||
const ruxaVideoProvider = createRuxaVideo({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("ruxa-video", resolvedBaseUrl);
|
||
return ruxaVideoProvider as any;
|
||
}
|
||
|
||
case "volcano-vision": {
|
||
// 火山引擎视觉图片生成服务(Seedream)
|
||
// 支持文生图、图生图、多图融合等功能
|
||
const volcanoVisionProvider = createVolcanoVision({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("volcano-vision", 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,
|
||
});
|
||
logProviderCreated("alibaba-image", resolvedBaseUrl);
|
||
return alibabaImageProvider as any;
|
||
}
|
||
|
||
case "bfl-image": {
|
||
const bflImageProvider = createBflImage({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("bfl-image", resolvedBaseUrl);
|
||
return bflImageProvider as any;
|
||
}
|
||
|
||
case "ruxa-image": {
|
||
// RUXA 图片编辑服务
|
||
const ruxaImageProvider = createRuxaImage({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("ruxa-image", resolvedBaseUrl);
|
||
return ruxaImageProvider as any;
|
||
}
|
||
|
||
case "fal-image": {
|
||
// FAL 图片编辑服务(Nano Banana Pro)
|
||
const falImageProvider = createFalImage({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("fal-image", resolvedBaseUrl);
|
||
return falImageProvider as any;
|
||
}
|
||
|
||
case "snapgen-image": {
|
||
const snapgenImageProvider = createSnapgenImage({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("snapgen-image", resolvedBaseUrl);
|
||
return snapgenImageProvider as any;
|
||
}
|
||
|
||
case "openai-image": {
|
||
const openAIImageProvider = createOpenAIImage({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("openai-image", resolvedBaseUrl);
|
||
return openAIImageProvider as any;
|
||
}
|
||
|
||
case "bananaimage": {
|
||
const bananaImageProvider = createBananaImage({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("bananaimage", resolvedBaseUrl);
|
||
return bananaImageProvider as any;
|
||
}
|
||
|
||
case "stepfun-image": {
|
||
const stepfunImageProvider = createStepfunImage({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("stepfun-image", resolvedBaseUrl);
|
||
return stepfunImageProvider as any;
|
||
}
|
||
|
||
case "midjourney": {
|
||
const midjourneyProvider = createMidjourney({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("midjourney", resolvedBaseUrl);
|
||
return midjourneyProvider as any;
|
||
}
|
||
|
||
case "runway": {
|
||
const runwayProvider = createRunway({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("runway", resolvedBaseUrl);
|
||
return runwayProvider as any;
|
||
}
|
||
|
||
case "krea": {
|
||
const kreaProvider = createKrea({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("krea", resolvedBaseUrl);
|
||
return kreaProvider as any;
|
||
}
|
||
|
||
case "custom":
|
||
default: {
|
||
if (isBltcyVideoBaseUrl(resolvedBaseUrl)) {
|
||
const bltcyVideoProvider = createBltcyVideo({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("bltcy-video", resolvedBaseUrl);
|
||
return bltcyVideoProvider as any;
|
||
}
|
||
if (isVolcanoMultimodalBaseUrl(resolvedBaseUrl)) {
|
||
const volcanoMultimodalProvider = createVolcanoMultimodal({
|
||
baseURL: resolvedBaseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
logProviderCreated("volcano-multimodal", 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),
|
||
});
|
||
logProviderCreated("custom", 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) {
|
||
logger.warn("model unavailable", { modelId });
|
||
return null;
|
||
}
|
||
|
||
const provider = await getAiProviderById({ id: model.providerId });
|
||
if (!provider || !provider.isEnabled) {
|
||
logger.warn("provider unavailable", { modelId, providerId: model.providerId });
|
||
return null;
|
||
}
|
||
|
||
// 根据供应商类型创建对应的 provider 实例
|
||
const providerInstance = getOrCreateProvider({
|
||
id: provider.id,
|
||
type: provider.type,
|
||
baseUrl: provider.baseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
|
||
if (!providerInstance) {
|
||
logger.error("provider instance creation failed", {
|
||
providerType: provider.type,
|
||
});
|
||
return null;
|
||
}
|
||
|
||
const resolvedModelId = resolveModelRuntimeId(model, {
|
||
overrideModelId,
|
||
providerType: provider.type,
|
||
});
|
||
|
||
// 创建基础模型
|
||
logger.debug("model creation started", {
|
||
modelId,
|
||
resolvedModelId,
|
||
providerId: provider.id,
|
||
providerType: provider.type,
|
||
});
|
||
|
||
// 根据 provider 类型选择正确的方法和模型ID
|
||
// customProvider (gemini, local) 使用 .languageModel()
|
||
// OpenAI-compatible 使用 .chat()
|
||
let baseModel;
|
||
if (provider.type === "gemini" || provider.type === "grsai-gpt-image") {
|
||
const imageProviderLabel = provider.type === "grsai-gpt-image" ? "Grsai GPT Image" : "Gemini image";
|
||
// 图片生成 provider 使用 chat() 方法(与 local-qwen 相同的调用方式)
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult(imageProviderLabel, baseModel);
|
||
} else if (provider.type === "local") {
|
||
// 本地模型使用 chat() 方法
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("local", baseModel);
|
||
} else if (provider.type === "qianwen-video") {
|
||
// 千问视频生成使用 chat() 方法
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("qianwen-video", baseModel);
|
||
} else if (provider.type === "fal-video") {
|
||
// FAL 视频生成使用 chat() 方法
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("fal-video", baseModel);
|
||
} else if (provider.type === "ruxa-video") {
|
||
// RUXA 视频生成使用 chat() 方法
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("ruxa-video", baseModel);
|
||
} else if (provider.type === "volcano-vision") {
|
||
// 火山引擎视觉生成使用 chat() 方法
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("volcano-vision", baseModel);
|
||
} else if (provider.type === "alibaba-image") {
|
||
// 阿里云千问图片生成使用 chat() 方法
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("alibaba-image", baseModel);
|
||
} else if (provider.type === "bfl-image") {
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("bfl-image", baseModel);
|
||
} else if (provider.type === "ruxa-image") {
|
||
// RUXA 图片编辑生成使用 chat() 方法
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("ruxa-image", baseModel);
|
||
} else if (provider.type === "fal-image") {
|
||
// FAL 图片编辑生成使用 chat() 方法
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("fal-image", baseModel);
|
||
} else if (provider.type === "snapgen-image") {
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("snapgen-image", baseModel);
|
||
} else if (provider.type === "openai-image") {
|
||
// OpenAI Images 兼容图片生成/编辑使用 chat() 方法
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("openai-image", baseModel);
|
||
} else if (provider.type === "bananaimage") {
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("bananaimage", baseModel);
|
||
} else if (provider.type === "stepfun-image") {
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("stepfun-image", baseModel);
|
||
} else if (provider.type === "midjourney") {
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("midjourney", baseModel);
|
||
} else if (provider.type === "runway") {
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("runway", baseModel);
|
||
} else if (provider.type === "krea") {
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("krea", baseModel);
|
||
} else if (provider.type === "huanke-gemini") {
|
||
// 环科 Gemini 原生格式使用 chat() 方法
|
||
baseModel = providerInstance.chat(resolvedModelId);
|
||
logChatModelResult("huanke-gemini", baseModel);
|
||
} else {
|
||
// OpenAI-compatible providers
|
||
baseModel = (providerInstance as any).chat(resolvedModelId);
|
||
logChatModelResult("openai-compatible", baseModel);
|
||
}
|
||
|
||
// 如果模型名称包含特定关键词,可以添加推理中间件
|
||
// 例如,为推理模型添加思考过程提取
|
||
if (model.displayName.toLowerCase().includes("reasoning") ||
|
||
model.displayName.toLowerCase().includes("推理")) {
|
||
logger.debug("reasoning middleware applied", { modelId, resolvedModelId });
|
||
return wrapLanguageModel({
|
||
model: baseModel,
|
||
middleware: extractReasoningMiddleware({ tagName: "think" }),
|
||
});
|
||
}
|
||
|
||
logger.debug("base model returned", { modelId, resolvedModelId });
|
||
return baseModel;
|
||
} catch (error) {
|
||
logger.error("dynamic model resolution failed", error, { modelId });
|
||
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) {
|
||
logger.error("available models load failed", 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;
|
||
logger.debug("system model default selected", {
|
||
purpose,
|
||
modelId: model.id,
|
||
providerId: provider.id,
|
||
providerType: provider.type,
|
||
});
|
||
|
||
const providerInstance = getOrCreateProvider({
|
||
id: provider.id,
|
||
type: provider.type,
|
||
baseUrl: provider.baseUrl,
|
||
apiKey: provider.apiKey,
|
||
});
|
||
|
||
if (providerInstance) {
|
||
return providerInstance.chat(model.modelId);
|
||
}
|
||
}
|
||
|
||
// 如果没有找到数据库配置,回退到环境变量配置
|
||
logger.warn("system model default missing, using env fallback", { purpose });
|
||
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");
|
||
}
|
||
|
||
logger.error("system model configuration missing", { purpose });
|
||
return null;
|
||
} catch (error) {
|
||
logger.error("system model resolution failed", error, { purpose });
|
||
return null;
|
||
}
|
||
}
|