fix: force canvas vision tasks to qwen vl models
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-02-20 22:10:15 +08:00
parent 7141704f65
commit 4222e723b0
2 changed files with 42 additions and 69 deletions

View File

@ -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: "未命名画布" });
}

View File

@ -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),
}
);