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"}