diff --git a/README.md b/README.md index 7d66424..7e6ed21 100644 --- a/README.md +++ b/README.md @@ -95,18 +95,25 @@ cp -r Ecom-Prompt-Studio/ecom-prompt-sop ~/.agents/skills/ | `evals/promptfooconfig.yaml` | 触发回归:断言每条提示命中的技能名与 SOP 路径 | | `evals/promptfooconfig.quality.yaml` | 文本质量抽检:读取 SOP 原文生成提示词,按硬规则评分;图片输出仍由人工检查 | | `tools/qwen_image.py` | 命令行出图通道:提示词直接投喂 Qwen-Image 2.1 Pro,支持文生图与最多 10 张参考图的图生图 | +| `tools/IMAGE-GEN-AUTHORIZATION.md` | **出图授权规则(硬性)**:任何图片生成模型调用前须取得用户授权,说明用途与预计花费 | ## 云端出图(Qwen-Image 2.1 Pro) +> **出图先授权**:本项目限制任何图片生成模型(Qwen-Image 全系、wan/万相、Seedream、Gemini/Nano Banana、GPT-Image、FLUX、z-image 等,含 MCP 工具与网页端代操作)在调用前必须取得用户授权,并说明用途、模型、张数与预计花费。规则与记录字段见 [`tools/IMAGE-GEN-AUTHORIZATION.md`](tools/IMAGE-GEN-AUTHORIZATION.md)。`tools/qwen_image.py` 已内置闸门,缺 `--authorize` / `--purpose` / `--unit-price` 直接拒绝,不发出请求。 + `tools/qwen_image.py` 走阿里云百炼 DashScope 同步接口,把 SOP 产出的提示词直接变成图片,省去手工复制到网页端。密钥只从环境变量 `DASHSCOPE_API_KEY` 或 `--key-file` 读取,不写入仓库任何文件: ```bash export DASHSCOPE_API_KEY="sk-..." -python tools/qwen_image.py -p "提示词" --size 1024*1024 -o out -python tools/qwen_image.py -f prompt.txt -i front.jpg -i detail.jpg -n 2 --seed 42 +python tools/qwen_image.py -p "提示词" --size 1024*1024 -o out \ + --authorize "用户 2026-10-29 14:20 回复:可以" --purpose "产品A 天猫主图第 2 张" --unit-price 0.25 +python tools/qwen_image.py -f prompt.txt -i front.jpg -i detail.jpg -n 2 --seed 42 \ + --authorize "用户 2026-10-29 14:20 回复:可以" --purpose "产品A 主图组" --unit-price 0.25 ``` -图生图用于产品锁定和翻新:把产品实拍图作为参考图,提示词里写清保留清单与改动清单。提示词默认按原文投喂、不做模型改写,SOP 里的硬规则不会被稀释;只有提示词很短、希望模型自行扩写时才加 `--prompt-extend`。`-n` 最多 6 张,`--size` 支持 `auto` 或 `宽*高`,输出落在 `out/`(已在 `.gitignore` 中忽略)。`python tools/qwen_image.py --selftest` 可离线校验参数与响应解析。 +脚本在发请求前打印「张数 × 单价 = 预计花费」并追加写入 `out/authorization.log`(已在 `.gitignore` 中忽略,只留本机)。单价按官方页面当时数值填写,拿不到确切单价就先问用户,不猜数字。 + +图生图用于产品锁定和翻新:把产品实拍图作为参考图,提示词里写清保留清单与改动清单。提示词默认按原文投喂、不做模型改写,SOP 里的硬规则不会被稀释;只有提示词很短、希望模型自行扩写时才加 `--prompt-extend`。`-n` 最多 6 张,`--size` 支持 `auto` 或 `宽*高`,输出落在 `out/`(已在 `.gitignore` 中忽略)。`python tools/qwen_image.py --selftest` 可离线校验参数、授权闸门与响应解析。 ## 评测与 CI diff --git a/ecom-detail-page-prompts/SKILL.md b/ecom-detail-page-prompts/SKILL.md index 5497cbf..7bef5c1 100644 --- a/ecom-detail-page-prompts/SKILL.md +++ b/ecom-detail-page-prompts/SKILL.md @@ -41,6 +41,7 @@ description: "电商详情页生图提示词导演:基于上传的产品实拍 14. 定义一套**套图风格锁**——整套页面共用的色板、光影、字体气质、版式语言和装饰系统,每页原样复用。 15. 为每一页输出可直接交给生图工具执行的提示词。 16. 用户指定了生图工具的,读 `references/tool-adaptation.md`,把提示词调整为该工具偏好的写法;未指定则用通用写法。 +17. **不得自行出图**。本项目限制任何图片生成模型(Qwen-Image 全系、wan/万相、Seedream、Gemini/Nano Banana、GPT-Image、FLUX、z-image 等,含 MCP 工具与网页端代操作)在调用前必须先说明用途、模型、张数和预计花费,取得用户明确同意后才可调用;规则正本见仓库(及镜像工作区)`tools/IMAGE-GEN-AUTHORIZATION.md`,单独安装技能目录时不带该文件,约束仍按本句执行。未获授权时只交付提示词文本。 ## 硬规则 diff --git a/ecom-detail-page-prompts/references/tool-adaptation.md b/ecom-detail-page-prompts/references/tool-adaptation.md index c4b00c0..268c2b2 100644 --- a/ecom-detail-page-prompts/references/tool-adaptation.md +++ b/ecom-detail-page-prompts/references/tool-adaptation.md @@ -62,14 +62,20 @@ ## 六、本仓库命令行出图通道(Qwen-Image 2.1 Pro) +> **调用前必须取得用户授权**:本项目限制任何图片生成模型在调用前都要先说明用途与预计花费并取得同意,Qwen-Image 全系同样适用。规则见仓库 `tools/IMAGE-GEN-AUTHORIZATION.md`。脚本缺 `--authorize` / `--purpose` / `--unit-price` 会直接拒绝,不发出请求。 + 仓库内 `tools/qwen_image.py` 提供文生图与图生图命令行通道,走阿里云百炼 DashScope 同步接口,模型 `qwen-image-2.1-pro`。密钥只从环境变量 `DASHSCOPE_API_KEY` 或 `--key-file` 读取,不写入任何文件。 ```bash export DASHSCOPE_API_KEY="sk-..." # 也可用 --key-file 指向密钥文件 -python tools/qwen_image.py -p "提示词" -o out -python tools/qwen_image.py -f page03.txt -i product_front.jpg -i product_detail.jpg --size 1024*1536 +python tools/qwen_image.py -p "提示词" -o out \ + --authorize "用户 2026-10-29 14:20 回复:可以" --purpose "产品A 天猫详情页第 3 页" --unit-price 0.25 +python tools/qwen_image.py -f page03.txt -i product_front.jpg -i product_detail.jpg --size 1024*1536 \ + --authorize "..." --purpose "..." --unit-price 0.25 ``` 参数要点:`--size` 用 `宽*高`(也接受 `宽x高`)或 `auto`;`-n` 支持 1 到 6 张;`--seed` 固定随机种子;`-i` 可重复,最多 10 张参考图。提示词默认按原文投喂、不做模型改写,需要模型按简单描述扩写时加 `--prompt-extend`。输出默认落在 `out/`,该目录不进版本库。详情页逐页生成时,一页一个提示词文件,参考图顺序固定为产品主图在前、细节图在后。提示词含大量引号或换行时用 `-f` 传文件,避免命令行转义问题。 +授权三项:`--authorize` 填用户原话或带日期的授权说明,不得自行编造;`--purpose` 具体到产品、平台、页面位置;`--unit-price` 填官方页面当时的每张单价,拿不到就先问用户。整页批量时按总张数报花费,不要拆成多次小额授权。脚本会打印「张数 × 单价 = 预计花费」并写入 `out/authorization.log`,只留本机。 + 脚本只存在于仓库与镜像工作区,单独安装技能目录时不带 `tools/`。 diff --git a/ecom-main-image-prompts/SKILL.md b/ecom-main-image-prompts/SKILL.md index b770155..3c93769 100644 --- a/ecom-main-image-prompts/SKILL.md +++ b/ecom-main-image-prompts/SKILL.md @@ -32,6 +32,7 @@ description: "全平台电商主图生图提示词导演:基于上传的产品 7. 为每张图规划产品专属职责(见下方"动态规划"),沿买家决策顺序展开;同时定义整套图共用的**套图风格锁**。 8. 按"模特规则"和"图中文字规则"执行人物与文字约束。 9. 用户指定了生图工具的,读 `references/tool-adaptation.md`,把提示词调整为该工具偏好的写法;未指定则用通用写法。 +10. **不得自行出图**。本项目限制任何图片生成模型(Qwen-Image 全系、wan/万相、Seedream、Gemini/Nano Banana、GPT-Image、FLUX、z-image 等,含 MCP 工具与网页端代操作)在调用前必须先说明用途、模型、张数和预计花费,取得用户明确同意后才可调用;规则正本见仓库(及镜像工作区)`tools/IMAGE-GEN-AUTHORIZATION.md`,单独安装技能目录时不带该文件,约束仍按本句执行。未获授权时只交付提示词文本。 ## 严格规则 diff --git a/ecom-main-image-prompts/references/tool-adaptation.md b/ecom-main-image-prompts/references/tool-adaptation.md index b2482da..2409849 100644 --- a/ecom-main-image-prompts/references/tool-adaptation.md +++ b/ecom-main-image-prompts/references/tool-adaptation.md @@ -50,14 +50,20 @@ ## 五、本仓库命令行出图通道(Qwen-Image 2.1 Pro) +> **调用前必须取得用户授权**:本项目限制任何图片生成模型在调用前都要先说明用途与预计花费并取得同意,Qwen-Image 全系同样适用。规则见仓库 `tools/IMAGE-GEN-AUTHORIZATION.md`。脚本缺 `--authorize` / `--purpose` / `--unit-price` 会直接拒绝,不发出请求。 + 仓库内 `tools/qwen_image.py` 提供文生图与图生图命令行通道,走阿里云百炼 DashScope 同步接口,模型 `qwen-image-2.1-pro`。密钥只从环境变量 `DASHSCOPE_API_KEY` 或 `--key-file` 读取,不写入任何文件。 ```bash export DASHSCOPE_API_KEY="sk-..." # 也可用 --key-file 指向密钥文件 -python tools/qwen_image.py -p "提示词" -o out -python tools/qwen_image.py -f prompt.txt -i product_front.jpg -i product_detail.jpg --size 1024*1024 +python tools/qwen_image.py -p "提示词" -o out \ + --authorize "用户 2026-10-29 14:20 回复:可以" --purpose "产品A 天猫主图第 2 张" --unit-price 0.25 +python tools/qwen_image.py -f prompt.txt -i product_front.jpg -i product_detail.jpg --size 1024*1024 \ + --authorize "..." --purpose "..." --unit-price 0.25 ``` 参数要点:`--size` 用 `宽*高`(也接受 `宽x高`)或 `auto`;`-n` 支持 1 到 6 张;`--seed` 固定随机种子;`-i` 可重复,最多 10 张参考图。提示词默认按原文投喂、不做模型改写,需要模型按简单描述扩写时加 `--prompt-extend`。输出默认落在 `out/`,该目录不进版本库。提示词含大量引号或换行时用 `-f` 传文件,避免命令行转义问题。 +授权三项:`--authorize` 填用户原话或带日期的授权说明,不得自行编造;`--purpose` 具体到产品、平台、页面位置;`--unit-price` 填官方页面当时的每张单价,拿不到就先问用户。脚本会打印「张数 × 单价 = 预计花费」并写入 `out/authorization.log`,只留本机。 + 脚本只存在于仓库与镜像工作区,单独安装技能目录时不带 `tools/`。 diff --git a/tools/IMAGE-GEN-AUTHORIZATION.md b/tools/IMAGE-GEN-AUTHORIZATION.md new file mode 100644 index 0000000..9d7a813 --- /dev/null +++ b/tools/IMAGE-GEN-AUTHORIZATION.md @@ -0,0 +1,62 @@ +# 图片生成模型调用授权规则 + +> 本项目硬性限制。任何图片生成模型(含 Qwen-Image 全系、wan/万相、Seedream、Gemini/Nano Banana、GPT-Image、FLUX、z-image 等,无论走本仓库脚本、MCP 工具还是网页端)在发起生成前,必须先取得用户授权。未授权不得调用。 +> +> 2026-10-29 由用户指令建立。修改本文件等于修改项目红线,需用户明确同意。 + +## 一、为什么有这个文件 + +出图按张计费,一次批量任务可能几十上百张,费用会累积。用途不明时还可能生成不该生成的东西(未发布的款式、他人的图、含平台风险元素的画面)。所以每一次出图都要先讲清两件事:**用在哪里**、**大概花多少**,用户点头之后才动手。 + +授权不是一次性的通行证。不同批次、不同用途、不同模型各问一次。上一批同意了,不代表这一批可以默认继续。 + +## 二、发起前必须向用户说明的三项 + +1. **用途**:这批图要做什么、用在哪里(哪个产品、哪个平台、哪套主图或详情页的第几页)。说得出具体落点,不能只写"电商图"。 +2. **模型与通道**:本次将调用哪个模型(如 `qwen-image-2.1-pro`),走哪条通道(本仓库 `tools/qwen_image.py`、MCP 图片工具、网页端)。 +3. **预计花费**:`张数 × 单价 = 预计花费`。单价必须取当时官方页面的真实数字,不得凭记忆或按经验估。 + +## 三、单价的取值口径 + +- 官方入口:阿里云百炼控制台的模型详情页或「费用与成本」账单页,查看该模型当前的按张单价。 +- 不同模型、不同分辨率档位、是否含编辑、是否走折扣,单价都不同,不能混用。 +- 拿不到确切单价时,向用户说明"单价待确认",由用户提供或在控制台确认后填入。**不允许猜测一个数字填进去。** +- 本文档不写死价格,价格会随官方调整变化,写死就会误导。 + +## 四、命令行通道的执行方式 + +`tools/qwen_image.py` 已内置闸门:缺少下列任一参数直接拒绝调用,不会发出请求。 + +```bash +python tools/qwen_image.py -p "提示词" -n 4 --size 1024*1536 \ + --authorize "用户在 2026-10-29 14:20 回复:可以,按这个方案出" \ + --purpose "某某产品天猫详情页第 3 页材质特写" \ + --unit-price 0.25 +``` + +| 参数 | 作用 | 填法 | +|---|---|---| +| `--authorize` | 用户同意的凭证 | 用户原话摘录,或带日期的授权说明。不得由模型自行编造 | +| `--purpose` | 这批图的落点 | 具体到产品、平台、页面位置 | +| `--unit-price` | 每张单价(元) | 官方页面当时显示的数值 | + +脚本会在调用前把「用途、张数、单价、预计花费、模型、授权原话」打印到终端,并追加写入 `out/authorization.log` 作为审计记录。`out/` 已在 `.gitignore` 中忽略,该日志只留本机,不进版本库、不推送。 + +## 五、其他通道的执行方式 + +- **MCP 图片工具**(如 `bailian_image_gen`、`bailian_image_edit`):同样先在对话里向用户说明用途、模型、预计花费,取得明确回复后再调用。 +- **网页端手工出图**:操作者自行出图的,不受本文件约束;但由 Agent 代操作网页端时同样适用本规则。 +- **批量任务**:一批里包含多个提示词时,按整批的总张数报花费,不要拆成多次小额授权绕过确认。 +- **重试**:因效果不满意重跑同一批,属于新的一次调用,重新报花费。同一批内的参数微调(改一个词重试一张)可以在原授权范围内继续,但要在日志里记清楚。 + +## 六、禁止事项 + +1. 禁止在未取得用户同意时调用任何图片生成模型,包括"先跑一张试试效果"。 +2. 禁止自行填写 `--authorize`。该字段必须来自用户的真实回复。 +3. 禁止编造单价,或用旧价格充当当前价格。 +4. 禁止把一批拆成多批,用来规避单次授权的花费说明。 +5. 禁止在用户只同意了某一种用途后,把生成的图挪作他用。 + +## 七、授权记录的最小字段 + +每次调用至少留:时间、模型、通道、张数、尺寸、单价、预计花费、用途、用户授权原话。脚本自动写入 `out/authorization.log`;手工或 MCP 通道在对话记录里保留上述信息即可。 diff --git a/tools/qwen_image.py b/tools/qwen_image.py index b8a91ac..dc422dd 100644 --- a/tools/qwen_image.py +++ b/tools/qwen_image.py @@ -3,9 +3,14 @@ 密钥只从环境变量 DASHSCOPE_API_KEY 或 --key-file 读取,不落盘、不打印。 +授权闸门:本项目要求每次出图前取得用户授权(见 tools/IMAGE-GEN-AUTHORIZATION.md)。 +--authorize / --purpose / --unit-price 三项缺一不可,否则不发出请求。 + 用法: - python tools/qwen_image.py -p "提示词" -o out - python tools/qwen_image.py -f prompt.txt -i front.jpg -i detail.jpg --size 1024*1024 + python tools/qwen_image.py -p "提示词" -o out \ + --authorize "用户 2026-10-29 14:20 回复:可以" --purpose "产品A 天猫主图第2张" --unit-price 0.25 + python tools/qwen_image.py -f prompt.txt -i front.jpg -i detail.jpg --size 1024*1024 \ + --authorize "..." --purpose "..." --unit-price 0.25 python tools/qwen_image.py --selftest """ @@ -93,6 +98,50 @@ def walk(node): return urls +def check_authorization(authorize, purpose, unit_price, num, model, size, out_dir): + """出图前的授权闸门:三项齐全才放行,并打印与落盘预计花费。""" + missing = [ + name + for name, value in ( + ("--authorize", authorize), + ("--purpose", purpose), + ("--unit-price", unit_price), + ) + if value is None or (isinstance(value, str) and not value.strip()) + ] + if missing: + sys.exit( + "缺少授权参数:" + + "、".join(missing) + + "\n本项目要求每次出图前取得用户授权,先向用户说明用途与预计花费。" + "\n规则见 tools/IMAGE-GEN-AUTHORIZATION.md" + ) + if unit_price < 0: + sys.exit(f"--unit-price 不能为负:{unit_price}") + + total = round(unit_price * num, 4) + record = { + "time": time.strftime("%Y-%m-%d %H:%M:%S"), + "model": model, + "size": size, + "num": num, + "unit_price": unit_price, + "estimated_cost": total, + "purpose": purpose.strip(), + "authorize": authorize.strip(), + } + print( + f"[授权确认] 模型={model} 张数={num} 单价={unit_price}元 预计花费={total}元\n" + f" 用途={record['purpose']}\n" + f" 授权={record['authorize']}", + file=sys.stderr, + ) + out_dir.mkdir(parents=True, exist_ok=True) + with open(out_dir / "authorization.log", "a", encoding="utf-8") as f: + f.write(json.dumps(record, ensure_ascii=False) + "\n") + return record + + def request(payload, key, timeout): req = urllib.request.Request( API_URL, @@ -120,6 +169,8 @@ def download(url, out_dir, index): def selftest(): + import tempfile + assert normalize_size("1024*1024") == "1024*1024" assert normalize_size("1024x1536") == "1024*1536" assert normalize_size("auto") == "auto" @@ -137,9 +188,33 @@ def selftest(): # 默认必须关闭改写:长 SOP 原文投喂时不能被模型重写 defaults = {action.dest: action.default for action in build_parser()._actions} assert defaults["prompt_extend"] is False, "prompt_extend 默认应为 False" + assert defaults["authorize"] is None and defaults["purpose"] is None and defaults["unit_price"] is None fake = {"output": {"choices": [{"message": {"content": [{"image": "https://a/1.png"}]}}]}} assert parse_images(fake) == ["https://a/1.png"] assert parse_images({"output": {"results": [{"url": "x", "image": "https://a/2.png"}]}}) == ["https://a/2.png"] + + with tempfile.TemporaryDirectory() as tmp: + out = Path(tmp) + # 授权三项缺任一都必须拦住,且不写日志 + for kwargs in ( + {"authorize": None, "purpose": "用途", "unit_price": 0.2}, + {"authorize": "用户同意", "purpose": None, "unit_price": 0.2}, + {"authorize": "用户同意", "purpose": "用途", "unit_price": None}, + {"authorize": " ", "purpose": "用途", "unit_price": 0.2}, + ): + try: + check_authorization(kwargs["authorize"], kwargs["purpose"], kwargs["unit_price"], 1, DEFAULT_MODEL, "1024*1024", out) + except SystemExit: + pass + else: + raise AssertionError(f"应拒绝未授权参数:{kwargs}") + assert not (out / "authorization.log").exists(), "未授权时不应写审计日志" + # 三项齐全时放行,花费按 张数 × 单价 计算并落盘 + rec = check_authorization("用户 2026-10-29 同意", "产品A 天猫主图", 0.25, 4, DEFAULT_MODEL, "1024*1536", out) + assert rec["estimated_cost"] == 1.0, rec + logged = (out / "authorization.log").read_text(encoding="utf-8").splitlines() + assert len(logged) == 1 + assert json.loads(logged[0])["purpose"] == "产品A 天猫主图" print("selftest ok") @@ -155,6 +230,9 @@ def build_parser(): ap.add_argument("--watermark", action="store_true", help="添加水印,默认不加") ap.add_argument("--model", default=DEFAULT_MODEL, help=f"模型名,默认 {DEFAULT_MODEL}") ap.add_argument("--key-file", help="API key 文件路径;不传则读 DASHSCOPE_API_KEY") + ap.add_argument("--authorize", help="用户授权凭证(用户原话或带日期说明),必填;见 tools/IMAGE-GEN-AUTHORIZATION.md") + ap.add_argument("--purpose", help="这批图的用途与落点(产品/平台/页面位置),必填") + ap.add_argument("--unit-price", type=float, help="每张单价(元,取官方页面当时数值),必填") ap.add_argument("-o", "--out", default="out", help="输出目录,默认 out") ap.add_argument("--timeout", type=int, default=300, help="请求超时秒数,默认 300") ap.add_argument("--selftest", action="store_true", help="离线自检,不调用 API") @@ -178,6 +256,17 @@ def main(): if not 1 <= args.num <= 6: sys.exit("--num 取值范围 1 到 6") + # 授权闸门在读取密钥和发请求之前,未授权不会产生任何 API 调用 + check_authorization( + args.authorize, + args.purpose, + args.unit_price, + args.num, + args.model, + normalize_size(args.size), + Path(args.out), + ) + key = read_key(args.key_file) payload = build_payload( prompt,