Claude Skill

batch-image

多商品批量生图流水线。商品清单 CSV → 整批统一视觉的商拍图,带并发、重试、断点续跑、成本熔断与挑图联系表。当用户说「批量生图」「一批商品」「跑整个 SKU 表」「几百个商品出图」时使用。

LLM Mart · 0 points · 0 views 2 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download dlazy-ai-ecommerce-skills-skills_batch-image-c022f20.zip · 40 KB
Part of dlazy-ai/ecommerce-skills — 26 skills

Install

skills CLI npx skills add https://github.com/dlazy-ai/ecommerce-skills/tree/main/skills/batch-image
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install dlazy-ai-ecommerce-skills@llmmart
Git git clone https://github.com/dlazy-ai/ecommerce-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole dlazy-ai/ecommerce-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

batch-image — 多商品批量生图

把单张生图变成流水线:一份商品清单 → 一整套风格统一的商拍图。

批量的难点不是「跑很多次」,而是跑出来的图要像一套。本技能的核心是规范段与变量段分离:视觉规范逐字不变,只有商品描述随 SKU 变化。


生成效果示例

输入:商品清单(2 个 SKU)
SKU001 军绿色麻花针织圆领毛衣,落肩宽松版型
SKU002 黑色亮面皮革布洛克德比鞋,厚底系带

实际执行的命令(循环体,两个 SKU 只有 DESC 与 --images 不同,规范段逐字相同):

for sku in a-sweater b-shoes; do
  case $sku in
    a-sweater) DESC='军绿色麻花针织圆领毛衣,落肩宽松版型' ;;
    b-shoes)   DESC='黑色亮面皮革布洛克德比鞋,厚底系带' ;;
  esac
  dlazy seedream-5.0 \
    --prompt "电商商拍图。图1 是商品:${DESC}。商品的颜色、材质纹理、款式细节必须与图1完全一致。放置在同一套统一视觉里:纯米白色摄影棚背景,柔和顶光加左侧补光,45 度视角,画面下方留出统一的商品投影,构图与留白在整组图中保持一致。真实商业产品摄影,无文字无水印。" \
    --images docs/batch-image/sku-${sku}.jpg --size 1:1 --resolution 2k \
    --save docs/batch-image/example-output-${sku}.jpg
done

输出:同一套视觉规范下的两个 SKU

SKU001 SKU002
1:1 / 2K,5 credits 1:1 / 2K,5 credits

两张的背景色、光位、视角与投影方向一致——因为规范段逐字相同;商品各自保真。100 个 SKU 就是这个循环跑 100 次,总价约 500 credits。


1、能力边界

能力 说明
批量规模 一批最多 100 个商品(对应企业功能的规格)
成本优势 批量场景下单图算力成本可降至约 7 折
协作 多人在线协作、出片进度实时追踪
资产共享 共享模特 / 参考图 / 作品 / 算力点
本技能落地 清单驱动循环 + 并发控制 + 失败重试 + 按 SKU 归档 + 汇总报告

不做:不在同一批里混用不同视觉规范(那不是一批);不跳过抽样质检直接全量上架;不因为批量就放宽商品保真要求。


2、输入素材规则

生成前先自检这几条硬性约束:

  • 大小:20KB ~ 15MB
  • 分辨率:大于 400×400
  • 格式:jpg / jpeg / png / webp

商品清单(manifest) 是批量的输入,建议 CSV:

sku,image,desc
SKU001,docs/batch-image/sku-a-sweater.jpg,军绿色麻花针织圆领毛衣,落肩宽松版型
SKU002,docs/batch-image/sku-b-shoes.jpg,黑色亮面皮革布洛克德比鞋,厚底系带

每张商品图仍要满足单图规则:20KB~15MB、>400×400、jpg/jpeg/png/webp。

批量前必做的事

步骤 说明
✅ 先跑通 1 个 SKU 单个跑不对,跑 100 个只是错 100 次
✅ 再抽样 5 个 SKU 覆盖品类/颜色/深浅色的边界情况
✅ 确认成本 --dry-run 单价 × SKU 数 × batch 数
❌ 直接全量 最容易烧算力的做法

3、规范段 / 变量段分离

这是批量出统一视觉的唯一方法:

prompt = [规范段:整批逐字不变]  +  [变量段:每个 SKU 不同]
段 内容 是否变
规范段 背景、光位、机位、构图留白、投影、色调、输出风格、no text, no watermark 整批逐字不变
变量段 商品品类 + 颜色 + 材质 + 关键结构特征 每个 SKU 不同

写成 shell:

SPEC='放置在同一套统一视觉里:纯米白色摄影棚背景,柔和顶光加左侧补光,45 度视角,画面下方留出统一的商品投影,构图与留白在整组图中保持一致。真实商业产品摄影,无文字无水印。'

while IFS=, read -r SKU IMG DESC; do
  dlazy seedream-5.0 \
    --prompt "电商商拍图。图1 是商品:${DESC}。商品的颜色、材质纹理、款式细节必须与图1完全一致。${SPEC}" \
    --images "$IMG" --size 1:1 --resolution 2k \
    --save "docs/batch-image/out/${SKU}.jpg"
done < manifest.csv

规范段一旦改动就必须整批重跑——否则同一批里会出现两种视觉。


4、并发、重试与进度

并发:dlazy 是单次调用一个任务,批量靠 shell 并发。控制在 4~6 路,太高容易触发限流。

run_one() { ... }               # 单个 SKU 的生成逻辑
export -f run_one
xargs -P 5 -I{} bash -c 'run_one {}' < sku-list.txt     # 5 路并发

重试:网络与服务端偶发失败很正常,包一层重试。

for attempt in 1 2 3; do
  dlazy ... && break
  echo "retry $attempt for $SKU" >&2; sleep $((attempt * 10))
done

异步模式:SKU 很多时用 --no-wait 先把任务全部提交,拿到 generateId 后再统一轮询,避免长时间占着进程。

GID=$(dlazy seedream-5.0 --no-wait ... | python3 -c 'import sys,json;print(json.load(sys.stdin)["result"]["task"]["generateId"])')
echo "$SKU,$GID" >> tasks.csv
# 稍后统一收结果
while IFS=, read -r SKU GID; do dlazy status "$GID" --wait; done < tasks.csv

进度与汇总:每个 SKU 记一行状态,跑完输出报告。

echo "$SKU,$(test -f out/$SKU.jpg && echo ok || echo fail)" >> report.csv
awk -F, '{c[$2]++} END{for(k in c) print k, c[k]}' report.csv

5、工具调用

本技能使用 dLazy 的 seedream-5.0(批量场景的关键指标是单图成本与参考图支持:本模型单张约 5 credits、支持最多 10 张参考图、支持 2K/3K/4K;100 个 SKU 的成本量级从数百降到数十。对商品保真要求极高的批次改用 gpt-image-2)。

调用方式

两种等价写法,选一种。统一入口会自动选后端、失败重试、建目录落盘、估算成本:

# A. 统一入口(推荐):可切任意后端,加 --dry-run 不计费空跑
node scripts/gen.mjs --task batch-image \
  --prompt '<见下方 Prompt 模板>' \
  --images <按下表顺序> \
  --save output/batch-image-<sku>.jpg

# B. 直接用 dLazy CLI(不想引入 Node 依赖时,效果等价)
dlazy seedream-5.0 --prompt '...' --images ... --save output/batch-image.jpg

参数约定(本技能固定用法)

参数 取值 理由
--images [该 SKU 的商品图](+ 整批共用的参考图 / 模特图) 共用素材放在固定位置,prompt 里用固定图号引用
--size 整批统一(1:1 主图 / 3:4 竖版) 不统一就不是一套
--resolution 2k 上架足够;4K 只给需要印刷的批次
--batch 1(批量场景靠 SKU 数量,不靠 batch) batch 会让成本翻倍
--no-wait SKU 很多时开启 先提交后轮询,避免长时间阻塞
--save docs/batch-image/out/<SKU>.jpg 用 SKU 命名,便于回填到商品库

