fix(seedream): size 面积钳制——修 1920x1080 被 ARK 400 打回(bump 0.37.1)
模型自选 16:9 出图(1920x1080=2.07M px)触发 ARK 硬门 `image size must be at least 3686400 pixels`(=1920²,卡总面积非单边), 整次文生图 400 失败。 - tools/seedream.py: 新增 _normalize_size(),出图前把 size 钳进 [min_pixels, max_pixels]:面积不足按 sqrt(min/area) 等比放大、 取整到 8 的倍数并复核达标(1920x1080→2560x1440);超上限等比缩小; 已合规原样透传(向后兼容)。归一化时返回串附 [note]、meta 记 requested_size,记账按真实出图尺寸。 - config/media/doubao.yaml: seedream_5 加 min_pixels/max_pixels (旧 yaml 缺键=不设该侧,行为不变)。 - bump 0.37.0→0.37.1;PROGRESS 加一条。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0e02cff6c6
commit
6f27b7cc5a
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。
|
||||
|
||||
最后更新:2026-07-03(ppt 门体系二轮硬化:逃生口收紧 + 导出自动质检 + svg_final 嵌图修复,bump 0.36.1)
|
||||
最后更新:2026-07-03(seedream size 面积钳制:修 1920x1080 被 ARK 400 打回,bump 0.37.1)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -21,6 +21,9 @@
|
|||
|
||||
## 已完成关键能力
|
||||
|
||||
### 2026-07-03 / seedream size 面积钳制(修 1920x1080 被 ARK 400 打回,bump 0.37.1)
|
||||
模型自选 16:9 出图(如 `1920x1080`=2,073,600px)触发 ARK 硬门 `image size must be at least 3686400 pixels`(=1920²),整次文生图直接 400 失败。根因:`tools/seedream.py` 把 `size` 原样透传,不校验 ARK 的**面积**约束(卡的是总像素不是单边,故 16:9 最小合规是 2560x1440)。修:tool 内新增 `_normalize_size()`,拿到 `chosen_size` 前先钳进 `[min_pixels, max_pixels]`——面积 `<min` 按 `sqrt(min/area)` 等比放大、两边向上取整到 8 的倍数并复核达标(1920x1080→2560x1440);`>max`(3072²=9,437,184)等比缩小;已合规原样透传(向后兼容)。约束值加到 `config/media/doubao.yaml` seedream_5 档(`min_pixels`/`max_pixels`,旧 yaml 缺键则视为不设该侧、行为不变)。归一化时返回串附 `[note]` 提示 + meta 记 `requested_size`,usage 记账按**真实出图尺寸**。选自动钳而非返错让模型重试:省一轮往返、避免二次错。新增 tests 手验 9 例全落合法区间。
|
||||
|
||||
### 2026-07-03 / ppt 对齐网格锁 + 错位/单调质检(d1285247 陶瓷 deck 复盘,bump 0.37.0)
|
||||
对 d1285247 产物(25 页陶瓷方案 PPTX)逐页几何量测 + PowerPoint COM 渲图目视复盘,三类缺陷:①跨页左基线漂移(0.656–0.75in 七个值)+ 并排块顶差 2–12px 的"想对齐没对齐"(S8/S19/S23);②5 页同为"图标+标题+三行字"卡网格,零流程箭头/零分层图形,单调;③标题语义不兑现("五层架构"画成五条等宽横条、"矩阵"画成卡片格)。根因:executor 手写绝对坐标但 spec_lock 无网格常量可依;质检只查重叠/越界不查对齐;"节奏不雷同"只约束相邻页。修四层:**A spec_lock 新增 `layout_grid` 锁段**(margin_x/content_top/footer_y/gutter,strategist 派生、executor 每页吸附、checker 强制;design_spec_reference §V 同步);**B executor-base §3 网格对齐纪律**(并排卡片同 top 同高等 gutter、打破网格 ≥16px 干净打破、同行文字 ≥0.3em 禁贴字);**C svg_quality_checker 新增 check 14**——兄弟卡片近失对齐(精确几何,2–12px error;底对齐/中心对齐/绘图区内数据柱三类豁免,71 charts 模板回归误报清零)、layout_grid 偏离 2–15px error、行内 gap 不等 warning、无锁存量项目跨页左缘聚类漂移 warning、版式指纹单调门(≥3 页同指纹 warn、≥4 或过半 error;仅对 NN_ 编号 deck 页聚合,模板库静默);**D 策略纪律升级**——同一版式原型整本 ≤2 次 + 标题语义必须被图形兑现(SKILL.md 大纲纪律 + strategist visual-floor GATE)。顺手修 comparison_columns 模板胶囊 5px 错位。新增 tests/test_svg_alignment_check.py 21 项,全量 153 过。已知边界:页面平衡类(底部大空白/重心偏移,S18/S22)误报风险高未进 checker,只进阶段五验收 checklist 眼看;错位 error 会被导出边界自动质检门连带拦截,存量项目重导出若报新 error 属预期(真缺陷)。
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ image:
|
|||
endpoint: /images/generations
|
||||
price_cny_per_image: 0.22 # 计费单位:成功输出张数;调价改这里 + 重启
|
||||
default_size: 2048x2048 # 原生最高 3072x3072;2K 兼顾质量/体积
|
||||
# 输出尺寸面积约束(ARK 硬门):面积 < min_pixels → 400 InvalidParameter。
|
||||
# 模型自选 16:9 之类小尺寸(如 1920x1080=2.07M)会栽,故 tool 侧等比钳到合法区间:
|
||||
# min = 1920² = 3,686,400(16:9 最小合规即 2560x1440);max = 3072² = 9,437,184。
|
||||
min_pixels: 3686400
|
||||
max_pixels: 9437184
|
||||
default_watermark: false # 默认无水印(申报/PPT 场景反需求)
|
||||
default_search: false # web search 额外加价 ~¥0.05/张;默认关
|
||||
request_timeout_s: 60 # 出图慢于此判超时
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
|
||||
# 改版本只动这一行。
|
||||
__version__ = "0.37.0"
|
||||
__version__ = "0.37.1"
|
||||
|
|
|
|||
|
|
@ -138,7 +138,15 @@ class SeedreamTool(Tool):
|
|||
|
||||
cfg = self.cfg
|
||||
model_id = cfg["model_id"]
|
||||
chosen_size = size or cfg.get("default_size", "2048x2048")
|
||||
requested_size = size or cfg.get("default_size", "2048x2048")
|
||||
# ARK 硬门:输出面积必须落在 [min_pixels, max_pixels],否则 400 InvalidParameter。
|
||||
# 模型自选 16:9 之类小尺寸(1920x1080=2.07M < 3.69M)会被打回,这里等比钳到合法区间,
|
||||
# 静默纠错省一轮往返;已合规的尺寸原样透传。归一化时给用户一行提示。
|
||||
chosen_size, size_note = self._normalize_size(
|
||||
requested_size,
|
||||
min_pixels=int(cfg.get("min_pixels", 0)),
|
||||
max_pixels=int(cfg.get("max_pixels", 0)),
|
||||
)
|
||||
chosen_watermark = bool(cfg.get("default_watermark", False)) if watermark is None else bool(watermark)
|
||||
chosen_search = bool(cfg.get("default_search", False)) if search is None else bool(search)
|
||||
timeout_s = float(cfg.get("request_timeout_s", 60))
|
||||
|
|
@ -185,6 +193,7 @@ class SeedreamTool(Tool):
|
|||
"prompt": prompt,
|
||||
"model_id": model_id,
|
||||
"size": chosen_size,
|
||||
"requested_size": requested_size, # 归一化前模型/用户请求的原始尺寸(=chosen_size 表示未钳)
|
||||
"watermark": chosen_watermark,
|
||||
"search": chosen_search,
|
||||
"mode": "i2i" if is_i2i else "t2i",
|
||||
|
|
@ -219,14 +228,75 @@ class SeedreamTool(Tool):
|
|||
# 格式严格 key=value · 分隔,parse 用正则 `key=([^·\n]+)` 抓。
|
||||
mode_seg = " · mode=i2i" if is_i2i else ""
|
||||
ref_line = f"\nreference={ref_disp[0]}" if is_i2i else ""
|
||||
note_line = f"\n{size_note}" if size_note else ""
|
||||
return (
|
||||
f"[seedream] model={model_id} · size={chosen_size} · "
|
||||
f"cost=¥{cost_cny:.2f} · elapsed={elapsed:.1f}s{mode_seg}\n"
|
||||
f"saved: {disp}{ref_line}\n"
|
||||
f"prompt={prompt!r}\n"
|
||||
f"watermark={chosen_watermark} search={chosen_search}"
|
||||
f"watermark={chosen_watermark} search={chosen_search}{note_line}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _normalize_size(
|
||||
requested: str, *, min_pixels: int = 0, max_pixels: int = 0
|
||||
) -> tuple[str, str]:
|
||||
"""把请求尺寸钳进 ARK 面积约束 [min_pixels, max_pixels],保持宽高比。
|
||||
|
||||
返回 (chosen_size, note):note 非空表示发生了钳制(用于提示用户 + 记账用真实尺寸)。
|
||||
- 无法解析成 "WxH" / 任一边 <= 0 → 原样返回,不阻塞(交给 API 自己报错,行为不回退)。
|
||||
- min/max 传 0 → 视为不设该侧约束(向后兼容:旧 yaml 无这两个键时不改变行为)。
|
||||
- 面积 < min:按 s=sqrt(min/area) 等比放大,两边向上取整到 8 的倍数,复核达标(不够再 +8)。
|
||||
- 面积 > max:按 s=sqrt(max/area) 等比缩小,两边向下取整到 8 的倍数,复核达标(超了再 -8)。
|
||||
- 已在区间内 → 原样透传,note 为空。
|
||||
"""
|
||||
raw = (requested or "").strip().lower().replace(" ", "")
|
||||
parts = raw.split("x")
|
||||
if len(parts) != 2:
|
||||
return requested, ""
|
||||
try:
|
||||
w, h = int(parts[0]), int(parts[1])
|
||||
except ValueError:
|
||||
return requested, ""
|
||||
if w <= 0 or h <= 0:
|
||||
return requested, ""
|
||||
|
||||
import math
|
||||
|
||||
def _round8(v: float, *, up: bool) -> int:
|
||||
n = math.ceil(v / 8) if up else math.floor(v / 8)
|
||||
return max(8, n * 8)
|
||||
|
||||
area = w * h
|
||||
if min_pixels > 0 and area < min_pixels:
|
||||
s = math.sqrt(min_pixels / area)
|
||||
nw, nh = _round8(w * s, up=True), _round8(h * s, up=True)
|
||||
# 取整可能把面积压回下限之下,补到达标为止(沿较长边加 8,尽量不破坏比例)
|
||||
while nw * nh < min_pixels:
|
||||
if nw >= nh:
|
||||
nh += 8
|
||||
else:
|
||||
nw += 8
|
||||
chosen = f"{nw}x{nh}"
|
||||
return chosen, (
|
||||
f"[note] 请求尺寸 {w}x{h}({area:,}px)低于模型最小面积 {min_pixels:,}px,"
|
||||
f"已等比放大到 {chosen} 出图。"
|
||||
)
|
||||
if max_pixels > 0 and area > max_pixels:
|
||||
s = math.sqrt(max_pixels / area)
|
||||
nw, nh = _round8(w * s, up=False), _round8(h * s, up=False)
|
||||
while nw * nh > max_pixels:
|
||||
if nw >= nh:
|
||||
nw -= 8
|
||||
else:
|
||||
nh -= 8
|
||||
chosen = f"{nw}x{nh}"
|
||||
return chosen, (
|
||||
f"[note] 请求尺寸 {w}x{h}({area:,}px)超过模型最大面积 {max_pixels:,}px,"
|
||||
f"已等比缩小到 {chosen} 出图。"
|
||||
)
|
||||
return requested, ""
|
||||
|
||||
@staticmethod
|
||||
def _extract_url(resp: dict) -> tuple[str, str]:
|
||||
"""ark images/generations 响应解析,容忍几种已知 shape:
|
||||
|
|
|
|||
Loading…
Reference in New Issue