ai-chatbot/lib/ai/qwen25-vl.ts
Your Name 5c2fd46534
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
fix: hardcode canvas vision routes to qwen2.5-vl with alibaba-image key
2026-02-20 22:14:59 +08:00

117 lines
3.2 KiB
TypeScript

import "server-only";
import { getEnabledAiProviders } from "@/lib/db/queries";
const QWEN25_VL_MODELS = [
"qwen2.5-vl-72b-instruct",
"qwen2.5-vl-7b-instruct",
] as const;
const QWEN_COMPAT_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
function parseAssistantText(payload: any): string {
const choices = Array.isArray(payload?.choices) ? payload.choices : [];
const content = choices[0]?.message?.content;
if (typeof content === "string") {
return content.trim();
}
if (Array.isArray(content)) {
const chunks = content
.map((part: any) => (typeof part?.text === "string" ? part.text.trim() : ""))
.filter(Boolean);
return chunks.join("\n").trim();
}
return "";
}
async function getQwenImageProviderApiKey() {
const providers = await getEnabledAiProviders();
const provider = providers.find(
(item) => item.type === "alibaba-image" && typeof item.apiKey === "string" && item.apiKey.trim(),
);
if (!provider?.apiKey) {
throw new Error("未配置可用的千问生图 Provider API Key");
}
return provider.apiKey.trim();
}
export async function callQwen25Vl(params: {
imageUrl: string;
prompt: string;
temperature?: number;
maxTokens?: number;
}) {
const apiKey = await getQwenImageProviderApiKey();
const endpoint = `${QWEN_COMPAT_BASE_URL}/chat/completions`;
let lastError: Error | null = null;
for (const model of QWEN25_VL_MODELS) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000);
try {
const response = await fetch(endpoint, {
method: "POST",
signal: controller.signal,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
model,
temperature: params.temperature ?? 0.1,
max_tokens: params.maxTokens ?? 300,
messages: [
{
role: "user",
content: [
{ type: "text", text: params.prompt },
{
type: "image_url",
image_url: {
url: params.imageUrl,
},
},
],
},
],
}),
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
const message =
payload?.error?.message || payload?.message || `HTTP ${response.status}`;
throw new Error(message);
}
const text = parseAssistantText(payload);
if (!text) {
throw new Error("Qwen2.5-VL 未返回文本内容");
}
return { text, model };
} finally {
clearTimeout(timeout);
}
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
const errorText = String(lastError.message || "");
const isModelNotFound =
/model.*not.*found|invalid model|not support/i.test(errorText);
if (!isModelNotFound) {
break;
}
}
}
throw lastError || new Error("Qwen2.5-VL 调用失败");
}