180 lines
4.6 KiB
TypeScript
180 lines
4.6 KiB
TypeScript
import "server-only";
|
||
|
||
import { createOpenAI } from "@ai-sdk/openai";
|
||
import type { LanguageModel } from "ai";
|
||
import { getLanguageModelById } from "@/lib/ai/dynamic-provider";
|
||
import {
|
||
isImageGenerationModelDescriptor,
|
||
isVideoModelDescriptor,
|
||
} from "@/lib/ai/model-classification";
|
||
import { isAiModelMultimodal } from "@/lib/db/ai-model-flags";
|
||
import {
|
||
getDefaultAiModel,
|
||
getEnabledAiModelsWithProvider,
|
||
getSystemSetting,
|
||
} from "@/lib/db/queries";
|
||
import {
|
||
normalizeProductPlanningAiModelConfig,
|
||
PRODUCT_PLANNING_AI_MODEL_CONFIG_KEY,
|
||
toProductPlanningOpenAIBaseUrl,
|
||
} from "./model-config";
|
||
|
||
type ProductPlanningModelResolution =
|
||
| {
|
||
model: LanguageModel;
|
||
source: "dedicated" | "default";
|
||
error?: undefined;
|
||
}
|
||
| {
|
||
model: null;
|
||
source: "dedicated" | "default";
|
||
error: string;
|
||
};
|
||
|
||
async function resolveDedicatedProductPlanningModel(): Promise<ProductPlanningModelResolution | null> {
|
||
const setting = await getSystemSetting({
|
||
key: PRODUCT_PLANNING_AI_MODEL_CONFIG_KEY,
|
||
}).catch(() => null);
|
||
const config = normalizeProductPlanningAiModelConfig(setting?.value);
|
||
|
||
if (!config.enabled) {
|
||
return null;
|
||
}
|
||
|
||
if (!config.apiKey.trim()) {
|
||
return {
|
||
model: null,
|
||
source: "dedicated",
|
||
error: "商品策划专用 AI 模型已启用,但未配置 API Key,请前往系统设置填写",
|
||
};
|
||
}
|
||
|
||
const provider = createOpenAI({
|
||
apiKey: config.apiKey.trim(),
|
||
baseURL: toProductPlanningOpenAIBaseUrl(config.baseUrl),
|
||
});
|
||
|
||
return {
|
||
model: provider.chat(config.modelId.trim()),
|
||
source: "dedicated",
|
||
};
|
||
}
|
||
|
||
async function resolveDefaultTextModel(): Promise<ProductPlanningModelResolution> {
|
||
const [defaultModel, enabledModels] = await Promise.all([
|
||
getDefaultAiModel().catch(() => undefined),
|
||
getEnabledAiModelsWithProvider(false),
|
||
]);
|
||
|
||
const candidates = enabledModels.filter(({ model, provider }) => {
|
||
const isVideoModel = isVideoModelDescriptor({
|
||
providerType: provider.type,
|
||
modelId: model.modelId,
|
||
capabilities: model.capabilities,
|
||
supportsImageGen: model.supportsImageGen,
|
||
});
|
||
return !isVideoModel && !model.supportsImageGen;
|
||
});
|
||
|
||
const selected =
|
||
(defaultModel ? candidates.find(({ model }) => model.id === defaultModel.id) : undefined) ||
|
||
candidates[0];
|
||
|
||
if (!selected) {
|
||
return {
|
||
model: null,
|
||
source: "default",
|
||
error: "当前站点没有可用的文本分析模型",
|
||
};
|
||
}
|
||
|
||
const model = await getLanguageModelById(selected.model.id);
|
||
if (!model) {
|
||
return {
|
||
model: null,
|
||
source: "default",
|
||
error: "当前站点没有可用的文本分析模型",
|
||
};
|
||
}
|
||
|
||
return {
|
||
model,
|
||
source: "default",
|
||
};
|
||
}
|
||
|
||
async function resolveDefaultVisionModel(): Promise<ProductPlanningModelResolution> {
|
||
const [defaultModel, enabledModels] = await Promise.all([
|
||
getDefaultAiModel().catch(() => undefined),
|
||
getEnabledAiModelsWithProvider(false),
|
||
]);
|
||
|
||
const candidates = enabledModels.filter(({ model, provider }) => {
|
||
const isVideoModel = isVideoModelDescriptor({
|
||
providerType: provider.type,
|
||
modelId: model.modelId,
|
||
capabilities: model.capabilities,
|
||
supportsImageGen: model.supportsImageGen,
|
||
});
|
||
const isImageModel = isImageGenerationModelDescriptor({
|
||
providerType: provider.type,
|
||
modelId: model.modelId,
|
||
capabilities: model.capabilities,
|
||
supportsImageGen: model.supportsImageGen,
|
||
});
|
||
|
||
return (
|
||
!isVideoModel &&
|
||
!isImageModel &&
|
||
(Boolean(model.supportsVision) ||
|
||
isAiModelMultimodal({
|
||
capabilities: model.capabilities,
|
||
defaultParams: model.defaultParams,
|
||
})) &&
|
||
!model.supportsImageGen
|
||
);
|
||
});
|
||
|
||
const selected =
|
||
(defaultModel ? candidates.find(({ model }) => model.id === defaultModel.id) : undefined) ||
|
||
candidates[0];
|
||
|
||
if (!selected) {
|
||
return {
|
||
model: null,
|
||
source: "default",
|
||
error: "当前站点没有可用的图片分析模型",
|
||
};
|
||
}
|
||
|
||
const model = await getLanguageModelById(selected.model.id);
|
||
if (!model) {
|
||
return {
|
||
model: null,
|
||
source: "default",
|
||
error: "当前站点没有可用的图片分析模型",
|
||
};
|
||
}
|
||
|
||
return {
|
||
model,
|
||
source: "default",
|
||
};
|
||
}
|
||
|
||
export async function resolveProductPlanningTextModel() {
|
||
const dedicated = await resolveDedicatedProductPlanningModel();
|
||
if (dedicated) {
|
||
return dedicated;
|
||
}
|
||
return resolveDefaultTextModel();
|
||
}
|
||
|
||
export async function resolveProductPlanningVisionModel() {
|
||
const dedicated = await resolveDedicatedProductPlanningModel();
|
||
if (dedicated) {
|
||
return dedicated;
|
||
}
|
||
return resolveDefaultVisionModel();
|
||
}
|