perf(review): 安装 Pillow 并优化截图下采样策略

- 视觉 token 成本由像素尺寸决定,故仅在长边超过 IMAGE_MAX_EDGE(默认1280) 时才缩放
- 不需要缩放且格式未变时原样返回,不重新编码,避免 CAD 线稿的无谓压缩损失
- 缩放后默认输出无损 PNG(IMAGE_FORMAT=png),可设 jpeg 换取更小体积
- 缺少 Pillow 时自动降级为原图直传
This commit is contained in:
gjm committed 2026-10-13 23:48:46 +08:00
1 parent ec5292debe
commit 1cbcbb92f4
3 files changed
+35 -12

No files matched your search

+26 -8
View File
@@ -23,28 +23,46 @@ class LlmError(Exception):
pass
def downsample_image_b64(b64, mime="image/png", max_edge=None, quality=None):
"""把 base64 截图下采样并转 JPEG,降低视觉 token 成本。
def downsample_image_b64(b64, mime="image/png", max_edge=None, fmt=None, quality=None):
"""缩放截图,降低视觉 token 成本。
缺少 Pillow 或参数为 0 时原样返回 (b64, mime)。
策略(视觉 token 成本主要由**像素尺寸**决定,而非文件大小):
- 仅当长边超过 `max_edge` 时才缩放;
- 既不需要缩放、目标格式又与原格式一致时,**原样返回**(不重新编码,
避免给 CAD 线稿引入无谓的压缩损失);
- 需要缩放时按 `fmt` 编码:`png`(默认,无损,适合线稿)/ `jpeg`(体积更小)。
缺少 Pillow 或 max_edge <= 0 时原样返回 (b64, mime)。
"""
max_edge = config.IMAGE_MAX_EDGE if max_edge is None else max_edge
fmt = (config.IMAGE_FORMAT if fmt is None else fmt).strip().lower()
quality = config.IMAGE_JPEG_QUALITY if quality is None else quality
if not HAS_PIL or max_edge <= 0:
return b64, mime
target_mime = "image/jpeg" if fmt == "jpeg" else "image/png"
try:
img = Image.open(io.BytesIO(base64.b64decode(b64)))
if img.mode not in ("RGB", "L"):
img = img.convert("RGB")
w, h = img.size
scale = max_edge / float(max(w, h))
if scale < 1.0:
need_resize = scale < 1.0
if not need_resize and mime == target_mime:
return b64, mime
if need_resize:
img = img.resize(
(max(1, int(w * scale)), max(1, int(h * scale))), Image.LANCZOS
)
buf = io.BytesIO()
img.save(buf, format="JPEG", quality=quality)
return base64.b64encode(buf.getvalue()).decode("ascii"), "image/jpeg"
if target_mime == "image/jpeg":
if img.mode not in ("RGB", "L"):
img = img.convert("RGB")
img.save(buf, format="JPEG", quality=quality)
else:
img.save(buf, format="PNG", optimize=True)
return base64.b64encode(buf.getvalue()).decode("ascii"), target_mime
except Exception:
return b64, mime