perf(review): 安装 Pillow 并优化截图下采样策略
- 视觉 token 成本由像素尺寸决定,故仅在长边超过 IMAGE_MAX_EDGE(默认1280) 时才缩放 - 不需要缩放且格式未变时原样返回,不重新编码,避免 CAD 线稿的无谓压缩损失 - 缩放后默认输出无损 PNG(IMAGE_FORMAT=png),可设 jpeg 换取更小体积 - 缺少 Pillow 时自动降级为原图直传
This commit is contained in:
1 parent
ec5292debe
commit
1cbcbb92f4
3 files changed
+34
-11
No files matched your search
@@ -43,8 +43,11 @@ type: project
|
||||
|
||||
## 环境事实
|
||||
|
||||
- 本机 **未安装 Pillow** → 截图下采样为**可选**(缺失时原样传原图,视觉 token 成本更高)。
|
||||
如需控成本:`pip install pillow`。
|
||||
- **Pillow 已安装**(12.3.0)。截图下采样策略(`dwg_review/llm_client.py: downsample_image_b64`):
|
||||
视觉 token 成本由**像素尺寸**决定而非文件大小 → **只在长边超 `IMAGE_MAX_EDGE`(默认 1280)时才缩放**;
|
||||
既不需要缩放、格式又没变时**原样返回不重编码**(避免给 CAD 线稿引入无谓压缩损失);
|
||||
缩放后默认输出**无损 PNG**(`IMAGE_FORMAT=png`,适合线稿),需要更小体积可设 `IMAGE_FORMAT=jpeg`。
|
||||
缺少 Pillow 时自动降级为原图直传。
|
||||
- 视觉模型 `deepseek-flash`。
|
||||
- **`DEEPSEEK_API_KEY` 设在 Windows `Machine`(系统)级**。因新设的系统变量**不会传播到已启动
|
||||
的进程**,`dwg_review/config.py` 的 `_read_env()` 在进程环境读不到时**回退读注册表**
|
||||
|
||||
@@ -90,9 +90,11 @@ ANNOTATE_LAYER = os.environ.get("ANNOTATE_LAYER", "AI_REVIEW")
|
||||
# ---------------------------------------------------------------------------
|
||||
# 截图(视觉 token 成本控制)
|
||||
# ---------------------------------------------------------------------------
|
||||
# 长边上限(下采样目标),0 表示不下采样
|
||||
# 长边上限(超过才缩放),0 表示不缩放
|
||||
IMAGE_MAX_EDGE = int(os.environ.get("IMAGE_MAX_EDGE", "1280"))
|
||||
# JPEG 质量
|
||||
# 缩放后的输出格式:png(默认,无损,适合 CAD 线稿)或 jpeg(体积更小)
|
||||
IMAGE_FORMAT = os.environ.get("IMAGE_FORMAT", "png").strip().lower()
|
||||
# JPEG 质量(仅 IMAGE_FORMAT=jpeg 时生效)
|
||||
IMAGE_JPEG_QUALITY = int(os.environ.get("IMAGE_JPEG_QUALITY", "80"))
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
if target_mime == "image/jpeg":
|
||||
if img.mode not in ("RGB", "L"):
|
||||
img = img.convert("RGB")
|
||||
img.save(buf, format="JPEG", quality=quality)
|
||||
return base64.b64encode(buf.getvalue()).decode("ascii"), "image/jpeg"
|
||||
else:
|
||||
img.save(buf, format="PNG", optimize=True)
|
||||
return base64.b64encode(buf.getvalue()).decode("ascii"), target_mime
|
||||
except Exception:
|
||||
return b64, mime
|
||||
|
||||
|
||||
Reference in new issue
Block a user