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

96 lines
8.9 KiB
Markdown
Raw Normal View History

---
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,未作线上蝦皮实跑。