fix: preserve 4k image size across failover pipeline
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 17:06:18 +08:00
parent 8749f41773
commit 64502c9d4b
2 changed files with 42 additions and 11 deletions

View File

@ -2828,6 +2828,12 @@ export async function POST(request: Request) {
const volcanoResult = await (volcanoLanguageModel as any).doGenerate({
messages: volcanoMessages,
maxImages: 1, // 故障转移时只生成 1 张
imageParams:
effectiveImageParams || {
aspectRatio: defaultImageQualityParams.aspectRatio,
imageSize: defaultImageQualityParams.imageSize,
imageCount: defaultImageQualityParams.imageCount,
},
});
const imageMarkdown = volcanoResult.text || "图片生成失败";

View File

@ -17,6 +17,15 @@ type VolcanoUrlRewriteConfig = {
let cachedRewriteConfig: VolcanoUrlRewriteConfig | null | undefined;
const VOLCANO_IMAGE_FETCH_TIMEOUT_MS = 20_000;
const VOLCANO_INLINE_MAX_SIDE = 1024;
const DEFAULT_VOLCANO_IMAGE_SIZE = "2K";
function normalizeVolcanoImageSize(imageSize?: string): "1K" | "2K" | "4K" {
const normalized = String(imageSize || "").trim().toUpperCase();
if (normalized === "1K" || normalized === "2K" || normalized === "4K") {
return normalized;
}
return DEFAULT_VOLCANO_IMAGE_SIZE;
}
function guessImageMimeTypeFromUrl(url: string): string {
const cleanUrl = url.split("?")[0]?.toLowerCase() || "";
@ -321,16 +330,19 @@ async function extractAndUploadImages(response: VolcanoVisionResponse): Promise<
* 将图片数据转换为 Markdown 格式
* 使用特殊格式传递缩略图和原图 URL:![thumb|原图URL](缩略图URL)
*/
function formatImageResponse(images: Array<{ thumbnail: string; original: string }>): string {
function formatImageResponse(
images: Array<{ thumbnail: string; original: string }>,
imageSize: string = DEFAULT_VOLCANO_IMAGE_SIZE
): string {
if (images.length === 0) {
return "生成图片失败,未找到图片数据";
}
// 🆕 统一使用 ||| 分隔符格式(与 Gemini 保持一致)
// 格式: ![thumb|||原图URL|||尺寸](缩略图URL)
// 豆包固定生成 2K 图片
const normalizedSize = normalizeVolcanoImageSize(imageSize);
return images
.map((img) => `![thumb|||${img.original}|||2K](${img.thumbnail})`)
.map((img) => `![thumb|||${img.original}|||${normalizedSize}](${img.thumbnail})`)
.join("\n\n");
}
@ -429,7 +441,8 @@ async function sendVolcanoRequest(
apiKey: string,
messages: any[],
modelId: string,
maxImages: number = 3 // 默认 3 张,故障转移时传 1
maxImages: number = 3, // 默认 3 张,故障转移时传 1
imageSize: string = DEFAULT_VOLCANO_IMAGE_SIZE
): Promise<VolcanoVisionResponse> {
const url = `${baseURL}/api/v3/images/generations`;
@ -465,10 +478,11 @@ async function sendVolcanoRequest(
console.log("[Volcano Vision] Image count:", image ? (Array.isArray(image) ? image.length : 1) : 0);
// 构建请求体
const normalizedSize = normalizeVolcanoImageSize(imageSize);
const requestBody: any = {
model: modelId,
prompt,
size: "2K",
size: normalizedSize,
watermark: false,
response_format: "url",
stream: false,
@ -579,7 +593,8 @@ async function* sendVolcanoStreamRequest(
baseURL: string,
apiKey: string,
messages: any[],
modelId: string
modelId: string,
imageSize: string = DEFAULT_VOLCANO_IMAGE_SIZE
): AsyncGenerator<VolcanoVisionStreamEvent> {
const url = `${baseURL}/api/v3/images/generations`;
@ -593,10 +608,11 @@ async function* sendVolcanoStreamRequest(
console.log("[Volcano Vision] Image count:", image ? (Array.isArray(image) ? image.length : 1) : 0);
// 构建请求体
const normalizedSize = normalizeVolcanoImageSize(imageSize);
const requestBody: any = {
model: modelId,
prompt,
size: "2K",
size: normalizedSize,
watermark: false,
response_format: "url",
stream: true,
@ -695,6 +711,7 @@ export function createVolcanoVision({ apiKey, baseURL }: VolcanoVisionConfig) {
const activeModelId = options.overrideModelId || modelId;
console.log("[Volcano Vision] Using modelId:", activeModelId);
console.log("[Volcano Vision] Image params from options:", options.imageParams);
const imageSize = normalizeVolcanoImageSize(options.imageParams?.imageSize);
// 🆕 优先使用用户选择的图片数量,其次是 maxImages 参数,默认 1 张
const imageCount = options.imageParams?.imageCount || options.maxImages || 1;
@ -703,11 +720,18 @@ export function createVolcanoVision({ apiKey, baseURL }: VolcanoVisionConfig) {
try {
// 发送请求
const response = await sendVolcanoRequest(baseURL, apiKey, options.messages, activeModelId, maxImages);
const response = await sendVolcanoRequest(
baseURL,
apiKey,
options.messages,
activeModelId,
maxImages,
imageSize
);
// 提取图片并上传到 S3
const imageUrls = await extractAndUploadImages(response);
const markdownText = formatImageResponse(imageUrls);
const markdownText = formatImageResponse(imageUrls, imageSize);
console.log(
`[Volcano Vision] Generated ${imageUrls.length} images (uploaded to S3)`
@ -748,14 +772,15 @@ export function createVolcanoVision({ apiKey, baseURL }: VolcanoVisionConfig) {
const allImages: Array<{ thumbnail: string; original: string }> = [];
// 使用流式请求
for await (const event of sendVolcanoStreamRequest(baseURL, apiKey, options.messages, modelId)) {
const imageSize = normalizeVolcanoImageSize(options.imageParams?.imageSize);
for await (const event of sendVolcanoStreamRequest(baseURL, apiKey, options.messages, modelId, imageSize)) {
if (event.type === "image_generation.partial_succeeded" && event.url) {
// 每张图片生成完成后立即上传并发送
try {
const imageUrls = await uploadImageToS3(event.url);
allImages.push(imageUrls);
const markdown = `![thumb|${imageUrls.original}](${imageUrls.thumbnail})`;
const markdown = `![thumb|||${imageUrls.original}|||${imageSize}](${imageUrls.thumbnail})`;
console.log(`[Volcano Vision Stream] Image ${event.image_index} uploaded, sending...`);
yield { type: "text-delta" as const, textDelta: markdown + "\n\n" };