fix(agent): use remote image urls for doubao multimodal vision
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-19 02:42:45 +08:00
parent 27b0a0e4e6
commit 485571b8eb
2 changed files with 95 additions and 8 deletions

View File

@ -147,8 +147,19 @@ async function toAgentVisionDataUrl(imageUrl: string): Promise<string | null> {
}
}
async function buildAgentVisionInputs(imageUrls: string[]) {
async function buildAgentVisionInputs(
imageUrls: string[],
options?: {
preferRemoteUrls?: boolean;
},
) {
if (imageUrls.length === 0) return [] as Array<{ type: "image"; image: string }>;
if (options?.preferRemoteUrls) {
return imageUrls.map((url) => ({
type: "image" as const,
image: url,
}));
}
const convertedResults = await Promise.all(imageUrls.map((url) => toAgentVisionDataUrl(url)));
const inlineCount = convertedResults.filter(Boolean).length;
@ -492,7 +503,7 @@ function isLikelyVisionCapableModel(item: EnabledModelWithProvider) {
if (item.model.supportsVision) return true;
if (isAiModelMultimodal(item.model)) return true;
const providerType = item.provider.type || "";
const providerType = String(item.provider.type || "");
const modelText = `${item.model.displayName || ""} ${item.model.modelId || ""}`.toLowerCase();
if (providerType === "volcano-multimodal") return true;
@ -523,6 +534,24 @@ function isLikelyVisionCapableModel(item: EnabledModelWithProvider) {
return false;
}
function shouldPreferRemoteVisionUrls(item?: EnabledModelWithProvider) {
if (!item) return false;
const providerType = String(item.provider.type || "");
const baseUrl = String((item.provider as { baseUrl?: string | null })?.baseUrl || "").toLowerCase();
const modelText = `${item.model.displayName || ""} ${item.model.modelId || ""}`.toLowerCase();
if (providerType === "volcano-multimodal" || providerType === "doubao-video" || providerType === "volcano-video") {
return true;
}
if (baseUrl.includes("ark.cn-beijing.volces.com")) {
return true;
}
if (/(doubao[-_.]?seed[-_.]?2[-_.]?0|seed[-_.]?2[-_.]?0)/i.test(modelText)) {
return true;
}
return false;
}
function pickQwenVisionModel(models: EnabledModelWithProvider[]) {
const visionModels = models.filter((item) => isLikelyVisionCapableModel(item));
const zhipuVision = visionModels.find(
@ -651,7 +680,10 @@ async function generateAgentConversation(
2) 只写客观可见信息,不要编造品牌、尺寸、镜头参数。
3) 全部输出控制在 6 行内。`;
const visionInputs = await buildAgentVisionInputs(validImageUrls);
const preferRemoteVisionUrls = shouldPreferRemoteVisionUrls(visionModelConfig);
const visionInputs = await buildAgentVisionInputs(validImageUrls, {
preferRemoteUrls: preferRemoteVisionUrls,
});
const { text: visionText } = await generateText({
model: visionModel,
messages: [
@ -666,7 +698,7 @@ async function generateAgentConversation(
visualAnalysis = visionText.trim();
console.log(
`[Auto Agent] Vision analysis model: ${visionModelConfig.model.displayName} (${visionModelConfig.provider.type})`
`[Auto Agent] Vision analysis model: ${visionModelConfig.model.displayName} (${visionModelConfig.provider.type}), preferRemote=${preferRemoteVisionUrls}`
);
} catch (error) {
console.warn("[Auto Agent] Vision analysis failed, continue without visual summary");

View File

@ -324,19 +324,68 @@ async function buildAnalyzerVisionImages(imageUrls?: string[]) {
});
}
function shouldPreferRemoteVisionUrls(modelWithProvider?: {
model?: {
displayName?: string | null;
modelId?: string | null;
} | null;
provider?: {
type?: string | null;
baseUrl?: string | null;
} | null;
}): boolean {
if (!modelWithProvider) return false;
const providerType = String(modelWithProvider.provider?.type || "");
const baseUrl = String(modelWithProvider.provider?.baseUrl || "").toLowerCase();
const modelText = `${modelWithProvider.model?.displayName || ""} ${modelWithProvider.model?.modelId || ""}`.toLowerCase();
if (providerType === "volcano-multimodal" || providerType === "doubao-video" || providerType === "volcano-video") {
return true;
}
if (baseUrl.includes("ark.cn-beijing.volces.com")) {
return true;
}
if (/(doubao[-_.]?seed[-_.]?2[-_.]?0|seed[-_.]?2[-_.]?0)/i.test(modelText)) {
return true;
}
return false;
}
async function buildAnalyzerVisionImagesByMode(params: {
imageUrls?: string[];
preferRemoteUrls: boolean;
}) {
const urls = (params.imageUrls || [])
.filter((url) => isHttpUrl(url))
.slice(0, ANALYZER_MAX_IMAGE_COUNT);
if (urls.length === 0) return [] as Array<{ type: "image"; image: string }>;
if (params.preferRemoteUrls) {
return urls.map((url) => ({ type: "image" as const, image: url }));
}
return buildAnalyzerVisionImages(urls);
}
function parseAnalysisResultFromModelText(
responseText: string,
userText: string,
hasImage: boolean,
): AnalysisResult | null {
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
if (!jsonMatch) return null;
if (!jsonMatch) {
console.warn("[Smart Analyzer] Model response missing JSON:", responseText.slice(0, 240));
return null;
}
try {
const result = JSON.parse(jsonMatch[0]) as AnalysisResult;
return normalizeIntentForImageContext(result, userText, hasImage);
} catch (error) {
console.error("[Smart Analyzer] Failed to parse model response:", error);
console.error(
"[Smart Analyzer] Failed to parse model response:",
error,
"raw=",
jsonMatch[0].slice(0, 240),
);
return null;
}
}
@ -780,11 +829,17 @@ ${historyText}
? `${contextMessage}用户上传了图片,请求内容:"${text}"`
: `${contextMessage}用户请求(无图片):"${text}"`;
const visionImages = hasImage ? await buildAnalyzerVisionImages(imageUrls) : [];
const preferRemoteVisionUrls = shouldPreferRemoteVisionUrls(selectedAnalyzerModel);
const visionImages = hasImage
? await buildAnalyzerVisionImagesByMode({
imageUrls,
preferRemoteUrls: preferRemoteVisionUrls,
})
: [];
if (visionImages.length > 0) {
const inlineCount = visionImages.filter((item) => item.image.startsWith("data:image/")).length;
console.log(
`[Smart Analyzer] Prepared analyzer images: total=${visionImages.length}, inline=${inlineCount}, remote=${visionImages.length - inlineCount}`,
`[Smart Analyzer] Prepared analyzer images: total=${visionImages.length}, inline=${inlineCount}, remote=${visionImages.length - inlineCount}, preferRemote=${preferRemoteVisionUrls}`,
);
}