模型切换:seedream-5.0 便宜、适合氛围与场景图;对商品保真要求极高(图案位置、五金细节)的批次换 gpt-image-2,成本约 6 倍。同一批内不要混用两个模型。

Command Examples

# basic call: 单个 SKU(先跑通这个,再谈批量)
dlazy seedream-5.0 \
  --prompt '电商商拍图。图1 是商品:军绿色麻花针织圆领毛衣,落肩宽松版型。商品的颜色、材质纹理、款式细节必须与图1完全一致。放置在同一套统一视觉里:纯米白色摄影棚背景,柔和顶光加左侧补光,45 度视角,画面下方留出统一的商品投影。真实商业产品摄影,无文字无水印。' \
  --images docs/batch-image/sku-a-sweater.jpg --size 1:1 --resolution 2k

# complex call: 完整流水线(清单驱动 + 5 路并发 + 3 次重试 + 汇总报告)
cat > manifest.csv <<'CSV'
SKU001,docs/batch-image/sku-a-sweater.jpg,军绿色麻花针织圆领毛衣,落肩宽松版型
SKU002,docs/batch-image/sku-b-shoes.jpg,黑色亮面皮革布洛克德比鞋,厚底系带
CSV

SPEC='放置在同一套统一视觉里:纯米白色摄影棚背景,柔和顶光加左侧补光,45 度视角,画面下方留出统一的商品投影,构图与留白在整组图中保持一致。真实商业产品摄影,无文字无水印。'
mkdir -p docs/batch-image/out

run_one() {
  IFS=, read -r SKU IMG DESC <<< "$1"
  for attempt in 1 2 3; do
    dlazy seedream-5.0 \
      --prompt "电商商拍图。图1 是商品:${DESC}。商品的颜色、材质纹理、款式细节必须与图1完全一致。${SPEC}" \
      --images "$IMG" --size 1:1 --resolution 2k \
      --save "docs/batch-image/out/${SKU}.jpg" >/dev/null 2>&1 && break
    sleep $((attempt * 10))
  done
  if [ -f "docs/batch-image/out/${SKU}.jpg" ]; then echo "${SKU},ok"; else echo "${SKU},fail"; fi
}
export -f run_one; export SPEC

xargs -P 5 -I{} bash -c 'run_one "{}"' < manifest.csv | tee docs/batch-image/report.csv
awk -F, '{c[$2]++} END{printf "\n=== 汇总 ===\n"; for(k in c) printf "%s: %d\n", k, c[k]}' docs/batch-image/report.csv

# 先估价:单价 × SKU 数
dlazy seedream-5.0 --dry-run --prompt '...' --images a.jpg --size 1:1 | grep estimatedCost
echo "SKU 数:$(wc -l < manifest.csv)"

延伸阅读

要查什么 去哪
认证、多后端配置、输出结构、错误码 references/provider-cli.md
seedream-5.0 的全部可用参数 references/model-flags.md
统一入口的全部选项 node scripts/gen.mjs --help

6、Prompt 模板

【变量段 · 每个 SKU 不同】
电商商拍图。图1 是商品:[品类 + 颜色 + 材质 + 关键结构特征]。
商品的颜色、材质纹理、款式细节必须与图1完全一致。

【规范段 · 整批逐字不变】
放置在同一套统一视觉里:[背景],[光位],[机位/视角],
画面下方留出统一的商品投影,构图与留白在整组图中保持一致。
真实商业产品摄影,无文字无水印。

按问题追加的修正句(改了就要整批重跑)

问题 处理
深色商品在浅背景上发灰 规范段补 深色商品需加右侧轮廓光勾边,避免与背景糊在一起
不同 SKU 的商品大小忽大忽小 规范段补 商品在画面中的占比统一为约 [X]%,边距一致
投影方向不一致 规范段补 投影统一朝画面右下方,长度约为商品高度的 [X]
某些 SKU 保真度不够 那部分 SKU 单独用 gpt-image-2 重跑,并在报告里标注

7、执行流程

  1. 建清单:sku,image,desc 三列 CSV,每张商品图先过单图规则校验。
  2. 写规范段:背景 / 光位 / 机位 / 占比 / 投影 / 色调 / 输出风格——定稿后不许改。
  3. 跑通 1 个 SKU:确认规范段和变量段都对。
  4. 抽样 5 个 SKU:覆盖深色 / 浅色 / 反光 / 大件 / 小件的边界。
  5. --dry-run 估总价:单价 × SKU 数。
  6. 全量跑:5 路并发 + 3 次重试;SKU 很多时用 --no-wait 提交后统一轮询。
  7. 看汇总报告:失败的单独重跑。
  8. 抽样质检:随机抽 10% 过 detect-task;不合格的 SKU 单独用 gpt-image-2 重跑。

8、常见问题

现象 原因 处理
同一批里视觉不统一 中途改了规范段 规范段定稿后不许改;改了就整批重跑
深色商品糊进背景 缺轮廓光 规范段补轮廓光句,整批重跑
商品占比忽大忽小 未约束占比 规范段补占比与边距句
部分 SKU 保真度不够 低价模型的局限 那些 SKU 单独用 gpt-image-2 重跑
跑到一半失败一堆 并发太高触发限流 并发降到 4~5,加重试与退避
成本超预算 用了 --batch 或 4K 批量场景 --batch 1 + 2k
跑完不知道哪些失败了 没有报告 用第四节的 report.csv + 汇总

Tips

Visit https://dlazy.com for more information.

