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

96 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: T-678
title: 自定义网关图片模型锁定 OpenAI 图片编辑接口并支持多图输入
phase: 7
deps: [T-677]
status: DONE
created: 2026-07-20
---
# T-678 自定义网关图片模型锁定 OpenAI 图片编辑接口并支持多图输入
## 问题 / 背景
T-677 恢复了⑤的自定义网关(`ai.backend=direct`)。图片模型的 `chat` / `auto` 分支依赖非标准聚合网关把生成图塞进对话响应,不符合 OpenAI 规范,需要收敛。
**但不能全局收敛 `appconfig.API_TYPES`。** 代码事实是:`api_type` 在 `app/ai.py` 全文只有 :348、:350 两处使用,都在图片路径内,**文字生成路径从不读取该字段**;然而 `appconfig.py:881` 的 `if normalized["api_type"] not in API_TYPES` 对**所有模型统一校验,不分 category**。因此收窄全局取值集合的后果不是文字请求走错接口,而是存量文字模型(`api_type: "chat"`)在载入与保存时直接抛 `ConfigError`,自定义网关生文全部不可用。收敛必须按模型类别做,不能动全局集合。
当前 direct 图片编辑也只能提交单张图:`_image_edit_body()` 的 `files` 是单键字典,`_multipart_body()` 不能表达重复字段。商品套图需要按顺序提交“主体图 + 参考图”,其前置能力应在本任务完成。
本任务锁定的外部契约是 OpenAI Image API 的图片编辑接口:`POST /v1/images/edits`、multipart 重复 `image[]` 文件字段、响应从 `data[0].b64_json` 或 `data[0].url` 读取。第一张为主体图是本项目的业务约定;OpenAI 文档只明确多图及遮罩作用于首图,不能据此声称模型一定保持主体一致性。
## 方案
### 1. 按类别收敛模型接口
- 保留全局 `API_TYPES = {"chat", "images_edits", "auto"}`,使 `category=text` 的既有模型配置仍能通过 `appconfig.py:881` 的校验正常载入与保存。
- 新增“图片编辑能力”校验:仅 `category=image` 且 `api_type=images_edits` 的模型可用于②生成封面与后续⑥商品套图;文字模型保持既有 `chat` / `auto` 行为。
- 图片模型的 `chat` / `auto` 存量配置原样保存、可查看,但在⑤显示“当前图片模型不支持 OpenAI 图片编辑接口”的中文状态,并在②、⑥预检中阻止提交。不得静默迁移、禁用或删除,避免触发“至少一个 image 模型启用”的既有校验冲突。
- ⑤根据模型类别展示接口类型:文字模型保留现有可选项;图片模型只允许选择并显示“OpenAI 图片编辑接口”。不再以全局文案宣称 `chat/completions` 永远不能生成图片,而是说明其不符合本项目锁定的图片编辑响应契约。
### 2. 图片编辑请求与响应契约
- 删除 direct 图片生成的 `_image_chat_payload()` 分支;`category=image` 的 direct 请求一律走 `/v1/images/edits` multipart,文字请求不在本任务范围内。
- `_image_edit_body()` 接受有序本地路径序列;multipart 用重复的 `image[]` 字段提交,首项为主体图,其余为参考图。保留单图入口,②仍只提交一张图。
- 请求体固定一张输出(`n=1`);套图数量仍由“一个 job 一次请求”表达,不能让一次响应内的多图破坏单 job 历史、重试与计费语义。
- `_find_image_ref()` 收敛为只读取标准 `data` 数组中的 `b64_json` / `url`。保留合法 data URL、base64 和 HTTP(S) URL 下载分支;非标准响应必须给出中文错误,不能递归猜测 `choices`、`message` 等字段。
- 继续使用现有 URL 安全校验;不得因兼容自定义网关放开 `file:`、本地路径或其他非 HTTP(S) 返回地址。
### 3. 配置、预检与回归
- 使用图片模型前,预检同时检查启用状态、`images_edits` 接口类型、URL、模型名、密钥和超时;错误只给中文可操作说明,不暴露密钥、完整上游 URL 或原始响应体。
- 图片模型的⑤「测试」入口改为「检查图片配置」:只执行本地字段、接口类型、URL 格式和超时校验,**不**向 `/images/edits` 发送没有 `image[]` 的 JSON `ping` 请求,也不伪造成功。原因是图片编辑端点要求 multipart 图片,真实请求可能产生服务商费用。
- 用户需要验证图片模型实际可用性时,必须通过②或⑥的正常生成确认流程完成;确认框继续如实提示「自定义网关不计点数,实际费用以服务商为准」。不得在“检查图片配置”中隐式发起可能计费的生成请求。
- ②已有的重试、并发、日志、“自定义网关不计点数”口径不变;本任务不改变默认网关及其 cmhub 生图路径。
- 在执行记录中记录 OpenAI 官方文档核实日期、`image[]` 字段、标准响应字段和支持的尺寸范围;外部兼容服务若不满足该契约,第一版明确不支持,不做自动探测或私有适配。
## 验收要点
- [ ] 自定义网关文字模型仍可走原有 `chat` / `auto` 生文路径;全局 `API_TYPES` 未被收窄。
- [ ] `category=image` 仅在 `api_type=images_edits` 时可提交图片生成;存量 `chat` / `auto` 图片模型保留且不被静默改写。
- [ ] multipart 使用有序重复 `image[]` 字段;单图与多图均有字段、顺序与首图主体的单元测试。
- [ ] 图片响应仅接受 `data[].b64_json` / `data[].url`;非标准响应有可读中文错误,非法 URL 不会下载。
- [ ] ⑤图片模型的「检查图片配置」不发送网络生图请求,旧 `test_ai_model()` 的 JSON ping 不会被用于 `images_edits` 模型;文字模型既有测试行为不回归。
- [ ] ②单图生成的结果、重试、并发、日志和不计点数口径不回归。
- [ ] ⑤的图片模型限制和②预检均为中文,不泄露密钥、完整上游 URL 或原始响应体。
- [ ] 默认网关的生文、生图、图片理解、异步 submit/poll、幂等键、计费与③更新蝦皮不受影响。
## 测试与文档
- `tests/test_ai.py`:文字 `chat` / `auto` 回归、图片 `images_edits` 单图与多图 `image[]` 请求、`n=1`、标准响应和非法/非标准响应错误路径。
- `tests/test_appconfig.py`:按类别的可用性校验、存量 `chat` / `auto` 图片模型不被改写、文字模型保存与加载不回归。
- `tests/test_gui.py`:⑤按类别显示接口类型、图片模型检查不发网络请求、②预检拦截不可用图片模型且文案中文。
- 更新 `docs/cmhub-integration-design.md`、`docs/routes.md`、`docs/api.md`,标明图片编辑的固定契约和 T-679 的依赖关系。
## 验证
```bash
py -3.10 -m unittest tests.test_ai tests.test_appconfig tests.test_gui
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
```
## 边界(不改什么)
- 不改商品套图;其接入拆分为 T-679a、T-679b、T-679c,由 T-679 集成验收。
- 不新增 provider 私有适配、接口自动探测、任意 URL 放行或图片理解的自定义网关路径。
- 不改 SQLite schema、Chrome/CDP、Excel 回写、蝦皮更新流程。
- 不改 cmhub 的请求协议、重试与超时策略、异步 submit/poll 状态机、幂等键、预扣退点账本。
- 不解除 T-596 的 BYOK 门禁;本任务仍属客户端直连的过渡形态。
## 关联
- T-677:⑤生成网关来源切换。
- T-679a:套图多来源任务状态机,依赖本任务的多图请求能力。
- T-596:BYOK 总设计与启动门禁。
## 执行记录
- 2026-07-20 完成:保留全局 `API_TYPES`,新增按图片类别的 OpenAI 图片编辑接口校验;存量 `chat` / `auto` 图片模型仍可载入、查看和保存,但设置页标示为不支持,②预检及 direct 生图会在发请求前用中文拦截。
- direct 封面请求统一使用 `/v1/images/edits` multipart;`_image_edit_body()` 支持有序多图,重复 `image[]` 字段的第一张为主体图,并固定 `n=1`。响应解析收敛到 `data[].b64_json` / `data[].url`;cmhub 保持独立的宽松解析函数,默认网关行为未改变。
- 图片模型按钮改为「检查图片配置」,worker 只调用本地字段、接口类型、URL 和超时检查,不发送空图片请求;文字模型继续走既有连接测试。
- 文档同步:`docs/cmhub-integration-design.md`、`docs/routes.md`、`docs/api.md`。接口契约于 2026-07-20 参考 [OpenAI Images API 文档](https://platform.openai.com/docs/guides/image-generation):图片编辑使用 multipart 图片输入,图片结果使用 base64 或 URL 表达;本项目固定采用 `image[]`、`n=1` 和标准 `data` 结果字段,不适配服务商私有响应。
- 验证通过:`py -3.10 -m unittest tests.test_ai tests.test_appconfig tests.test_gui`(287 passed);`py -3.10 -m unittest discover -s tests`(643 passed);`py -3.10 -m ruff check app tests main.py`;`py -3.10 -m compileall app main.py`;`git diff --check`。本任务未改 CDP,未作线上蝦皮实跑。