新增Agent自动模型标签并支持智普4.7服务商
This commit is contained in:
parent
2913bf77be
commit
46f06819dc
@ -98,7 +98,7 @@ type ToolConfig = {
|
||||
type AiProvider = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image";
|
||||
type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "zhipu";
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
isEnabled: boolean;
|
||||
@ -128,6 +128,7 @@ type AiModel = {
|
||||
isEnabled: boolean;
|
||||
isDefault: boolean;
|
||||
sortOrder: number;
|
||||
isAgentModel?: boolean;
|
||||
imageGenConfig?: ImageGenConfig; // 🆕 图片生成特性配置
|
||||
};
|
||||
|
||||
@ -284,6 +285,7 @@ const PROVIDER_TYPES = [
|
||||
{ value: "qianwen-video", label: "千问视频生成" },
|
||||
{ value: "volcano-vision", label: "火山引擎视觉" },
|
||||
{ value: "volcano-video", label: "火山引擎视频" },
|
||||
{ value: "zhipu", label: "智普官方 4.7" },
|
||||
];
|
||||
|
||||
export function AdminSettingsClient() {
|
||||
@ -740,7 +742,7 @@ export function AdminSettingsClient() {
|
||||
const [providerForm, setProviderForm] = useState({
|
||||
id: "",
|
||||
name: "",
|
||||
type: "openai" as "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image",
|
||||
type: "openai" as "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "zhipu",
|
||||
baseUrl: "",
|
||||
apiKey: "",
|
||||
isEnabled: true,
|
||||
@ -759,6 +761,7 @@ export function AdminSettingsClient() {
|
||||
supportsImageGen: false,
|
||||
isEnabled: true,
|
||||
isDefault: false,
|
||||
isAgentModel: false,
|
||||
sortOrder: 0,
|
||||
fallbackModelId1: "", // 备用模型 ID 1
|
||||
fallbackModelId2: "", // 备用模型 ID 2
|
||||
@ -1353,6 +1356,7 @@ export function AdminSettingsClient() {
|
||||
supportsImageGen: false,
|
||||
isEnabled: true,
|
||||
isDefault: false,
|
||||
isAgentModel: false,
|
||||
sortOrder: 0,
|
||||
fallbackModelId1: "",
|
||||
fallbackModelId2: "",
|
||||
@ -2307,6 +2311,7 @@ export function AdminSettingsClient() {
|
||||
"alibaba-image": "bg-amber-500",
|
||||
"ruxa-image": "bg-emerald-500",
|
||||
"openai": "bg-green-500",
|
||||
"zhipu": "bg-lime-500",
|
||||
"qianwen-video": "bg-cyan-500",
|
||||
"custom": "bg-gray-500",
|
||||
"local": "bg-yellow-500",
|
||||
@ -3617,6 +3622,8 @@ export function AdminSettingsClient() {
|
||||
baseUrl:
|
||||
!prev.baseUrl && value === "ruxa-image"
|
||||
? "https://api.ruxa.ai"
|
||||
: !prev.baseUrl && value === "zhipu"
|
||||
? "https://open.bigmodel.cn/api/paas/v4"
|
||||
: prev.baseUrl,
|
||||
}))
|
||||
}
|
||||
@ -3634,7 +3641,13 @@ export function AdminSettingsClient() {
|
||||
<Input
|
||||
value={providerForm.baseUrl}
|
||||
onChange={(e) => setProviderForm({ ...providerForm, baseUrl: e.target.value })}
|
||||
placeholder={providerForm.type === "ruxa-image" ? "https://api.ruxa.ai" : "https://api.openai.com/v1"}
|
||||
placeholder={
|
||||
providerForm.type === "ruxa-image"
|
||||
? "https://api.ruxa.ai"
|
||||
: providerForm.type === "zhipu"
|
||||
? "https://open.bigmodel.cn/api/paas/v4"
|
||||
: "https://api.openai.com/v1"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@ -3899,6 +3912,13 @@ export function AdminSettingsClient() {
|
||||
<Label>设为默认</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={modelForm.isAgentModel}
|
||||
onCheckedChange={(checked) => setModelForm({ ...modelForm, isAgentModel: checked })}
|
||||
/>
|
||||
<Label>Agent 自动模型(前端隐藏,仅后台 Agent 使用)</Label>
|
||||
</div>
|
||||
|
||||
{/* 🆕 图片生成模型特性配置 */}
|
||||
{modelForm.supportsImageGen && (
|
||||
@ -4029,6 +4049,7 @@ export function AdminSettingsClient() {
|
||||
<div className="font-medium flex items-center gap-2">
|
||||
{model.displayName}
|
||||
{model.isDefault && <span className="text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded">默认</span>}
|
||||
{model.isAgentModel && <span className="text-xs bg-violet-100 text-violet-800 px-2 py-0.5 rounded">Agent</span>}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
ID: {model.modelId} | 提供商: {provider?.name ?? "未知"}
|
||||
@ -4046,6 +4067,7 @@ export function AdminSettingsClient() {
|
||||
supportsImageGen: model.supportsImageGen,
|
||||
isEnabled: model.isEnabled,
|
||||
isDefault: model.isDefault,
|
||||
isAgentModel: model.isAgentModel || false,
|
||||
sortOrder: model.sortOrder || 0,
|
||||
fallbackModelId1: (model as any).fallbackModelId1 || "",
|
||||
fallbackModelId2: (model as any).fallbackModelId2 || "",
|
||||
|
||||
@ -54,6 +54,7 @@ import { uploadFile } from "@/lib/storage/service";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
import type { AppUsage } from "@/lib/usage";
|
||||
import { convertToUIMessages, generateUUID } from "@/lib/utils";
|
||||
import { isAiModelAgentAuto } from "@/lib/db/ai-model-flags";
|
||||
import { generateTitleFromUserMessage } from "../../actions";
|
||||
import { type PostRequestBody, postRequestBodySchema } from "./schema";
|
||||
|
||||
@ -832,7 +833,13 @@ export async function POST(request: Request) {
|
||||
|
||||
if (selectedModelIsUUID) {
|
||||
const selectedModelConfig = await getAiModelById({ id: requestedModelId });
|
||||
if (selectedModelConfig?.supportsImageGen && !selectedModelConfig?.isSubModel) {
|
||||
if (selectedModelConfig && isAiModelAgentAuto(selectedModelConfig)) {
|
||||
shouldRunAgentConversation = true;
|
||||
shouldKeepSelectedModel = false;
|
||||
console.log(
|
||||
`[Smart Auto Mode] 🧠 Agent-only model detected, force auto routing: ${selectedModelConfig.displayName}`
|
||||
);
|
||||
} else if (selectedModelConfig?.supportsImageGen && !selectedModelConfig?.isSubModel) {
|
||||
shouldRunAgentConversation = true;
|
||||
shouldKeepSelectedModel = true;
|
||||
console.log(
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { getEnabledAiModelsWithProvider, getDefaultAiModel } from "@/lib/db/queries";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
import { isAiModelAgentAuto } from "@/lib/db/ai-model-flags";
|
||||
|
||||
// 获取前端可用的 AI 模型列表(需要登录但不需要管理员权限)
|
||||
export async function GET() {
|
||||
@ -14,9 +15,12 @@ export async function GET() {
|
||||
// 只获取主模型(不包括子模型),子模型由后端根据 videoParams.mode 自动选择
|
||||
const modelsWithProvider = await getEnabledAiModelsWithProvider(false);
|
||||
const defaultModel = await getDefaultAiModel();
|
||||
const visibleModelsWithProvider = modelsWithProvider.filter(
|
||||
({ model }) => !isAiModelAgentAuto(model)
|
||||
);
|
||||
|
||||
// 转换为前端需要的格式
|
||||
const models = modelsWithProvider.map(({ model, provider }) => ({
|
||||
const models = visibleModelsWithProvider.map(({ model, provider }) => ({
|
||||
id: model.id,
|
||||
modelId: model.modelId,
|
||||
displayName: model.displayName,
|
||||
@ -31,9 +35,18 @@ export async function GET() {
|
||||
capabilities: model.capabilities, // 🆕 返回子模型的能力配置
|
||||
}));
|
||||
|
||||
const resolvedDefaultModelId =
|
||||
(defaultModel &&
|
||||
!isAiModelAgentAuto(defaultModel) &&
|
||||
models.some((model) => model.id === defaultModel.id)
|
||||
? defaultModel.id
|
||||
: models.find((model) => model.isDefault)?.id) ??
|
||||
models[0]?.id ??
|
||||
null;
|
||||
|
||||
return Response.json({
|
||||
models,
|
||||
defaultModelId: defaultModel?.id ?? models[0]?.id ?? null,
|
||||
defaultModelId: resolvedDefaultModelId,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof ChatSDKError) {
|
||||
@ -42,4 +55,3 @@ export async function GET() {
|
||||
return new ChatSDKError("offline:chat").toResponse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import { DataStreamHandler } from "@/components/data-stream-handler";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
import { auth } from "../(auth)/auth";
|
||||
import { getDefaultAiModel, getEnabledAiModelsWithProvider } from "@/lib/db/queries";
|
||||
import { isAiModelAgentAuto } from "@/lib/db/ai-model-flags";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
@ -33,14 +34,17 @@ async function NewChatPage() {
|
||||
if (!initialModelId) {
|
||||
// 尝试获取默认模型
|
||||
const defaultModel = await getDefaultAiModel();
|
||||
|
||||
if (defaultModel) {
|
||||
|
||||
if (defaultModel && !isAiModelAgentAuto(defaultModel)) {
|
||||
initialModelId = defaultModel.id;
|
||||
} else {
|
||||
// 如果没有设置默认模型,获取第一个启用的模型
|
||||
const enabledModels = await getEnabledAiModelsWithProvider();
|
||||
if (enabledModels.length > 0) {
|
||||
initialModelId = enabledModels[0].model.id;
|
||||
const visibleModels = enabledModels.filter(
|
||||
({ model }) => !isAiModelAgentAuto(model)
|
||||
);
|
||||
if (visibleModels.length > 0) {
|
||||
initialModelId = visibleModels[0].model.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
getAiProviderById,
|
||||
} from "@/lib/db/queries";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
import { isAiModelAgentAuto } from "@/lib/db/ai-model-flags";
|
||||
|
||||
// 🆕 图片生成模型特性配置 Schema
|
||||
const imageGenConfigSchema = z.object({
|
||||
@ -35,6 +36,7 @@ const createModelSchema = z.object({
|
||||
sortOrder: z.number().optional(),
|
||||
fallbackModelId1: z.string().max(128).optional(),
|
||||
fallbackModelId2: z.string().max(128).optional(),
|
||||
isAgentModel: z.boolean().optional(),
|
||||
imageGenConfig: imageGenConfigSchema, // 🆕 图片生成特性配置
|
||||
});
|
||||
|
||||
@ -51,9 +53,17 @@ const updateModelSchema = z.object({
|
||||
sortOrder: z.number().optional(),
|
||||
fallbackModelId1: z.string().max(128).optional(),
|
||||
fallbackModelId2: z.string().max(128).optional(),
|
||||
isAgentModel: z.boolean().optional(),
|
||||
imageGenConfig: imageGenConfigSchema, // 🆕 图片生成特性配置
|
||||
});
|
||||
|
||||
function normalizeModel(model: any) {
|
||||
return {
|
||||
...model,
|
||||
isAgentModel: isAiModelAgentAuto(model),
|
||||
};
|
||||
}
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
await requireAdmin();
|
||||
@ -67,16 +77,16 @@ export async function GET(request: Request) {
|
||||
if (!model) {
|
||||
return new ChatSDKError("bad_request:api", "Model not found").toResponse();
|
||||
}
|
||||
return Response.json({ model });
|
||||
return Response.json({ model: normalizeModel(model) });
|
||||
}
|
||||
|
||||
if (providerId) {
|
||||
const models = await getAiModelsByProviderId({ providerId });
|
||||
return Response.json({ models });
|
||||
return Response.json({ models: models.map(normalizeModel) });
|
||||
}
|
||||
|
||||
const models = await getAllAiModels();
|
||||
return Response.json({ models });
|
||||
return Response.json({ models: models.map(normalizeModel) });
|
||||
} catch (error) {
|
||||
if (error instanceof ChatSDKError) {
|
||||
return error.toResponse();
|
||||
@ -100,7 +110,7 @@ export async function POST(request: Request) {
|
||||
|
||||
const model = await createAiModel(data);
|
||||
|
||||
return Response.json({ model: model[0] });
|
||||
return Response.json({ model: normalizeModel(model[0]) });
|
||||
} catch (error) {
|
||||
if (error instanceof ChatSDKError) {
|
||||
return error.toResponse();
|
||||
@ -129,7 +139,7 @@ export async function PUT(request: Request) {
|
||||
}
|
||||
|
||||
console.log("[Admin API] Model updated successfully:", model[0].id); // Debug log
|
||||
return Response.json({ model: model[0] });
|
||||
return Response.json({ model: normalizeModel(model[0]) });
|
||||
} catch (error) {
|
||||
console.error("[Admin API] Update model error:", error); // Debug log
|
||||
if (error instanceof ChatSDKError) {
|
||||
@ -164,4 +174,3 @@ export async function DELETE(request: Request) {
|
||||
return new ChatSDKError("offline:admin").toResponse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,9 +9,26 @@ import {
|
||||
} from "@/lib/db/queries";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
|
||||
const providerTypes = [
|
||||
"alibaba",
|
||||
"openai",
|
||||
"custom",
|
||||
"local",
|
||||
"gemini",
|
||||
"huanke-gemini",
|
||||
"qianwen-video",
|
||||
"volcano-vision",
|
||||
"volcano-video",
|
||||
"alibaba-image",
|
||||
"ruxa-image",
|
||||
"zhipu",
|
||||
] as const;
|
||||
type ProviderType = (typeof providerTypes)[number];
|
||||
type StoredProviderType = Exclude<ProviderType, "zhipu">;
|
||||
|
||||
const createProviderSchema = z.object({
|
||||
name: z.string().min(1).max(128),
|
||||
type: z.enum(["alibaba", "openai", "custom", "local", "gemini", "huanke-gemini", "qianwen-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image"]),
|
||||
type: z.enum(providerTypes),
|
||||
baseUrl: z.string().min(1), // 改为简单的字符串验证,支持 localhost
|
||||
apiKey: z.string().min(1),
|
||||
isEnabled: z.boolean().optional(),
|
||||
@ -20,12 +37,32 @@ const createProviderSchema = z.object({
|
||||
const updateProviderSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string().min(1).max(128).optional(),
|
||||
type: z.enum(["alibaba", "openai", "custom", "local", "gemini", "huanke-gemini", "qianwen-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image"]).optional(),
|
||||
type: z.enum(providerTypes).optional(),
|
||||
baseUrl: z.string().min(1).optional(), // 改为简单的字符串验证,支持 localhost
|
||||
apiKey: z.string().min(1).optional(),
|
||||
isEnabled: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const ZHIPU_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
|
||||
|
||||
function normalizeProviderTypeForStorage(
|
||||
type?: ProviderType
|
||||
): StoredProviderType | undefined {
|
||||
if (!type) return undefined;
|
||||
return type === "zhipu" ? "custom" : type;
|
||||
}
|
||||
|
||||
function isZhipuBaseUrl(baseUrl: string) {
|
||||
return baseUrl.includes("open.bigmodel.cn");
|
||||
}
|
||||
|
||||
function normalizeProviderTypeForResponse(provider: Record<string, any>) {
|
||||
if (provider.type === "custom" && typeof provider.baseUrl === "string" && isZhipuBaseUrl(provider.baseUrl)) {
|
||||
return { ...provider, type: "zhipu" };
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
function maskApiKey(apiKey?: string) {
|
||||
if (!apiKey) return undefined;
|
||||
if (apiKey.length <= 8) return "****";
|
||||
@ -55,20 +92,22 @@ export async function GET(request: Request) {
|
||||
}
|
||||
// 隐藏 API Key 的部分内容
|
||||
return Response.json({
|
||||
provider: {
|
||||
provider: normalizeProviderTypeForResponse({
|
||||
...provider,
|
||||
apiKey: provider.apiKey.slice(0, 8) + "****" + provider.apiKey.slice(-4),
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
const providers = await getAllAiProviders();
|
||||
// 隐藏 API Key 的部分内容
|
||||
return Response.json({
|
||||
providers: providers.map((p) => ({
|
||||
...p,
|
||||
apiKey: p.apiKey.slice(0, 8) + "****" + p.apiKey.slice(-4),
|
||||
})),
|
||||
providers: providers.map((p) =>
|
||||
normalizeProviderTypeForResponse({
|
||||
...p,
|
||||
apiKey: p.apiKey.slice(0, 8) + "****" + p.apiKey.slice(-4),
|
||||
})
|
||||
),
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof ChatSDKError) {
|
||||
@ -87,14 +126,17 @@ export async function POST(request: Request) {
|
||||
|
||||
const data = createProviderSchema.parse(body);
|
||||
console.log("[Admin API] Validation passed, creating provider...");
|
||||
const storageType = normalizeProviderTypeForStorage(data.type) ?? "custom";
|
||||
|
||||
const provider = await createAiProvider({
|
||||
...data,
|
||||
type: storageType,
|
||||
baseUrl: data.type === "zhipu" ? data.baseUrl || ZHIPU_BASE_URL : data.baseUrl,
|
||||
updatedBy: session.user.id,
|
||||
});
|
||||
|
||||
console.log("[Admin API] Provider created successfully:", provider[0].id);
|
||||
return Response.json({ provider: provider[0] });
|
||||
return Response.json({ provider: normalizeProviderTypeForResponse(provider[0] as any) });
|
||||
} catch (error) {
|
||||
console.error("[Admin API] Create provider error:", error);
|
||||
if (error instanceof ChatSDKError) {
|
||||
@ -117,6 +159,7 @@ export async function PUT(request: Request) {
|
||||
|
||||
const provider = await updateAiProvider({
|
||||
...data,
|
||||
type: normalizeProviderTypeForStorage(data.type),
|
||||
updatedBy: session.user.id,
|
||||
});
|
||||
|
||||
@ -124,7 +167,7 @@ export async function PUT(request: Request) {
|
||||
return new ChatSDKError("bad_request:api", "Provider not found").toResponse();
|
||||
}
|
||||
|
||||
return Response.json({ provider: provider[0] });
|
||||
return Response.json({ provider: normalizeProviderTypeForResponse(provider[0] as any) });
|
||||
} catch (error) {
|
||||
if (error instanceof ChatSDKError) {
|
||||
return error.toResponse();
|
||||
|
||||
@ -1058,6 +1058,26 @@ function PureModelSelectorCompact({
|
||||
setOptimisticModelId(selectedModelId);
|
||||
}, [selectedModelId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (displayModels.length === 0) return;
|
||||
|
||||
const currentExists = displayModels.some((m) => m.id === selectedModelId);
|
||||
if (currentExists) return;
|
||||
|
||||
const fallbackModelId =
|
||||
dynamicModels.find((m) => m.isDefault)?.id ??
|
||||
dynamicModels[0]?.id ??
|
||||
(showAutoModel ? "auto" : "");
|
||||
|
||||
if (!fallbackModelId) return;
|
||||
|
||||
setOptimisticModelId(fallbackModelId);
|
||||
onModelChange?.(fallbackModelId);
|
||||
startTransition(() => {
|
||||
saveChatModelAsCookie(fallbackModelId);
|
||||
});
|
||||
}, [displayModels, dynamicModels, onModelChange, selectedModelId, showAutoModel]);
|
||||
|
||||
// 自动选择模型的逻辑 - 必须在所有条件 return 之前定义
|
||||
const selectBestModel = useCallback(() => {
|
||||
const hasAttachments = attachments && attachments.length > 0;
|
||||
|
||||
@ -65,16 +65,25 @@ async function generateAgentConversation(
|
||||
state: AutoAgentStateType
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const { getEnabledAiModelsWithProvider } = await import("@/lib/db/queries");
|
||||
const {
|
||||
getEnabledAiModelsWithProvider,
|
||||
getAgentAiModelWithProvider,
|
||||
} = await import("@/lib/db/queries");
|
||||
const { getLanguageModelById } = await import("./dynamic-provider");
|
||||
const allModels = await getEnabledAiModelsWithProvider();
|
||||
const agentModel = await getAgentAiModelWithProvider();
|
||||
const canUseAgentModel = Boolean(
|
||||
agentModel && (!state.hasImage || agentModel.model.supportsVision)
|
||||
);
|
||||
|
||||
const preferredVisionModel = allModels.find(
|
||||
(m) => m.provider.type === "alibaba" && m.model.supportsVision && m.model.isEnabled
|
||||
);
|
||||
const candidateModel = state.hasImage
|
||||
? preferredVisionModel || allModels.find((m) => m.model.supportsVision && m.model.isEnabled)
|
||||
: allModels.find((m) => m.model.isEnabled && !m.model.isSubModel);
|
||||
const candidateModel =
|
||||
(canUseAgentModel ? agentModel : undefined) ||
|
||||
(state.hasImage
|
||||
? preferredVisionModel || allModels.find((m) => m.model.supportsVision && m.model.isEnabled)
|
||||
: allModels.find((m) => m.model.isEnabled && !m.model.isSubModel));
|
||||
|
||||
if (!candidateModel) {
|
||||
return analysis.conversationalReply || null;
|
||||
|
||||
@ -66,6 +66,17 @@ function getOrCreateProvider(provider: Pick<AiProvider, "id" | "type" | "baseUrl
|
||||
break;
|
||||
}
|
||||
|
||||
case "zhipu": {
|
||||
providerInstance = createOpenAI({
|
||||
apiKey: provider.apiKey,
|
||||
baseURL: provider.baseUrl || "https://open.bigmodel.cn/api/paas/v4",
|
||||
});
|
||||
console.log(
|
||||
`[Provider] Created Zhipu Open Platform provider: ${provider.baseUrl || "https://open.bigmodel.cn/api/paas/v4"}`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case "local": {
|
||||
// 本地部署的模型(如千问2.5-VL)
|
||||
// 使用自定义的本地模型接口,不兼容 OpenAI 格式
|
||||
|
||||
@ -110,11 +110,16 @@ export async function routeToModel(params: {
|
||||
|
||||
// 使用动态导入避免循环依赖
|
||||
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 allModels = await getEnabledAiModelsWithProvider();
|
||||
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);
|
||||
|
||||
// 获取意图对应的配置
|
||||
|
||||
@ -294,21 +294,26 @@ export async function optimizePromptWithLLM(params: {
|
||||
|
||||
try {
|
||||
// 动态导入避免循环依赖
|
||||
const { getEnabledAiModelsWithProvider } = await import("@/lib/db/queries");
|
||||
const {
|
||||
getEnabledAiModelsWithProvider,
|
||||
getAgentAiModelWithProvider,
|
||||
} = await import("@/lib/db/queries");
|
||||
const { getLanguageModelById } = await import("./dynamic-provider");
|
||||
|
||||
// 获取千问3 VL 模型
|
||||
const allModels = await getEnabledAiModelsWithProvider();
|
||||
const agentModel = await getAgentAiModelWithProvider();
|
||||
const qwenModel = allModels.find(
|
||||
(m) => m.provider.type === "alibaba" && m.model.supportsVision
|
||||
);
|
||||
const selectedOptimizerModel = agentModel || qwenModel;
|
||||
|
||||
if (!qwenModel) {
|
||||
if (!selectedOptimizerModel) {
|
||||
console.log("[Prompt Optimizer LLM] No Qwen3 VL model, falling back to template");
|
||||
return optimizePrompt({ originalPrompt, intent, subIntent });
|
||||
}
|
||||
|
||||
const model = await getLanguageModelById(qwenModel.model.id);
|
||||
const model = await getLanguageModelById(selectedOptimizerModel.model.id);
|
||||
if (!model) {
|
||||
console.log("[Prompt Optimizer LLM] Failed to get model, falling back to template");
|
||||
return optimizePrompt({ originalPrompt, intent, subIntent });
|
||||
|
||||
@ -206,21 +206,29 @@ async function analyzWithLLM(
|
||||
context?: string
|
||||
): Promise<AnalysisResult | null> {
|
||||
// 使用动态导入避免循环依赖问题
|
||||
const { getEnabledAiModelsWithProvider } = await import("@/lib/db/queries");
|
||||
const {
|
||||
getEnabledAiModelsWithProvider,
|
||||
getAgentAiModelWithProvider,
|
||||
} = await import("@/lib/db/queries");
|
||||
const { getLanguageModelById } = await import("./dynamic-provider");
|
||||
|
||||
// 获取千问3 VL 模型
|
||||
const allModels = await getEnabledAiModelsWithProvider();
|
||||
const agentModel = await getAgentAiModelWithProvider();
|
||||
const qwenModel = allModels.find(
|
||||
(m) => m.provider.type === "alibaba" && m.model.supportsVision
|
||||
);
|
||||
const selectedAnalyzerModel =
|
||||
(agentModel && (!hasImage || agentModel.model.supportsVision)
|
||||
? agentModel
|
||||
: undefined) || qwenModel;
|
||||
|
||||
if (!qwenModel) {
|
||||
if (!selectedAnalyzerModel) {
|
||||
console.log("[Smart Analyzer] No Qwen3 VL model found");
|
||||
return null;
|
||||
}
|
||||
|
||||
const model = await getLanguageModelById(qwenModel.model.id);
|
||||
const model = await getLanguageModelById(selectedAnalyzerModel.model.id);
|
||||
if (!model) {
|
||||
console.log("[Smart Analyzer] Failed to get language model");
|
||||
return null;
|
||||
|
||||
40
lib/db/ai-model-flags.ts
Normal file
40
lib/db/ai-model-flags.ts
Normal file
@ -0,0 +1,40 @@
|
||||
const AGENT_AUTO_MODEL_KEY = "agentAutoModel";
|
||||
|
||||
function toRecord(value: unknown): Record<string, unknown> | null {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return null;
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
export function isAiModelAgentAuto(input: {
|
||||
defaultParams?: unknown;
|
||||
capabilities?: unknown;
|
||||
}): boolean {
|
||||
const defaultParams = toRecord(input.defaultParams);
|
||||
if (typeof defaultParams?.[AGENT_AUTO_MODEL_KEY] === "boolean") {
|
||||
return defaultParams[AGENT_AUTO_MODEL_KEY] as boolean;
|
||||
}
|
||||
|
||||
const capabilities = toRecord(input.capabilities);
|
||||
if (typeof capabilities?.[AGENT_AUTO_MODEL_KEY] === "boolean") {
|
||||
return capabilities[AGENT_AUTO_MODEL_KEY] as boolean;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function withAgentAutoModelFlag(
|
||||
defaultParams: unknown,
|
||||
enabled: boolean
|
||||
): Record<string, unknown> | null {
|
||||
const next = { ...(toRecord(defaultParams) || {}) };
|
||||
|
||||
if (enabled) {
|
||||
next[AGENT_AUTO_MODEL_KEY] = true;
|
||||
} else {
|
||||
delete next[AGENT_AUTO_MODEL_KEY];
|
||||
}
|
||||
|
||||
return Object.keys(next).length > 0 ? next : null;
|
||||
}
|
||||
@ -28,7 +28,7 @@ const runMigrate = async () => {
|
||||
ADD CONSTRAINT "AiProvider_type_check"
|
||||
CHECK (type IN (
|
||||
'alibaba', 'openai', 'custom', 'local', 'gemini',
|
||||
'huanke-gemini', 'qianwen-video', 'volcano-vision', 'volcano-video', 'alibaba-image', 'ruxa-image'
|
||||
'huanke-gemini', 'qianwen-video', 'volcano-vision', 'volcano-video', 'alibaba-image', 'ruxa-image', 'zhipu'
|
||||
));
|
||||
`);
|
||||
const end = Date.now();
|
||||
|
||||
@ -51,6 +51,10 @@ import {
|
||||
generatedImage,
|
||||
} from "./schema";
|
||||
import { generateHashedPassword } from "./utils";
|
||||
import {
|
||||
isAiModelAgentAuto,
|
||||
withAgentAutoModelFlag,
|
||||
} from "./ai-model-flags";
|
||||
|
||||
// Optionally, if not using email/pass login, you can
|
||||
// use the Drizzle adapter for Auth.js / NextAuth
|
||||
@ -941,7 +945,7 @@ export async function createAiProvider({
|
||||
updatedBy,
|
||||
}: {
|
||||
name: string;
|
||||
type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image";
|
||||
type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "zhipu";
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
isEnabled?: boolean;
|
||||
@ -979,7 +983,7 @@ export async function updateAiProvider({
|
||||
}: {
|
||||
id: string;
|
||||
name?: string;
|
||||
type?: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image";
|
||||
type?: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "zhipu";
|
||||
baseUrl?: string;
|
||||
apiKey?: string;
|
||||
isEnabled?: boolean;
|
||||
@ -1126,6 +1130,7 @@ export async function createAiModel({
|
||||
fallbackModelId1,
|
||||
fallbackModelId2,
|
||||
imageGenConfig,
|
||||
isAgentModel,
|
||||
}: {
|
||||
providerId: string;
|
||||
modelId: string;
|
||||
@ -1139,6 +1144,7 @@ export async function createAiModel({
|
||||
fallbackModelId1?: string;
|
||||
fallbackModelId2?: string;
|
||||
imageGenConfig?: ImageGenConfig;
|
||||
isAgentModel?: boolean;
|
||||
}): Promise<AiModel[]> {
|
||||
try {
|
||||
// 如果设置为默认,先取消其他默认
|
||||
@ -1164,6 +1170,10 @@ export async function createAiModel({
|
||||
fallbackModelId1: fallbackModelId1 || null,
|
||||
fallbackModelId2: fallbackModelId2 || null,
|
||||
imageGenConfig: imageGenConfig || null,
|
||||
defaultParams:
|
||||
isAgentModel === undefined
|
||||
? null
|
||||
: withAgentAutoModelFlag(null, isAgentModel),
|
||||
})
|
||||
.returning();
|
||||
} catch (_error) {
|
||||
@ -1188,6 +1198,7 @@ export async function updateAiModel({
|
||||
fallbackModelId1,
|
||||
fallbackModelId2,
|
||||
imageGenConfig,
|
||||
isAgentModel,
|
||||
}: {
|
||||
id: string;
|
||||
providerId?: string;
|
||||
@ -1202,6 +1213,7 @@ export async function updateAiModel({
|
||||
fallbackModelId1?: string;
|
||||
fallbackModelId2?: string;
|
||||
imageGenConfig?: ImageGenConfig;
|
||||
isAgentModel?: boolean;
|
||||
}): Promise<AiModel[]> {
|
||||
try {
|
||||
// 如果设置为默认,先取消其他默认
|
||||
@ -1228,6 +1240,22 @@ export async function updateAiModel({
|
||||
if (fallbackModelId2 !== undefined) updateData.fallbackModelId2 = fallbackModelId2 || null;
|
||||
// 🆕 图片生成配置
|
||||
if (imageGenConfig !== undefined) updateData.imageGenConfig = imageGenConfig || null;
|
||||
if (isAgentModel !== undefined) {
|
||||
const [existingModel] = await db
|
||||
.select({ defaultParams: aiModel.defaultParams })
|
||||
.from(aiModel)
|
||||
.where(eq(aiModel.id, id))
|
||||
.limit(1);
|
||||
|
||||
if (!existingModel) {
|
||||
return [];
|
||||
}
|
||||
|
||||
updateData.defaultParams = withAgentAutoModelFlag(
|
||||
existingModel.defaultParams,
|
||||
isAgentModel
|
||||
);
|
||||
}
|
||||
|
||||
return await db
|
||||
.update(aiModel)
|
||||
@ -1476,6 +1504,20 @@ export async function getEnabledAiModelsWithProvider(includeSubModels: boolean =
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAgentAiModelWithProvider() {
|
||||
const models = await getEnabledAiModelsWithProvider(false);
|
||||
const candidates = models
|
||||
.filter(({ model }) => isAiModelAgentAuto(model))
|
||||
.sort((a, b) => {
|
||||
if (a.model.isDefault !== b.model.isDefault) {
|
||||
return Number(b.model.isDefault) - Number(a.model.isDefault);
|
||||
}
|
||||
return (a.model.sortOrder ?? 0) - (b.model.sortOrder ?? 0);
|
||||
});
|
||||
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 获取指定父模型的所有子模型
|
||||
* 用于火山引擎视频等需要智能选择模型的场景
|
||||
|
||||
@ -215,7 +215,7 @@ export type ToolConfig = InferSelectModel<typeof toolConfig>;
|
||||
export const aiProvider = pgTable("AiProvider", {
|
||||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||||
name: varchar("name", { length: 128 }).notNull(), // 提供商名称,如 "阿里云通义千问"
|
||||
type: varchar("type", { enum: ["alibaba", "openai", "custom", "local", "gemini", "huanke-gemini", "qianwen-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image"] })
|
||||
type: varchar("type", { enum: ["alibaba", "openai", "custom", "local", "gemini", "huanke-gemini", "qianwen-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image", "zhipu"] })
|
||||
.notNull()
|
||||
.default("custom"), // 提供商类型
|
||||
baseUrl: text("baseUrl").notNull(), // API 基础 URL
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user