Files (ecommerce-skills)
  • examples
    • brand.yaml 1.6 KB
      # ⚠️ 由 scripts/build-skills.mjs 从 shared/examples/brand.yaml 同步生成,不要直接改这里。
      # 店铺品牌视觉规范 —— 所有生图技能读这一份,保证几百个 SKU 看起来像同一家店。
      #   node scripts/brand.mjs --brand brand.yaml --for flat-lay
      #   node scripts/gen.mjs --task flat-lay --brand brand.yaml --prompt '...'
      
      brand:
        name: 示例品牌
        # 一句话概括调性,会原样进 prompt
        tone: quiet minimalist, warm and lived-in, never glossy or commercial
      
      model:
        # 锁模特:给一张脸的参考图,所有技能都会把它作为最后一张参考图传入
        reference: assets/model/face-a.jpg
        description: East Asian woman, late twenties, natural makeup, shoulder-length black hair
        body: slim, height around 168cm
      
      photography:
        background: seamless off-white studio backdrop, RGB 248 248 246
        lighting: soft large softbox from camera left, gentle fill, no hard shadows
        camera: 85mm equivalent, eye level, shallow depth of field
        grade: neutral white balance around 5200K, low contrast, slightly lifted blacks
        crop: full body with headroom, product centered
      
      layout:
        # 给带排版的技能(主图 / 详情页)用
        margin: at least 8% empty margin on all sides
        typeface: clean sans-serif, no decorative fonts
        text_color: near-black on light background
      
      forbid:
        - no visible brand logos other than the product's own
        - no text or watermark
        - no exaggerated poses or dramatic wind effects
        - no oversaturated colors
      
      # 可选:把这些直接写进合规目标,生成时就按平台要求出图
      compliance:
        platform: amazon
      
  • references
    • model-flags.md 1.8 KB
      # `seedream-5.0` 参数清单
      
      本技能默认用的模型的完整参数。日常只需要「参数约定」里那几个,
      这份清单在需要用到非常规参数时再看。
      
      **CRITICAL INSTRUCTION FOR AGENT**:
      Run the `dlazy seedream-5.0` command to get results.
      
      ```bash
      dlazy seedream-5.0 -h
      
      Options:
        --prompt <prompt>          Prompt
        --images [images...]       Images [image: url or local path] (max 10)
        --resolution <resolution>  Resolution [default: 2k] (choices: "2k", "3k",
                                   "4k")
        --size <size>              Size [default: 16:9] (choices: "1:1", "4:3",
                                   "3:4", "16:9", "9:16", "3:2", "2:3", "21:9")
        --dry-run                  Print payload without executing the tool
        --no-wait                  Return generateId immediately for async tasks
        --timeout <seconds>        Max seconds to wait for async completion (default:
                                   "1800")
        --input <jsonOrFile>       Inline JSON or @path/to/file.json — merged under
                                   flag values (flags win)
        --save <path>              Download the result asset to this local path
                                   (mkdir + retry handled for you). A destination
                                   path — NOT a response format; for stdout shape use
                                   --format
        --batch <n>                Fan-out N parallel runs (cloud tools only)
                                   (default: "1")
        -h, --help                 display help for command
      ```
      
      > Any flag also accepts pipe references — `-` (auto-pick from upstream stdin), `@N` (n-th output), `@N.path` (jsonpath into output), `@*` (all primary values), `@stdin` / `@stdin:path` (whole envelope). See `dlazy --help` for details.
      
      ---
      
      换其他后端时参数由 `scripts/gen.mjs` 统一翻译,见 [`provider-cli.md`](provider-cli.md)。
      
    • platform-specs.md 2.1 KB
      <!-- 由 scripts/build-skills.mjs 从 shared/references/platform-specs.md 同步生成,不要直接改这里。 -->
      # 平台图片规格(可机检子集)
      
      `scripts/check_listing.py` 内置的规则来源与口径。**平台规则会变,以各平台最新官方文档为准**;
      需要覆盖时写一份 JSON 用 `--rules` 传入,结构与下表字段一一对应。
      
      ---
      
      ## 字段含义
      
      | 字段 | 含义 |
      | --- | --- |
      | `pure_white_bg` | 是否要求纯白 RGB(255,255,255) 背景 |
      | `bg_tolerance` | 判定「纯白」允许的单通道偏差 |
      | `bg_coverage` | 边缘一圈需要有多大比例落在容差内 |
      | `min_long_side` / `recommend_long_side` / `max_long_side` | 最长边像素 |
      | `min_occupancy` | 商品包围盒面积 ÷ 画面面积 的下限 |
      | `allow_alpha` | 是否允许透明通道 |
      | `allow_border` | 是否允许描边 / 外框 |
      | `formats` / `max_bytes` / `aspect` | 允许格式、体积上限、允许比例 |
      
      ---
      
      ## 内置规则
      
      | 平台 | 纯白底 | 最长边(下限 / 建议) | 主体占比 | 比例 | 体积上限 |
      | --- | --- | --- | --- | --- | --- |
      | `amazon` | 是 | 1000 / 1600 | ≥ 85% | 不限 | 10 MB |
      | `tiktok-shop` | 否 | 800 / 1600 | ≥ 60% | 1:1 或 3:4 | 5 MB |
      | `temu` | 是 | 800 / 1350 | ≥ 70% | 1:1 | 3 MB |
      | `shopee` | 否 | 500 / 1024 | ≥ 55% | 1:1 | 2 MB |
      | `shopify` | 否 | 1024 / 2048 | 不限 | 不限 | 20 MB |
      | `taobao` | 是 | 800 / 1200 | ≥ 70% | 1:1 | 3 MB |
      
      ---
      
      ## 几个容易踩的点
      
      - **透明 PNG**:Amazon 会把透明像素转成黑色。永远压平成白底 JPEG 再传。
      - **「白底」不等于「看起来是白的」**:棚拍的浅灰墙(约 RGB 208)肉眼像白,机检直接判不合格。
      - **主体占比**:留白过多是最常见的驳回原因之一,比分辨率不够更常见。
      - **文字 / 水印 / 拼图**:像素层测不了,交给 `detect-task` 的视觉模型或人工。
      - **自动修复的边界**:`--fix` 能压白底、按占比重构画布、补分辨率、压体积;
        它**不会**修图,也不会去水印——那是 `item-repair` 和 `remove-watermark` 的活。
      
    • provider-cli.md 4.7 KB
      <!-- 由 scripts/build-skills.mjs 从 shared/references/provider-cli.md 同步生成,不要直接改这里。 -->
      # 后端调用参考
      
      技能正文只写「要生成什么」。认证、计费、错误码、输出结构这些每个技能都一样的东西放在这里,
      **用到时再读**,不占技能的常驻上下文。
      
      ---
      
      ## 一、认证
      
      ### 默认后端 dLazy
      
      ```bash
      dlazy login            # 设备码流程,远程 shell 也能用,自动写入本地配置
      dlazy auth set <KEY>   # 已有 key 时直接写入
      ```
      
      key 存在用户配置目录(macOS/Linux `~/.dlazy/config.json`,Windows `%USERPROFILE%\.dlazy\config.json`),
      权限限本机用户。也可以每次调用用环境变量 `DLAZY_API_KEY` 传入。
      
      手动获取:登录 [dlazy.com](https://dlazy.com) → [API Key 页面](https://dlazy.com/dashboard/organization/api-key)。
      key 按组织隔离,可随时轮换或吊销。
      
      ### 其他后端
      
      本技能库不锁定单一厂商。配好任意一家的 key 即可跑:
      
      | 后端 | 环境变量 | 说明 |
      | --- | --- | --- |
      | `dlazy` | `dlazy login` 或 `DLAZY_API_KEY` | 默认,最省事 |
      | `openai` | `OPENAI_API_KEY` | 走 `/v1/images/edits` 与 `/v1/images/generations` |
      | `gemini` | `GEMINI_API_KEY` | Nano Banana 系列 |
      | `fal` | `FAL_KEY` | |
      | `replicate` | `REPLICATE_API_TOKEN` | |
      | `ark` | `ARK_API_KEY` + `ARK_MODEL` | 火山方舟,模型 ID 需按开通情况填 |
      
      选路优先级:`--provider` 参数 > `PROVIDER` 环境变量 > 第一个配了 key 的 > `dlazy`。
      
      ```bash
      node scripts/gen.mjs --doctor     # 看当前哪个后端可用
      ```
      
      各后端的模型 ID 可用 `GEN_MODEL_OPENAI` / `GEN_MODEL_GEMINI` / `GEN_MODEL_FAL` /
      `GEN_MODEL_REPLICATE` / `GEN_MODEL_ARK` 覆盖。**厂商目录会变,以各家最新文档为准。**
      
      ---
      
      ## 二、两种调用方式
      
      ### 方式 A:统一入口(推荐)
      
      ```bash
      node scripts/gen.mjs --task <技能名> --prompt '...' --images a.jpg b.jpg --save out.jpg
      ```
      
      它负责:后端选路、默认尺寸档位、失败重试(429/5xx 指数退避)、落盘建目录、成本估算。
      
      ```bash
      node scripts/gen.mjs --task flat-lay --prompt '...' --dry-run   # 不调用不计费,只看要发什么
      node scripts/gen.mjs --help
      ```
      
      ### 方式 B:直接用 dLazy CLI
      
      不想引入 Node 依赖时,技能正文里的 `dlazy ...` 命令可以原样执行,效果等价。
      
      ```bash
      npx @dlazy/cli@1.2.3 <command>     # 不装全局二进制
      ```
      
      - CLI 源码:[github.com/dlazy-ai/cli](https://github.com/dlazy-ai/cli) · npm 包 `@dlazy/cli`
      
      ---
      
      ## 三、数据流向
      
      调用 dLazy 时:提示词与参数发往 `api.dlazy.com`;传入的本地图片会上传到 `files.dlazy.com`
      供模型读取;产出 URL 同样托管在 `files.dlazy.com`。这是云端生成 API 的通用形态。
      换成其他后端时,数据流向对应厂商,不经过 dLazy。
      
      ---
      
      ## 四、输出结构
      
      `gen.mjs`(加 `--json`):
      
      ```json
      {
        "ok": true,
        "task": "flat-lay",
        "provider": "dlazy",
        "model": "gpt-image-2",
        "files": ["docs/flat-lay/output-sku001.jpg"],
        "texts": [],
        "estimatedCredits": 60,
        "elapsedMs": 58213
      }
      ```
      
      dLazy CLI 原生:
      
      ```json
      {
        "ok": true,
        "result": {
          "tool": "gpt-image-2",
          "data": { "urls": ["https://files.dlazy.com/data/ai/....jpg"] },
          "savedPath": "docs/flat-lay/example-output.jpg"
        }
      }
      ```
      
      加 `--no-wait` 的异步任务不返回 `data`,返回 `task: { generateId, status }`,
      用 `dlazy status <generateId> --wait` 轮询。
      
      文本类模型(如质检)产出在 `result.data.texts[0]`:
      
      ```bash
      dlazy claude-sonnet-5 --prompt '...' --images x.jpg \
        | python3 -c 'import sys,json;print(json.load(sys.stdin)["result"]["data"]["texts"][0])'
      ```
      
      ---
      
      ## 五、错误处理
      
      | Code | 类型 | 示例 |
      | --- | --- | --- |
      | 401 | 未授权 / 无 key | `ok: false, code: "unauthorized"` |
      | 501 | 缺必填参数 | `error: required option '--prompt <prompt>' not specified` |
      | 502 | 本地文件读不到 | `Error: Image file not found: ...` |
      | 503 | 余额不足 | `ok: false, code: "insufficient_balance"` |
      | 503 | 服务端错误 | `HTTP status code error (500)` |
      | 504 | 异步任务失败 | `=== Generation Failed ===` / `Prompt violates safety policy` |
      
      **给 Agent 的硬性要求**
      
      1. 命中 `insufficient_balance` → 明确告诉用户算力不足,并给出充值入口
         <https://dlazy.com/dashboard/organization/settings?tab=credits>
      2. 命中 `unauthorized` / 缺 key → 告诉用户去 <https://dlazy.com/dashboard/organization/api-key>
         取 key,用 `dlazy auth set <key>` 存好再继续。
      3. 用 `gen.mjs` 时,429 与 5xx 已自动重试;仍失败才向用户报错。
      4. **不要**为了「跑通」而偷偷降级参数(尺寸、档位、批量),先问用户。
      
  • scripts
    • lib
      • miniyaml.mjs 2.8 KB · in bundle
      • providers.mjs 11.9 KB · in bundle
      • tasks.json 3.1 KB
        {
          "_note": "技能 → 默认模型与参数。dlazy 列为默认后端的模型名;其他后端走 providers.mjs 的通用映射,可用 GEN_MODEL_<PROVIDER> 覆盖。",
          "_credits": { "gpt-image-2": 60, "seedream-5.0": 30, "seedream-5.0-pro": 45, "banana-pro": 25, "claude-sonnet-5": 3 },
          "tasks": {
            "flat-lay":                { "model": "gpt-image-2",      "size": "1024x1536", "quality": "high",   "format": "jpeg" },
            "wear-everything":         { "model": "gpt-image-2",      "size": "1024x1536", "quality": "medium", "format": "jpeg" },
            "image-fusion":            { "model": "seedream-5.0",     "size": "3:4",       "resolution": "2k" },
            "one-shot":                { "model": "gpt-image-2",      "size": "1024x1536", "quality": "medium", "format": "jpeg" },
            "fission-pattern":         { "model": "gpt-image-2",      "size": "1024x1536", "quality": "medium", "format": "jpeg" },
            "item-detail":             { "model": "seedream-5.0-pro", "size": "3:4",       "resolution": "2k" },
            "creative-scene":          { "model": "banana-pro",       "size": "1024x1536", "format": "jpeg" },
            "batch-image":             { "model": "seedream-5.0",     "size": "3:4",       "resolution": "2k" },
            "to-3d":                   { "model": "gpt-image-2",      "size": "1024x1024", "quality": "high",   "format": "jpeg" },
            "clothing-extraction":     { "model": "gpt-image-2",      "size": "1024x1024", "quality": "high",   "format": "jpeg" },
            "fabric-on-body":          { "model": "gpt-image-2",      "size": "1024x1536", "quality": "high",   "format": "jpeg" },
            "clothing-detail":         { "model": "gpt-image-2",      "size": "1024x1024", "quality": "high",   "format": "jpeg" },
            "clothing-grass-planting": { "model": "gpt-image-2",      "size": "1024x1536", "quality": "medium", "format": "jpeg" },
            "item-selling-point":      { "model": "seedream-5.0-pro", "size": "1:1",       "resolution": "2k" },
            "item-change-background":  { "model": "gpt-image-2",      "size": "1024x1024", "quality": "high",   "format": "jpeg" },
            "remove-watermark":        { "model": "gpt-image-2",      "size": "1024x1024", "quality": "high",   "format": "jpeg" },
            "material-enhancement":    { "model": "gpt-image-2",      "size": "1024x1024", "quality": "high",   "format": "jpeg" },
            "item-repair":             { "model": "gpt-image-2",      "size": "1024x1024", "quality": "high",   "format": "jpeg" },
            "detect-task":             { "model": "claude-sonnet-5",  "text": true },
            "listing-optimizer":       { "model": "gpt-image-2",      "size": "1024x1024", "quality": "high",   "format": "jpeg" },
            "cross-border-localize":   { "model": "seedream-5.0-pro", "size": "1:1",       "resolution": "2k" },
            "brand-kit":               { "model": "gpt-image-2",      "size": "1024x1536", "quality": "high",   "format": "jpeg" },
            "platform-compliance":     { "model": "claude-sonnet-5",  "text": true },
            "main-image-video":        { "model": "$DLAZY_VIDEO_MODEL", "video": true },
            "product-video-ad":        { "model": "$DLAZY_VIDEO_MODEL", "video": true },
            "ugc-testimonial":         { "model": "$DLAZY_VIDEO_MODEL", "video": true }
          }
        }
        
    • batch.mjs 10.8 KB · in bundle
    • brand.mjs 4.4 KB · in bundle
    • check_listing.py 15.6 KB
      #!/usr/bin/env python3
      # ⚠️ 由 scripts/build-skills.mjs 从 shared/scripts/check_listing.py 同步生成,不要直接改这里。
      """
      check_listing.py —— 上架前的客观合规校验。
      
      detect-task 用视觉模型判断「像不像真的」,这个脚本判断「传上去会不会被驳回」。
      两者互补:一个主观、一个客观;一个要花算力、一个只读像素。
      
        python3 scripts/check_listing.py main.jpg --platform amazon
        python3 scripts/check_listing.py imgs/*.jpg --platform amazon --json
        python3 scripts/check_listing.py raw.png --platform amazon --fix out/
      
      退出码:0 全部通过(含仅警告);1 存在驳回风险;2 参数或读图错误。
      
      ⚠️ 平台规则会变。本文件内置的是可机检子集,以各平台最新官方文档为准;
         需要覆盖时用 --rules your-rules.json(结构见 references/platform-specs.md)。
      """
      from __future__ import annotations
      
      import argparse
      import json
      import os
      import sys
      
      try:
          from PIL import Image, ImageChops
      except ImportError:
          sys.exit("需要 Pillow:pip install Pillow")
      
      # ---------------------------------------------------------------- 规则集
      
      RULES = {
          "amazon": {
              "label": "Amazon 主图",
              "pure_white_bg": True, "bg_tolerance": 2, "bg_coverage": 0.98,
              "min_long_side": 1000, "recommend_long_side": 1600, "max_long_side": 10000,
              "min_occupancy": 0.85, "allow_alpha": False, "allow_border": False,
              "formats": ["JPEG", "PNG", "TIFF", "GIF"], "max_bytes": 10 * 1024**2,
              "aspect": None,
              "notes": "主图禁文字/logo/水印/拼图;透明 PNG 会被转黑底,必须压平为白底 JPEG。",
          },
          "tiktok-shop": {
              "label": "TikTok Shop 主图",
              "pure_white_bg": False, "bg_tolerance": 8, "bg_coverage": 0.90,
              "min_long_side": 800, "recommend_long_side": 1600, "max_long_side": 8000,
              "min_occupancy": 0.60, "allow_alpha": False, "allow_border": False,
              "formats": ["JPEG", "PNG"], "max_bytes": 5 * 1024**2,
              "aspect": [(1, 1), (3, 4)],
              "notes": "禁边框与水印;1:1 或 3:4。",
          },
          "temu": {
              "label": "Temu 主图",
              "pure_white_bg": True, "bg_tolerance": 6, "bg_coverage": 0.95,
              "min_long_side": 800, "recommend_long_side": 1350, "max_long_side": 8000,
              "min_occupancy": 0.70, "allow_alpha": False, "allow_border": False,
              "formats": ["JPEG", "PNG"], "max_bytes": 3 * 1024**2,
              "aspect": [(1, 1)],
              "notes": "主图须方图、干净背景、无促销文字。",
          },
          "shopee": {
              "label": "Shopee 主图",
              "pure_white_bg": False, "bg_tolerance": 10, "bg_coverage": 0.85,
              "min_long_side": 500, "recommend_long_side": 1024, "max_long_side": 8000,
              "min_occupancy": 0.55, "allow_alpha": False, "allow_border": True,
              "formats": ["JPEG", "PNG"], "max_bytes": 2 * 1024**2,
              "aspect": [(1, 1)],
              "notes": "方图;单张 ≤2MB。",
          },
          "shopify": {
              "label": "Shopify 商品图",
              "pure_white_bg": False, "bg_tolerance": 12, "bg_coverage": 0.0,
              "min_long_side": 1024, "recommend_long_side": 2048, "max_long_side": 20000,
              "min_occupancy": 0.0, "allow_alpha": True, "allow_border": True,
              "formats": ["JPEG", "PNG", "WEBP"], "max_bytes": 20 * 1024**2,
              "aspect": None,
              "notes": "平台无硬性约束;2048×2048 方图便于缩放与放大镜。",
          },
          "taobao": {
              "label": "淘宝 / 1688 主图",
              "pure_white_bg": True, "bg_tolerance": 6, "bg_coverage": 0.92,
              "min_long_side": 800, "recommend_long_side": 1200, "max_long_side": 8000,
              "min_occupancy": 0.70, "allow_alpha": False, "allow_border": False,
              "formats": ["JPEG", "PNG"], "max_bytes": 3 * 1024**2,
              "aspect": [(1, 1)],
              "notes": "主图方图;白底图另有独立坑位要求。",
          },
      }
      
      PASS, WARN, FAIL = "pass", "warn", "fail"
      MARK = {PASS: "✓", WARN: "!", FAIL: "✗"}
      
      # ---------------------------------------------------------------- 度量
      
      def border_ring(img, frac=0.02):
          """取四边一圈像素,用来判定背景色。"""
          w, h = img.size
          d = max(1, int(min(w, h) * frac))
          px = img.load()
          out = []
          for y in list(range(d)) + list(range(h - d, h)):
              for x in range(0, w, max(1, w // 200)):
                  out.append(px[x, y])
          for x in list(range(d)) + list(range(w - d, w)):
              for y in range(0, h, max(1, h // 200)):
                  out.append(px[x, y])
          return out
      
      
      def bg_stats(img, tolerance):
          """返回 (背景基色, 纯白比例, 与基色一致的比例)。"""
          ring = border_ring(img)
          if not ring:
              return (255, 255, 255), 0.0, 0.0
          white = sum(1 for p in ring if all(abs(c - 255) <= tolerance for c in p[:3]))
          # 众数近似:把颜色量化到 8 级再统计
          buckets = {}
          for p in ring:
              k = tuple(c // 32 for c in p[:3])
              buckets[k] = buckets.get(k, 0) + 1
          mode_k = max(buckets, key=buckets.get)
          members = [p[:3] for p in ring if tuple(c // 32 for c in p[:3]) == mode_k]
          base = tuple(round(sum(c[i] for c in members) / len(members)) for i in range(3))
          same = sum(1 for p in ring
                     if sum((a - b) ** 2 for a, b in zip(p[:3], base)) ** 0.5 <= 24)
          return base, white / len(ring), same / len(ring)
      
      
      def subject_bbox(img, base, thresh=18):
          """相对背景基色做差,得到主体包围盒与占位面积。"""
          w, h = img.size
          bg = Image.new("RGB", img.size, tuple(int(c) for c in base))
          diff = ImageChops.difference(img.convert("RGB"), bg).convert("L")
          mask = diff.point(lambda v: 255 if v > thresh else 0)
          box = mask.getbbox()
          if not box:
              return None, 0.0, 0.0
          ink = sum(mask.histogram()[1:]) / float(w * h)
          x0, y0, x1, y1 = box
          return box, ((x1 - x0) * (y1 - y0)) / float(w * h), ink
      
      
      def has_border(img, base):
          """最外一圈是否是一条与背景不同的均匀细边。"""
          w, h = img.size
          if min(w, h) < 20:
              return False
          px = img.convert("RGB").load()
          ring = [px[x, 0] for x in range(0, w, max(1, w // 100))] + \
                 [px[x, h - 1] for x in range(0, w, max(1, w // 100))]
          if not ring:
              return False
          far = sum(1 for p in ring
                    if sum((a - b) ** 2 for a, b in zip(p, base)) ** 0.5 > 40)
          return far / len(ring) > 0.9
      
      
      def ratio_ok(w, h, allowed, tol=0.02):
          if not allowed:
              return True, f"{w}:{h}"
          r = w / h
          for a, b in allowed:
              if abs(r - a / b) <= tol:
                  return True, f"{a}:{b}"
          return False, f"{r:.3f}"
      
      # ---------------------------------------------------------------- 校验
      
      def check(path, rules):
          try:
              img = Image.open(path)
              img.load()
          except Exception as e:  # noqa: BLE001
              return {"file": path, "error": str(e), "checks": [], "verdict": "error"}
      
          fmt = img.format
          size_bytes = os.path.getsize(path)
          w, h = img.size
          has_alpha = img.mode in ("RGBA", "LA", "PA") or "transparency" in img.info
          rgb = img.convert("RGB")
          base, white_frac, same_frac = bg_stats(rgb, rules["bg_tolerance"])
          box, occ, ink = subject_bbox(rgb, base)
      
          out = []
      
          def add(name, level, detail, measured=None, required=None):
              out.append({"check": name, "level": level, "detail": detail,
                          "measured": measured, "required": required})
      
          # 尺寸
          long_side = max(w, h)
          if long_side < rules["min_long_side"]:
              add("分辨率", FAIL, f"最长边 {long_side}px,低于下限", long_side, rules["min_long_side"])
          elif long_side < rules["recommend_long_side"]:
              add("分辨率", WARN, f"最长边 {long_side}px,达标但不足以触发放大镜",
                  long_side, rules["recommend_long_side"])
          elif long_side > rules["max_long_side"]:
              add("分辨率", FAIL, f"最长边 {long_side}px 超上限", long_side, rules["max_long_side"])
          else:
              add("分辨率", PASS, f"{w}×{h}", long_side, rules["recommend_long_side"])
      
          # 比例
          ok, got = ratio_ok(w, h, rules["aspect"])
          add("画面比例", PASS if ok else FAIL,
              f"{w}:{h}" if ok else f"实际 {got},不在允许比例内", got,
              rules["aspect"] and "/".join(f"{a}:{b}" for a, b in rules["aspect"]))
      
          # 背景
          if rules["pure_white_bg"]:
              need = rules["bg_coverage"]
              if white_frac >= need:
                  add("纯白背景", PASS, f"边缘 {white_frac:.1%} 为 RGB(255,255,255)±{rules['bg_tolerance']}",
                      round(white_frac, 4), need)
              else:
                  add("纯白背景", FAIL,
                      f"边缘仅 {white_frac:.1%} 为纯白,背景基色约 RGB{tuple(int(c) for c in base)}",
                      round(white_frac, 4), need)
          else:
              add("背景一致性", PASS if same_frac >= rules["bg_coverage"] else WARN,
                  f"边缘 {same_frac:.1%} 颜色一致(基色 RGB{tuple(int(c) for c in base)})",
                  round(same_frac, 4), rules["bg_coverage"])
      
          # 主体占比
          if rules["min_occupancy"] > 0:
              if occ >= rules["min_occupancy"]:
                  add("主体占比", PASS, f"包围盒占画面 {occ:.1%}", round(occ, 4), rules["min_occupancy"])
              else:
                  add("主体占比", FAIL,
                      f"包围盒仅占 {occ:.1%},商品太小(留白过多)", round(occ, 4), rules["min_occupancy"])
      
          # 透明通道
          if has_alpha and not rules["allow_alpha"]:
              add("透明通道", FAIL, "含 alpha;上传后透明像素可能被转为黑色,须压平为白底", True, False)
          else:
              add("透明通道", PASS, "无 alpha" if not has_alpha else "允许 alpha", has_alpha, rules["allow_alpha"])
      
          # 边框
          if not rules["allow_border"]:
              b = has_border(rgb, base)
              add("边框", FAIL if b else PASS,
                  "检出一圈与背景不同的描边" if b else "无描边", b, False)
      
          # 格式与体积
          add("文件格式", PASS if fmt in rules["formats"] else FAIL,
              f"{fmt}", fmt, "/".join(rules["formats"]))
          add("文件体积", PASS if size_bytes <= rules["max_bytes"] else FAIL,
              f"{size_bytes / 1024**2:.2f} MB", size_bytes, rules["max_bytes"])
      
          # 色彩模式
          add("色彩模式", PASS if img.mode in ("RGB", "RGBA", "L", "P") else WARN,
              img.mode, img.mode, "RGB")
      
          # 需模型判定的项 —— 不假装能测
          add("文字 / 水印 / 拼图", "manual",
              "像素层判不了,交给 detect-task 或人工过一眼", None, None)
      
          fails = [c for c in out if c["level"] == FAIL]
          warns = [c for c in out if c["level"] == WARN]
          verdict = "reject-risk" if fails else ("warn" if warns else "pass")
          return {
              "file": path, "platform": rules["label"], "verdict": verdict,
              "width": w, "height": h, "format": fmt, "bytes": size_bytes,
              "bgBase": [int(c) for c in base], "whiteFraction": round(white_frac, 4),
              "occupancy": round(occ, 4), "inkCoverage": round(ink, 4), "bbox": box,
              "checks": out, "notes": rules["notes"],
          }
      
      # ---------------------------------------------------------------- 自动修复
      
      def fix(path, rules, outdir):
          """压白底 → 按目标占比裁切 → 补足分辨率 → 存合规 JPEG。"""
          img = Image.open(path)
          img.load()
          if img.mode in ("RGBA", "LA", "PA") or "transparency" in img.info:
              rgba = img.convert("RGBA")
              canvas = Image.new("RGB", rgba.size, (255, 255, 255))
              canvas.paste(rgba, mask=rgba.split()[-1])
              img = canvas
          else:
              img = img.convert("RGB")
      
          base, _, _ = bg_stats(img, rules["bg_tolerance"])
          box, occ, _ = subject_bbox(img, base)
      
          if rules["pure_white_bg"] and tuple(int(c) for c in base) != (255, 255, 255):
              # 把接近基色的像素刷成纯白,保留主体
              bg = Image.new("RGB", img.size, tuple(int(c) for c in base))
              diff = ImageChops.difference(img, bg).convert("L")
              mask = diff.point(lambda v: 255 if v > 18 else 0)
              white = Image.new("RGB", img.size, (255, 255, 255))
              img = Image.composite(img, white, mask)
              base = (255, 255, 255)
      
          target = rules["min_occupancy"] or 0.85
          if box and occ < target:
              x0, y0, x1, y1 = box
              bw, bh = x1 - x0, y1 - y0
              crop = img.crop(box)
              square = bool(rules["aspect"]) and (1, 1) in rules["aspect"]
              if square:
                  # 方图:包围盒非正方时,占比的物理上限是 bw*bh/max^2
                  side = max(int(round((bw * bh / target) ** 0.5)), bw, bh)
                  cw = ch = side
              else:
                  # 自由比例:画布跟随包围盒长宽比,占比可精确落在 target
                  k = target ** 0.5
                  cw, ch = max(int(round(bw / k)), bw), max(int(round(bh / k)), bh)
              canvas = Image.new("RGB", (cw, ch), tuple(int(c) for c in base))
              canvas.paste(crop, ((cw - bw) // 2, (ch - bh) // 2))
              img = canvas
      
          need = rules["recommend_long_side"]
          if max(img.size) < need:
              s = need / max(img.size)
              img = img.resize((round(img.width * s), round(img.height * s)), Image.LANCZOS)
      
          os.makedirs(outdir, exist_ok=True)
          stem = os.path.splitext(os.path.basename(path))[0]
          dst = os.path.join(outdir, f"{stem}-fixed.jpg")
          q = 92
          img.save(dst, "JPEG", quality=q, subsampling=0, optimize=True)
          while os.path.getsize(dst) > rules["max_bytes"] and q > 60:
              q -= 8
              img.save(dst, "JPEG", quality=q, subsampling=0, optimize=True)
          return dst
      
      # ---------------------------------------------------------------- 输出
      
      def render(reports):
          lines = []
          for r in reports:
              if r.get("error"):
                  lines.append(f"✗ {r['file']} —— 读图失败:{r['error']}")
                  continue
              head = {"pass": "通过", "warn": "通过(有提醒)", "reject-risk": "有驳回风险"}[r["verdict"]]
              lines.append(f"\n{r['file']}  ·  {r['platform']}  ·  {head}")
              lines.append(f"  {r['width']}×{r['height']} {r['format']} {r['bytes']/1024**2:.2f}MB"
                           f" · 背景 RGB{tuple(r['bgBase'])} · 主体占比 {r['occupancy']:.1%}")
              lines.append("")
              for c in r["checks"]:
                  mark = MARK.get(c["level"], "?")
                  lines.append(f"  {mark} {c['check']:<16} {c['detail']}")
              lines.append(f"\n  平台备注:{r['notes']}")
          return "\n".join(lines)
      
      
      def main():
          ap = argparse.ArgumentParser(description="上架前客观合规校验")
          ap.add_argument("images", nargs="+")
          ap.add_argument("--platform", default="amazon",
                          help="/".join(RULES) + "(默认 amazon)")
          ap.add_argument("--rules", help="自定义规则 JSON,覆盖内置规则集")
          ap.add_argument("--json", action="store_true")
          ap.add_argument("--fix", metavar="OUTDIR", help="自动修复并输出到该目录")
          a = ap.parse_args()
      
          table = dict(RULES)
          if a.rules:
              with open(a.rules, encoding="utf8") as f:
                  for k, v in json.load(f).items():
                      table[k] = {**table.get(k, RULES["amazon"]), **v}
          if a.platform not in table:
              sys.exit(f"未知平台 {a.platform},可选:{'/'.join(table)}")
          rules = table[a.platform]
      
          reports = [check(p, rules) for p in a.images]
      
          if a.fix:
              for r in reports:
                  if r.get("error"):
                      continue
                  r["fixed"] = fix(r["file"], rules, a.fix)
              reports = [check(r["fixed"], rules) | {"source": r["file"]}
                         if r.get("fixed") else r for r in reports]
      
          if a.json:
              print(json.dumps(reports, ensure_ascii=False, indent=2))
          else:
              print(render(reports))
      
          sys.exit(1 if any(r.get("verdict") == "reject-risk" for r in reports) else 0)
      
      
      if __name__ == "__main__":
          main()
      
    • gen.mjs 9.3 KB · in bundle
    • run_loop.mjs 9.5 KB · in bundle
  • skill.md 12.6 KB
    ---
    name: batch-image
    description: 多商品批量生图流水线。商品清单 CSV → 整批统一视觉的商拍图,带并发、重试、断点续跑、成本熔断与挑图联系表。当用户说「批量生图」「一批商品」「跑整个 SKU 表」「几百个商品出图」时使用。
    ---
    
    # batch-image — 多商品批量生图
    
    把单张生图变成**流水线**:一份商品清单 → 一整套风格统一的商拍图。
    
    批量的难点不是「跑很多次」,而是**跑出来的图要像一套**。本技能的核心是**规范段与变量段分离**:视觉规范逐字不变,只有商品描述随 SKU 变化。
    
    ---
    
    ## 生成效果示例
    
    | 输入:商品清单(2 个 SKU) |
    | --- |
    | `SKU001` <img src="../../docs/batch-image/sku-a-sweater.jpg" width="150"> `军绿色麻花针织圆领毛衣,落肩宽松版型` |
    | `SKU002` <img src="../../docs/batch-image/sku-b-shoes.jpg" width="150"> `黑色亮面皮革布洛克德比鞋,厚底系带` |
    
    实际执行的命令(循环体,两个 SKU 只有 `DESC` 与 `--images` 不同,规范段逐字相同):
    
    ```bash
    for sku in a-sweater b-shoes; do
      case $sku in
        a-sweater) DESC='军绿色麻花针织圆领毛衣,落肩宽松版型' ;;
        b-shoes)   DESC='黑色亮面皮革布洛克德比鞋,厚底系带' ;;
      esac
      dlazy seedream-5.0 \
        --prompt "电商商拍图。图1 是商品:${DESC}。商品的颜色、材质纹理、款式细节必须与图1完全一致。放置在同一套统一视觉里:纯米白色摄影棚背景,柔和顶光加左侧补光,45 度视角,画面下方留出统一的商品投影,构图与留白在整组图中保持一致。真实商业产品摄影,无文字无水印。" \
        --images docs/batch-image/sku-${sku}.jpg --size 1:1 --resolution 2k \
        --save docs/batch-image/example-output-${sku}.jpg
    done
    ```
    
    **输出:同一套视觉规范下的两个 SKU**
    
    | SKU001 | SKU002 |
    | --- | --- |
    | <img src="../../docs/batch-image/example-output-a-sweater.jpg" width="280"> | <img src="../../docs/batch-image/example-output-b-shoes.jpg" width="280"> |
    | 1:1 / 2K,5 credits | 1:1 / 2K,5 credits |
    
    两张的背景色、光位、视角与投影方向一致——因为规范段逐字相同;商品各自保真。100 个 SKU 就是这个循环跑 100 次,总价约 500 credits。
    
    ---
    
    ## 1、能力边界
    
    | 能力 | 说明 |
    | --- | --- |
    | 批量规模 | 一批最多 100 个商品(对应企业功能的规格) |
    | 成本优势 | 批量场景下单图算力成本可降至约 7 折 |
    | 协作 | 多人在线协作、出片进度实时追踪 |
    | 资产共享 | 共享模特 / 参考图 / 作品 / 算力点 |
    | 本技能落地 | 清单驱动循环 + 并发控制 + 失败重试 + 按 SKU 归档 + 汇总报告 |
    
    **不做**:不在同一批里混用不同视觉规范(那不是一批);不跳过抽样质检直接全量上架;不因为批量就放宽商品保真要求。
    
    ---
    
    ## 2、输入素材规则
    
    生成前先自检这几条硬性约束:
    
    - 大小:**20KB ~ 15MB**
    - 分辨率:**大于 400×400**
    - 格式:**jpg / jpeg / png / webp**
    
    **商品清单(manifest)** 是批量的输入,建议 CSV:
    
    ```csv
    sku,image,desc
    SKU001,docs/batch-image/sku-a-sweater.jpg,军绿色麻花针织圆领毛衣,落肩宽松版型
    SKU002,docs/batch-image/sku-b-shoes.jpg,黑色亮面皮革布洛克德比鞋,厚底系带
    ```
    
    **每张商品图仍要满足单图规则**:20KB~15MB、>400×400、jpg/jpeg/png/webp。
    
    **批量前必做的事**
    
    | 步骤 | 说明 |
    | --- | --- |
    | ✅ 先跑通 1 个 SKU | 单个跑不对,跑 100 个只是错 100 次 |
    | ✅ 再抽样 5 个 SKU | 覆盖品类/颜色/深浅色的边界情况 |
    | ✅ 确认成本 | `--dry-run` 单价 × SKU 数 × batch 数 |
    | ❌ 直接全量 | 最容易烧算力的做法 |
    
    ---
    
    ## 3、规范段 / 变量段分离
    
    这是批量出统一视觉的唯一方法:
    
    ```text
    prompt = [规范段:整批逐字不变]  +  [变量段:每个 SKU 不同]
    ```
    
    | 段 | 内容 | 是否变 |
    | --- | --- | --- |
    | 规范段 | 背景、光位、机位、构图留白、投影、色调、输出风格、`no text, no watermark` | **整批逐字不变** |
    | 变量段 | 商品品类 + 颜色 + 材质 + 关键结构特征 | 每个 SKU 不同 |
    
    写成 shell:
    
    ```bash
    SPEC='放置在同一套统一视觉里:纯米白色摄影棚背景,柔和顶光加左侧补光,45 度视角,画面下方留出统一的商品投影,构图与留白在整组图中保持一致。真实商业产品摄影,无文字无水印。'
    
    while IFS=, read -r SKU IMG DESC; do
      dlazy seedream-5.0 \
        --prompt "电商商拍图。图1 是商品:${DESC}。商品的颜色、材质纹理、款式细节必须与图1完全一致。${SPEC}" \
        --images "$IMG" --size 1:1 --resolution 2k \
        --save "docs/batch-image/out/${SKU}.jpg"
    done < manifest.csv
    ```
    
    **规范段一旦改动就必须整批重跑**——否则同一批里会出现两种视觉。
    
    ---
    
    ## 4、并发、重试与进度
    
    **并发**:dlazy 是单次调用一个任务,批量靠 shell 并发。控制在 4~6 路,太高容易触发限流。
    
    ```bash
    run_one() { ... }               # 单个 SKU 的生成逻辑
    export -f run_one
    xargs -P 5 -I{} bash -c 'run_one {}' < sku-list.txt     # 5 路并发
    ```
    
    **重试**:网络与服务端偶发失败很正常,包一层重试。
    
    ```bash
    for attempt in 1 2 3; do
      dlazy ... && break
      echo "retry $attempt for $SKU" >&2; sleep $((attempt * 10))
    done
    ```
    
    **异步模式**:SKU 很多时用 `--no-wait` 先把任务全部提交,拿到 `generateId` 后再统一轮询,避免长时间占着进程。
    
    ```bash
    GID=$(dlazy seedream-5.0 --no-wait ... | python3 -c 'import sys,json;print(json.load(sys.stdin)["result"]["task"]["generateId"])')
    echo "$SKU,$GID" >> tasks.csv
    # 稍后统一收结果
    while IFS=, read -r SKU GID; do dlazy status "$GID" --wait; done < tasks.csv
    ```
    
    **进度与汇总**:每个 SKU 记一行状态,跑完输出报告。
    
    ```bash
    echo "$SKU,$(test -f out/$SKU.jpg && echo ok || echo fail)" >> report.csv
    awk -F, '{c[$2]++} END{for(k in c) print k, c[k]}' report.csv
    ```
    
    ---
    
    ## 5、工具调用
    
    本技能使用 dLazy 的 **`seedream-5.0`**(批量场景的关键指标是**单图成本**与**参考图支持**:本模型单张约 5 credits、支持最多 10 张参考图、支持 2K/3K/4K;100 个 SKU 的成本量级从数百降到数十。对商品保真要求极高的批次改用 `gpt-image-2`)。
    
    ### 调用方式
    
    两种等价写法,选一种。统一入口会自动选后端、失败重试、建目录落盘、估算成本:
    
    ```bash
    # A. 统一入口(推荐):可切任意后端,加 --dry-run 不计费空跑
    node scripts/gen.mjs --task batch-image \
      --prompt '<见下方 Prompt 模板>' \
      --images <按下表顺序> \
      --save output/batch-image-<sku>.jpg
    
    # B. 直接用 dLazy CLI(不想引入 Node 依赖时,效果等价)
    dlazy seedream-5.0 --prompt '...' --images ... --save output/batch-image.jpg
    ```
    
    **参数约定(本技能固定用法)**
    
    | 参数 | 取值 | 理由 |
    | --- | --- | --- |
    | `--images` | `[该 SKU 的商品图]`(+ 整批共用的参考图 / 模特图) | 共用素材放在固定位置,prompt 里用固定图号引用 |
    | `--size` | 整批统一(`1:1` 主图 / `3:4` 竖版) | 不统一就不是一套 |
    | `--resolution` | `2k` | 上架足够;4K 只给需要印刷的批次 |
    | `--batch` | `1`(批量场景靠 SKU 数量,不靠 batch) | batch 会让成本翻倍 |
    | `--no-wait` | SKU 很多时开启 | 先提交后轮询,避免长时间阻塞 |
    | `--save` | `docs/batch-image/out/<SKU>.jpg` | 用 SKU 命名,便于回填到商品库 |
    
    > **模型切换**:`seedream-5.0` 便宜、适合氛围与场景图;对商品保真要求极高(图案位置、五金细节)的批次换 `gpt-image-2`,成本约 6 倍。同一批内不要混用两个模型。
    
    ### Command Examples
    
    ```bash
    # basic call: 单个 SKU(先跑通这个,再谈批量)
    dlazy seedream-5.0 \
      --prompt '电商商拍图。图1 是商品:军绿色麻花针织圆领毛衣,落肩宽松版型。商品的颜色、材质纹理、款式细节必须与图1完全一致。放置在同一套统一视觉里:纯米白色摄影棚背景,柔和顶光加左侧补光,45 度视角,画面下方留出统一的商品投影。真实商业产品摄影,无文字无水印。' \
      --images docs/batch-image/sku-a-sweater.jpg --size 1:1 --resolution 2k
    
    # complex call: 完整流水线(清单驱动 + 5 路并发 + 3 次重试 + 汇总报告)
    cat > manifest.csv <<'CSV'
    SKU001,docs/batch-image/sku-a-sweater.jpg,军绿色麻花针织圆领毛衣,落肩宽松版型
    SKU002,docs/batch-image/sku-b-shoes.jpg,黑色亮面皮革布洛克德比鞋,厚底系带
    CSV
    
    SPEC='放置在同一套统一视觉里:纯米白色摄影棚背景,柔和顶光加左侧补光,45 度视角,画面下方留出统一的商品投影,构图与留白在整组图中保持一致。真实商业产品摄影,无文字无水印。'
    mkdir -p docs/batch-image/out
    
    run_one() {
      IFS=, read -r SKU IMG DESC <<< "$1"
      for attempt in 1 2 3; do
        dlazy seedream-5.0 \
          --prompt "电商商拍图。图1 是商品:${DESC}。商品的颜色、材质纹理、款式细节必须与图1完全一致。${SPEC}" \
          --images "$IMG" --size 1:1 --resolution 2k \
          --save "docs/batch-image/out/${SKU}.jpg" >/dev/null 2>&1 && break
        sleep $((attempt * 10))
      done
      if [ -f "docs/batch-image/out/${SKU}.jpg" ]; then echo "${SKU},ok"; else echo "${SKU},fail"; fi
    }
    export -f run_one; export SPEC
    
    xargs -P 5 -I{} bash -c 'run_one "{}"' < manifest.csv | tee docs/batch-image/report.csv
    awk -F, '{c[$2]++} END{printf "\n=== 汇总 ===\n"; for(k in c) printf "%s: %d\n", k, c[k]}' docs/batch-image/report.csv
    
    # 先估价:单价 × SKU 数
    dlazy seedream-5.0 --dry-run --prompt '...' --images a.jpg --size 1:1 | grep estimatedCost
    echo "SKU 数:$(wc -l < manifest.csv)"
    
    ```
    
    ### 延伸阅读
    
    | 要查什么 | 去哪 |
    | --- | --- |
    | 认证、多后端配置、输出结构、错误码 | [`references/provider-cli.md`](references/provider-cli.md) |
    | `seedream-5.0` 的全部可用参数 | [`references/model-flags.md`](references/model-flags.md) |
    | 统一入口的全部选项 | `node scripts/gen.mjs --help` |
    
    ## 6、Prompt 模板
    
    ```text
    【变量段 · 每个 SKU 不同】
    电商商拍图。图1 是商品:[品类 + 颜色 + 材质 + 关键结构特征]。
    商品的颜色、材质纹理、款式细节必须与图1完全一致。
    
    【规范段 · 整批逐字不变】
    放置在同一套统一视觉里:[背景],[光位],[机位/视角],
    画面下方留出统一的商品投影,构图与留白在整组图中保持一致。
    真实商业产品摄影,无文字无水印。
    ```
    
    **按问题追加的修正句**(改了就要整批重跑)
    
    | 问题 | 处理 |
    | --- | --- |
    | 深色商品在浅背景上发灰 | 规范段补 `深色商品需加右侧轮廓光勾边,避免与背景糊在一起` |
    | 不同 SKU 的商品大小忽大忽小 | 规范段补 `商品在画面中的占比统一为约 [X]%,边距一致` |
    | 投影方向不一致 | 规范段补 `投影统一朝画面右下方,长度约为商品高度的 [X]` |
    | 某些 SKU 保真度不够 | 那部分 SKU 单独用 `gpt-image-2` 重跑,并在报告里标注 |
    
    ---
    
    ## 7、执行流程
    
    1. **建清单**:`sku,image,desc` 三列 CSV,每张商品图先过单图规则校验。
    2. **写规范段**:背景 / 光位 / 机位 / 占比 / 投影 / 色调 / 输出风格——**定稿后不许改**。
    3. **跑通 1 个 SKU**:确认规范段和变量段都对。
    4. **抽样 5 个 SKU**:覆盖深色 / 浅色 / 反光 / 大件 / 小件的边界。
    5. **`--dry-run` 估总价**:单价 × SKU 数。
    6. **全量跑**:5 路并发 + 3 次重试;SKU 很多时用 `--no-wait` 提交后统一轮询。
    7. **看汇总报告**:失败的单独重跑。
    8. **抽样质检**:随机抽 10% 过 [detect-task](../detect-task/skill.md);不合格的 SKU 单独用 `gpt-image-2` 重跑。
    
    ---
    
    ## 8、常见问题
    
    | 现象 | 原因 | 处理 |
    | --- | --- | --- |
    | 同一批里视觉不统一 | 中途改了规范段 | 规范段定稿后不许改;改了就整批重跑 |
    | 深色商品糊进背景 | 缺轮廓光 | 规范段补轮廓光句,整批重跑 |
    | 商品占比忽大忽小 | 未约束占比 | 规范段补占比与边距句 |
    | 部分 SKU 保真度不够 | 低价模型的局限 | 那些 SKU 单独用 `gpt-image-2` 重跑 |
    | 跑到一半失败一堆 | 并发太高触发限流 | 并发降到 4~5,加重试与退避 |
    | 成本超预算 | 用了 `--batch` 或 4K | 批量场景 `--batch 1` + `2k` |
    | 跑完不知道哪些失败了 | 没有报告 | 用第四节的 report.csv + 汇总 |
    
    ---
    
    ## Tips
    
    Visit https://dlazy.com for more information.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related