fix: force canvas vision tasks to qwen vl models
This commit is contained in:
parent
7141704f65
commit
4222e723b0
@ -1,12 +1,8 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { generateText } from "ai";
|
||||
import {
|
||||
getAgentAiModelWithProvider,
|
||||
getEnabledAiModelsWithProvider,
|
||||
} from "@/lib/db/queries";
|
||||
import { getEnabledAiModelsWithProvider } from "@/lib/db/queries";
|
||||
import { getLanguageModelById } from "@/lib/ai/dynamic-provider";
|
||||
import { isAiModelMultimodal } from "@/lib/db/ai-model-flags";
|
||||
|
||||
const BLOCKED_PROVIDER_TYPES = new Set([
|
||||
"fal-video",
|
||||
@ -21,41 +17,33 @@ const BLOCKED_PROVIDER_TYPES = new Set([
|
||||
|
||||
type VisionModelItem = Awaited<ReturnType<typeof getEnabledAiModelsWithProvider>>[number];
|
||||
|
||||
function isVisionCandidate(item: VisionModelItem) {
|
||||
function isQwenVisionModel(item: VisionModelItem) {
|
||||
const providerType = String(item.provider?.type || "");
|
||||
if (BLOCKED_PROVIDER_TYPES.has(providerType)) return false;
|
||||
|
||||
if (item.model.supportsVision) return true;
|
||||
if (isAiModelMultimodal(item.model)) return true;
|
||||
|
||||
const modelText = `${item.model.modelId || ""} ${item.model.displayName || ""}`.toLowerCase();
|
||||
if (/vl|vision|多模态|glm-4v|gpt-4o|doubao-seed-2-0|seed-2-0/.test(modelText)) {
|
||||
return true;
|
||||
}
|
||||
return /qwen.*vl|通义.*vl|千问.*vl/.test(modelText);
|
||||
}
|
||||
|
||||
return false;
|
||||
function isQwen25VisionModel(item: VisionModelItem) {
|
||||
if (!isQwenVisionModel(item)) return false;
|
||||
const modelText = `${item.model.modelId || ""} ${item.model.displayName || ""}`.toLowerCase();
|
||||
return /qwen[-_. ]?2\.?5[-_. ]?vl|qwen2\.5[-_. ]?vl/.test(modelText);
|
||||
}
|
||||
|
||||
function scoreVisionCandidate(item: VisionModelItem) {
|
||||
let score = 0;
|
||||
const providerType = String(item.provider?.type || "");
|
||||
if (isAiModelMultimodal(item.model)) score += 220;
|
||||
if (item.model.supportsVision) score += 160;
|
||||
if (providerType === "volcano-multimodal") score += 80;
|
||||
if (providerType === "zhipu") score += 50;
|
||||
if (providerType === "alibaba") score += 40;
|
||||
if (providerType === "custom") score += 30;
|
||||
if (isQwen25VisionModel(item)) score += 400;
|
||||
if (item.model.supportsVision) score += 120;
|
||||
if (providerType === "alibaba") score += 80;
|
||||
if (providerType === "custom") score += 40;
|
||||
if (item.model.isDefault) score += 20;
|
||||
score -= Math.max(0, item.model.sortOrder ?? 0);
|
||||
return score;
|
||||
}
|
||||
|
||||
function pickAutoTitleModel(allModels: VisionModelItem[], agentModel: VisionModelItem | undefined) {
|
||||
if (agentModel && isVisionCandidate(agentModel)) {
|
||||
return agentModel;
|
||||
}
|
||||
|
||||
const candidates = allModels.filter((item) => isVisionCandidate(item));
|
||||
function pickAutoTitleModel(allModels: VisionModelItem[]) {
|
||||
const candidates = allModels.filter((item) => isQwenVisionModel(item));
|
||||
if (candidates.length === 0) return null;
|
||||
return [...candidates].sort((a, b) => scoreVisionCandidate(b) - scoreVisionCandidate(a))[0];
|
||||
}
|
||||
@ -77,25 +65,23 @@ export async function POST(request: NextRequest) {
|
||||
return NextResponse.json({ error: "Image URL is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
// 从数据库获取视觉模型,优先使用 AGENT 多模态模型
|
||||
// 固定使用 Qwen2.5-VL(若未配置则降级到其它 Qwen-VL)
|
||||
const modelsData = await getEnabledAiModelsWithProvider();
|
||||
const agentModel = await getAgentAiModelWithProvider();
|
||||
|
||||
console.log("[Auto-title] Available models count:", modelsData.length);
|
||||
console.log("[Auto-title] All model IDs:", modelsData.map(m => m.model.modelId));
|
||||
|
||||
const visionModelData = pickAutoTitleModel(modelsData, agentModel);
|
||||
const visionModelData = pickAutoTitleModel(modelsData);
|
||||
|
||||
console.log("[Auto-title] Selected vision model:", {
|
||||
modelId: visionModelData?.model.modelId,
|
||||
displayName: visionModelData?.model.displayName,
|
||||
providerType: visionModelData?.provider?.type,
|
||||
usedAgentModel: Boolean(agentModel && visionModelData && agentModel.model.id === visionModelData.model.id),
|
||||
isMultimodal: Boolean(visionModelData && isAiModelMultimodal(visionModelData.model)),
|
||||
isQwen25: Boolean(visionModelData && isQwen25VisionModel(visionModelData)),
|
||||
});
|
||||
|
||||
if (!visionModelData) {
|
||||
console.log("[Auto-title] No compatible vision model found, using default title");
|
||||
console.log("[Auto-title] No Qwen VL model found, using default title");
|
||||
return NextResponse.json({ title: "未命名画布" });
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { generateText } from "ai";
|
||||
import { getAgentAiModelWithProvider, getEnabledAiModelsWithProvider } from "@/lib/db/queries";
|
||||
import { getEnabledAiModelsWithProvider } from "@/lib/db/queries";
|
||||
import { getLanguageModelById } from "@/lib/ai/dynamic-provider";
|
||||
import { isAiModelMultimodal } from "@/lib/db/ai-model-flags";
|
||||
import { uploadFile } from "@/lib/storage/service";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
@ -40,45 +39,36 @@ const BLOCKED_PROVIDER_TYPES = new Set([
|
||||
"doubao-video",
|
||||
]);
|
||||
|
||||
function isVisionCandidate(item: Awaited<ReturnType<typeof getEnabledAiModelsWithProvider>>[number]) {
|
||||
type VisionModelItem = Awaited<ReturnType<typeof getEnabledAiModelsWithProvider>>[number];
|
||||
|
||||
function isQwenVisionModel(item: VisionModelItem) {
|
||||
if (BLOCKED_PROVIDER_TYPES.has(String(item.provider?.type || ""))) return false;
|
||||
|
||||
if (item.model.supportsVision) return true;
|
||||
if (isAiModelMultimodal(item.model)) return true;
|
||||
|
||||
const modelText = `${item.model.displayName || ""} ${item.model.modelId || ""}`.toLowerCase();
|
||||
if (/vl|vision|多模态|glm-4v|gpt-4o|doubao-seed-2-0|seed-2-0/.test(modelText)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return /qwen.*vl|通义.*vl|千问.*vl/.test(modelText);
|
||||
}
|
||||
|
||||
function scoreVisionCandidate(item: Awaited<ReturnType<typeof getEnabledAiModelsWithProvider>>[number]) {
|
||||
function isQwen25VisionModel(item: VisionModelItem) {
|
||||
if (!isQwenVisionModel(item)) return false;
|
||||
const modelText = `${item.model.displayName || ""} ${item.model.modelId || ""}`.toLowerCase();
|
||||
return /qwen[-_. ]?2\.?5[-_. ]?vl|qwen2\.5[-_. ]?vl/.test(modelText);
|
||||
}
|
||||
|
||||
function scoreQwenVisionCandidate(item: VisionModelItem) {
|
||||
let score = 0;
|
||||
if (isAiModelMultimodal(item.model)) score += 200;
|
||||
if (item.model.supportsVision) score += 160;
|
||||
if (isQwen25VisionModel(item)) score += 400;
|
||||
if (item.model.supportsVision) score += 120;
|
||||
const providerType = String(item.provider?.type || "");
|
||||
if (providerType === "volcano-multimodal") score += 80;
|
||||
if (providerType === "zhipu") score += 40;
|
||||
if (providerType === "alibaba") score += 30;
|
||||
if (providerType === "alibaba") score += 80;
|
||||
if (providerType === "custom") score += 40;
|
||||
if (item.model.isDefault) score += 20;
|
||||
score -= Math.max(0, item.model.sortOrder ?? 0);
|
||||
return score;
|
||||
}
|
||||
|
||||
function pickObjectVisionModel(
|
||||
allModels: Awaited<ReturnType<typeof getEnabledAiModelsWithProvider>>,
|
||||
agentModel: Awaited<ReturnType<typeof getAgentAiModelWithProvider>>,
|
||||
) {
|
||||
if (agentModel && isVisionCandidate(agentModel)) {
|
||||
return agentModel;
|
||||
}
|
||||
|
||||
const candidates = allModels.filter((item) => isVisionCandidate(item));
|
||||
function pickObjectVisionModel(allModels: VisionModelItem[]) {
|
||||
const candidates = allModels.filter((item) => isQwenVisionModel(item));
|
||||
if (candidates.length === 0) return null;
|
||||
|
||||
return [...candidates].sort((a, b) => scoreVisionCandidate(b) - scoreVisionCandidate(a))[0];
|
||||
return [...candidates].sort((a, b) => scoreQwenVisionCandidate(b) - scoreQwenVisionCandidate(a))[0];
|
||||
}
|
||||
|
||||
function inferFileExtFromMime(mimeType: string) {
|
||||
@ -134,16 +124,15 @@ export async function POST(request: Request) {
|
||||
|
||||
const normalizedImage = await normalizeImageInput(imageData);
|
||||
|
||||
// 优先使用「AGENT + 多模态/视觉」模型
|
||||
// 固定使用 Qwen2.5-VL(若未配置则降级到其它 Qwen-VL)
|
||||
const allModels = await getEnabledAiModelsWithProvider();
|
||||
const agentModel = await getAgentAiModelWithProvider();
|
||||
const selectedModel = pickObjectVisionModel(allModels, agentModel);
|
||||
const selectedModel = pickObjectVisionModel(allModels);
|
||||
|
||||
if (!selectedModel) {
|
||||
console.log("[PickObject] No vision model found");
|
||||
console.log("[PickObject] No Qwen VL model found");
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '未配置可用的视觉/多模态模型',
|
||||
error: '未配置可用的 Qwen-VL 模型(请配置 qwen2.5-vl)',
|
||||
}, { status: 500 });
|
||||
}
|
||||
|
||||
@ -153,9 +142,7 @@ export async function POST(request: Request) {
|
||||
selectedModel.model.displayName,
|
||||
selectedModel.provider.type,
|
||||
{
|
||||
isAgentModel:
|
||||
agentModel?.model?.id === selectedModel.model.id,
|
||||
isMultimodal: isAiModelMultimodal(selectedModel.model),
|
||||
isQwen25: isQwen25VisionModel(selectedModel),
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user