Files
cmshoppe/docs/tasks/T-679.md
T

103 lines
9.0 KiB
Markdown
Raw Normal View History

---
id: T-679
title: 商品套图接入自定义网关(主图 + 参考图)
phase: 7
deps: [T-677, T-678]
status: TODO
created: 2026-07-20
---
# T-679 商品套图接入自定义网关(主图 + 参考图)
## 问题 / 背景
T-677 在自定义网关(`ai.backend=direct`)下停用了⑥商品套图的新建、重试与「AI 帮写」,只保留已提交默认网关任务的取回入口。现在需要让⑥的生成能力也能走自定义网关,使 cmhub 上游不稳或用户自备算力时,套图不再是唯一断链的模块。
相关代码事实:
- `app/image_studio_generation.py` 的来源耦合只有两处:`_runtime()`(:35)写死 `ai._cmhub_runtime(config, "image", ...)`,以及 `_submit_or_resume_job()`(:401)的 cmhub 提交与 `_poll_job()`(:325 调用)轮询。建 job、并发槽、停止、输出路径、存资产、重试删除历史、进度事件**已经与来源无关**。
- **direct 是同步路径**:`_call_with_retry()` 返回后 `_extract_image_bytes()` 取字节、`_save_jpeg()` 落盘(`app/ai.py:387-390`)。因此自定义网关不需要异步状态机,而是**跳过** submit/poll 两步,不是补齐它们。
- 已提交任务的回收守卫(`app/image_studio_generation.py:200`)判断条件是 `getattr(job, "task_id", None) and not _is_default_gateway_job(job)`,**只拦有 `task_id` 的 job**。direct job 天然没有 `task_id`,自动不被拦截,该守卫无需修改即可继续保护已扣点的 cmhub 任务。
- `create_generation_jobs()`(:115)目前把 `generation_source` 与 `provider` 硬编码为 `"cmhub"`,接入多来源后必须写入真实来源,否则回收守卫的判断依据失效。
**能力边界必须如实表达**:cmhub 的多图契约中,首图为主体且由服务端强制追加"保留主体"规则,客户端无法覆盖(T-658 已确认)。OpenAI 图片编辑接口的多图输入没有等价契约,主体保留只能写入提示词由模型自行遵守。对白底图这类"必须仍是同一件商品"的场景,两者不等价,不得因为支持多图就宣称效果相同。
## 方案
### 1. 来源分派(两处接缝)
- `_runtime()`(:35)改为按来源返回带稳定来源标记的运行时对象:默认网关沿用 `ai._cmhub_runtime()`;自定义网关解析本轮实际使用的图像模型,复用 T-677 的 `ai.freeze_runtime_config()` 快照与 `ai._role_model("image", ...)`。运行时对象仅存在于内存,密钥不得写入 SQLite、运行日志、异常正文或 UI。
- `_submit_or_resume_job()`(:401)按来源分支:默认网关保持现有异步提交;自定义网关走 T-678 提供的多图同步请求,直接返回图片字节。
- `_run_one_job()`(:284)在自定义网关下跳过 `_poll_job()`(:325),直接进入保存分支。`request_result` 的契约需要能同时表达"远程 URL 待下载"(cmhub)与"字节已在手"(direct),两种形态都要能走到 :329 的保存与 :344 的 `add_asset()`。
- `create_generation_jobs()`(:115)写入真实 `generation_source` / `provider`,不再硬编码 `"cmhub"`。
- `_ensure_new_submission_allowed()`(:47)放开自定义网关;三层守卫(`create_generation_jobs()`、`ProductSuiteGenerateWorker`、`_submit_or_resume_job()`)继续使用同一守卫函数,改为按来源判断可用性而不是逐层各写一遍。
### 2. 主图与参考图
- 未勾选「每张上传图分别作为主图生成」时,自定义网关按 T-678 的多图请求提交:第一张为主体图,其余为参考图,数量上限按所选模型与服务商实际限制处理并在超限时给出中文提示。
- 勾选逐图主图时维持现有每图单独提交的语义,不引入跨 SKU 混图。
- 主体保留只能通过提示词表达:把"第一张为商品主体、必须保持商品本体不变,其余仅供风格与场景参考"拼进发送给自定义网关的提示词。**不得**声称与默认网关等价。
- ⑥在自定义网关下显示中文说明:参考图效果取决于所选模型,主体一致性弱于默认网关。
### 3. 停止、超时与并发
- **停止会变迟钝**:direct 是一次同步 HTTP 请求,请求发出后没有中断点,`should_stop` 只能在两张图之间生效。UI 必须如实说明"正在停止,需等待当前图片返回",不得让用户误判为卡死;不得用 `processEvents()` 或强杀线程伪装成即时取消。
- 自定义网关不使用 `CMHUB_IMAGE_SUBMIT/POLL/READ_TIMEOUT`(:446、:513、:485)这套为异步设计的常量,改用模型自身配置的超时秒数。
- 并发沿用现有 `MAX_CMHUB_IMAGE_STUDIO_WORKERS = 5` 与全局信号量(:15-16),本任务不调整取值,避免把针对 cmhub 的保护与用户自备服务的限流混为一谈。
- 套图传入的是 `aspect_ratio`(如 `1:1`),OpenAI 图片编辑接口吃的是尺寸文本,需要一层明确换算;换算规则与不支持的比例回退口径要有测试。
### 4. 费用口径与不变部分
- ⑥在自定义网关下不显示"预计消耗 X 点",改用 T-677 在②已定的口径「自定义网关(不计点数,费用由服务商收取)」,不得显示 `0 点`或沿用旧余额。默认网关下点数预估与余额展示不变。
- 「AI 帮写」在自定义网关下继续不可用:`appconfig.CATEGORIES` 无 vision 类别,自定义模型没有图片理解配置位。入口保持置灰并说明仅默认网关支持。
- 已提交默认网关任务的「继续查询已提交图片」入口与回收守卫(:200)保持不变;切到自定义网关不得影响已扣点任务的轮询、下载与保存。
## 验收要点
- [ ] 自定义网关下⑥可新建并完成套图生成;job 的 `generation_source` / `provider` 记录真实来源,`task_id` 为空。
- [ ] 未勾选逐图主图时按主图 + 参考图提交,首图为主体;勾选时维持每图单独提交。
- [ ] 切换来源后,此前已提交的默认网关任务仍可继续查询、下载并保存,不被拒绝、不新建任务、不二次扣点;回收守卫逻辑未被削弱。
- [ ] 运行中保存⑤来源或修改模型,不影响已启动任务的来源快照;密钥只存在于 worker 内存,未出现在 DB、日志、异常正文或 UI。
- [ ] 自定义网关下停止在当前图片返回后生效,UI 有"正在停止"的中文说明,不出现伪即时取消或卡死观感。
- [ ] 自定义网关使用模型自身超时;`aspect_ratio` 到尺寸的换算与不支持比例的回退有明确行为。
- [ ] ⑥在自定义网关下显示不计点数口径与主体一致性弱化说明;默认网关下点数预估不回归。
- [ ] 「AI 帮写」在自定义网关下仍不可用并给出中文原因。
- [ ] 默认网关的套图生成、重试、删除撤销、历史、导出与③更新蝦皮全部不回归。
## 测试与文档
- `tests/test_image_studio_generation.py`:来源分派、direct 同步路径跳过轮询、多图请求顺序与主体图位置、`generation_source`/`provider` 写入、已提交 cmhub 任务在来源切换后仍可恢复、无 `task_id` 与来源不匹配的 job 被拒绝、比例换算、停止在图片边界生效。
- `tests/test_gui.py`:⑥在两种来源下的入口可用性、点数与主体一致性文案、停止提示、AI 帮写仍不可用、运行中保存设置不改变本轮来源。
- `tests/test_ai.py`:套图复用的多图请求构造与超时取值。
- 更新 `docs/routes.md`、`docs/04-architecture.md`、`docs/api.md`;如⑥文案或流程变化,同步 `docs/ui/` 对应效果图并在 `docs/ui/README.md` 登记。
## 验证
```bash
py -3.10 -m unittest tests.test_image_studio_generation tests.test_gui tests.test_ai
py -3.10 -m unittest discover -s tests
py -3.10 -m ruff check app tests main.py
py -3.10 -m compileall app main.py
git diff --check
```
## 边界(不改什么)
- 不新增 vision 类别或图片理解的自定义网关路径;「AI 帮写」仍为默认网关独占。
- 不改 cmhub 的请求协议、异步 submit/poll 状态机、幂等键、预扣退点账本与已提交任务回收守卫。
- 不调整套图并发上限与全局信号量取值。
- 不新增 provider 适配、接口自动探测或任意上游 URL 放行(接口规范由 T-678 锁定)。
- 不改 SQLite schema、Chrome/CDP、Excel 回写、蝦皮更新流程。
- 不解除 T-596 的 BYOK 门禁;本任务仍属客户端直连的过渡形态。
## 关联
- T-678:自定义网关锁定 OpenAI 规范并支持多图输入(本任务的前置能力)。
- T-677:⑤生成网关来源切换(本任务**有意放宽**其"不给商品套图新增自定义网关路径"的边界;放宽理由为套图是当前唯一在来源切换后完全断链的模块)。
- T-658:商品套图多图参考提交规则(cmhub 首图主体、服务端强制保留的契约来源)。
- T-596:BYOK 总设计与启动门禁(`status: BLOCKED`,正式形态要求经 cmhub 代理并收口 direct)。
## 执行记录
(做完在这里写:改了什么文件、跑了什么验证命令及结果、遇到的阻塞、关键决策。)