From 304a083184a99fad7b5cd6f3c5cf1f306d9528c5 Mon Sep 17 00:00:00 2001 From: seaislee1209 Date: Tue, 23 Jun 2026 03:18:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(image):=20=E5=9B=BE=E7=94=9F=E5=9B=BE?= =?UTF-8?q?=E5=8F=82=E8=80=83=E5=9B=BE=E8=BF=87=E5=A4=A7=E6=97=B6=E9=99=8D?= =?UTF-8?q?=E9=87=87=E6=A0=B7,=E6=A0=B9=E6=B2=BB=20yunqi=20400=20invalid?= =?UTF-8?q?=5Fimage=5Ffile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 杂鱼煲商品三视图本地/线上都 400:主图 6000×4000 / 5.8MB,yunqi(gpt-image edits)拒收过大参考图 报 invalid_image_file(实测缩到 ≤2048 即过)。image_edit 发参考图前按需降采样:最长边 >2048 或 >4MB 才缩(否则原图直发不损质量);三视图成品 ≤1536px,2048 已 ≥ 成品分辨率 → 对成品无损。 保留透明通道(有 alpha 存 PNG,否则 JPEG q92),解析失败原样放行。商品三视图/故事板/模特上身等 所有 image_edit 路径一并受益。 验证:用真实 6000×4000 主图走打补丁的 provider.image_edit → 当场出图(原必 400);探测 yunqi 接受 ≥4096、6000 拒;48 测全绿。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../apps/ai/providers/openai_compatible.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/core/backend/apps/ai/providers/openai_compatible.py b/core/backend/apps/ai/providers/openai_compatible.py index dd86afa..9c9078d 100644 --- a/core/backend/apps/ai/providers/openai_compatible.py +++ b/core/backend/apps/ai/providers/openai_compatible.py @@ -1,10 +1,35 @@ from typing import Any +import io import requests from .volcano import VolcanoArkProvider +def _downscale_ref_for_edit(data: bytes, content_type: str, max_edge: int = 2048) -> tuple[bytes, str]: + """图生图参考图过大时按需降采样,避免中转站(yunqi/gpt-image edits)拒收大图报 400 + 「invalid_image_file」(实测原图 6000×4000 / 5.8MB 被拒,缩到 ≤2048 即通过)。 + + 只在「最长边 > max_edge 或体积 > 4MB」时才处理,否则原样返回(不损质量)。 + 三视图等成品输出 ≤1536px,参考图 2048 已 ≥ 输出分辨率 → 降到 2048 对成品零损失,只是去掉 + 成品用不上的冗余像素。保留透明通道(有 alpha 存 PNG,否则 JPEG q92)。解析失败则原样放行。""" + try: + from PIL import Image + + im = Image.open(io.BytesIO(data)) + if max(im.size) <= max_edge and len(data) <= 4_000_000: + return data, content_type + im.thumbnail((max_edge, max_edge)) + out = io.BytesIO() + if im.mode in ("RGBA", "LA", "P"): + im.convert("RGBA").save(out, format="PNG", optimize=True) + return out.getvalue(), "image/png" + im.convert("RGB").save(out, format="JPEG", quality=92) + return out.getvalue(), "image/jpeg" + except Exception: # noqa: BLE001 — 非图/解析失败:原样发,让上游/中转站决定,不因压缩环节中断生成 + return data, content_type + + class OpenAICompatibleProvider(VolcanoArkProvider): """通用 OpenAI 兼容中转站适配器(tokenssr / yunqi / 任意 New-API 网关)。 @@ -78,12 +103,14 @@ class OpenAICompatibleProvider(VolcanoArkProvider): for idx, ref in enumerate(images or []): fileobj, content_type = self.media_to_bytes(ref) content_type = content_type or "image/png" + # 过大参考图按需降采样(≤2048,对成品零损失),否则中转站拒收报 400 invalid_image_file + img_bytes, content_type = _downscale_ref_for_edit(fileobj.getvalue(), content_type) ext = "png" if "jpeg" in content_type or "jpg" in content_type: ext = "jpg" elif "webp" in content_type: ext = "webp" - files.append(("image[]", (f"ref{idx + 1}.{ext}", fileobj.getvalue(), content_type))) + files.append(("image[]", (f"ref{idx + 1}.{ext}", img_bytes, content_type))) if not files: raise ValueError("image_edit 至少需要一张参考图") data = {"model": model, "prompt": prompt, "size": size, "n": "1"}