docs(tasks): refine T-564 with two-commit hint and three-state resume check
补实现提示:分两步 commit(先 DB 层加列+同步 Task dataclass+helper 跑绿, 再 ai.py submit/poll 对接),并写清续查三态判断(image_task_id/image_task_key 的存在组合区分已 submit / submit 前崩溃 / 全新 submit)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5f5bf73949
commit
487408ba0f
+31
-7
@@ -15,8 +15,13 @@ cmhub 侧已实现并测试通过异步任务化接口(新增 `/generate/image
|
||||
|
||||
设计与逐点落地评估见 Obsidian「生图接口异步任务化-提交轮询方案」§「对接契约 v3」+「cmshopee 侧落地评估」。**对接字段/错误码以该文 v3 节与 cmhub `docs/api.md` 为准。**
|
||||
|
||||
> 编号说明:当前仓库任务文件为 `T-564`。如果外部沟通里写成 `T-654`,按本文件 `T-564` 执行,避免任务编号分叉。
|
||||
|
||||
## 方案(改哪个文件、改成什么)
|
||||
|
||||
> 实现提示:本任务横跨 DB 层与 ai.py 对接,建议**分两步 commit**——先落 `app/db.py`(加列 + 同步 `Task` dataclass + 迁移 + 3 个 helper + `test_db` 跑绿),确认 `list_tasks`/`get_task` 因 `cls(**dict(row))`(`db.py:323`)不因新列崩溃后,再落 `app/ai.py` 的 submit/poll/续查对接。降低单次改动风险,也便于回滚。
|
||||
> 续查前置的三态判断(实现时写清、单测覆盖):`image_task_id` 有值 = 已 submit,直接 GET 续查;`image_task_key` 有值但 `image_task_id` 空 = submit 前崩溃,带同 key 重发;两者皆空 = 全新 submit。
|
||||
|
||||
### 接口(cmhub 已实现,本任务对接)
|
||||
- submit:`POST /api/v1/generate/image/tasks` → `202 {task_id, status:queued, call_id, points_cost, points_balance, expires_at}`(**提交即预扣**)。请求体与旧生图一致;头带 `Idempotency-Key`、`X-Client-Version`。
|
||||
- poll:`GET /api/v1/generate/image/tasks/{task_id}` → 除 404 外恒 `200`,按 `status` 分支:`queued/running` / `succeeded`(带 `result.image_url`) / `failed`|`expired`(带 `error.code`,**均已退点**)。
|
||||
@@ -32,15 +37,27 @@ cmhub 侧已实现并测试通过异步任务化接口(新增 `/generate/image
|
||||
5. 超时分段:不再用 `_cmhub_read_timeout` 恒 900(`ai.py:1074`),submit/poll 各自超时。
|
||||
6. 余额/计费事件回调(`_emit_cmhub_metadata`)在 submit 202 触发一次(`points_cost/points_balance/call_id`)。
|
||||
7. 重试语义分层:失败重试是「换新 Idempotency-Key 重新 submit」的上层重跑,不走 `_cmhub_call_with_retry`(`ai.py:1149`)复用同 payload 的内层重试;内层对 submit 关闭或强制带同一幂等键。
|
||||
8. **`gen_cover(...)` 单独调用兼容**:`generate_batch()` 内有 `task.id/db_path`,可完整使用异步 submit/poll/续查/落库;单独直接调用 `gen_cover()` 时没有任务上下文,不得强行写库。第一版可走“无持久化异步”(submit+poll 但不支持重启续查),或保留旧同步兼容路径;必须在代码和文档里明确,不能让公开函数签名变成必须传 DB。
|
||||
|
||||
### 结构改动
|
||||
- **① request 段写库落 `task_id`**:`_request_cmhub_cover_image` 现无状态、不碰 DB。需把 `task.id` + 持久化回调(或 `db_path`)传进去,在 submit 成功点写 `image_task_id`。调用点在 `run_cmhub_cover_tasks`(`ai.py:687`)。
|
||||
- **② per-attempt 幂等键**:键用 `f"{task.id}:{generate_attempts}"`(复用 `app/db.py:239` 的 `generate_attempts`),保证「手动重新生成封面」不撞回旧任务,而「同一次 submit 网络抖动重发」仍幂等返回同一 `task_id`。
|
||||
- **② per-attempt 幂等键不要直接依赖 `generate_attempts`**:当前 `generate_attempts` 只在 `set_generated()` / `mark_failed()` 时递增,用户重置封面后重新生成时可能仍复用旧值。新增本地 `image_task_key`(例如 `task.id + uuid/nonce`)作为 `Idempotency-Key` 更稳:submit 前若没有 key 先落库生成;同一次网络抖动重发复用同 key;用户重置封面、poll 返回 `failed/expired` 后重试、或需要重新 submit 时清空并生成新 key。
|
||||
- **③ 轮询占 request-pool 线程**:submit+poll 同步塞进 `request_executor` future 即可(本地线程、廉价,非稀缺资源);`cmhub_image_concurrency_plan`(`ai.py:241`)现约束「在途轮询数」,第一版保持池形、可放宽上限,不重构编排。
|
||||
|
||||
### `app/db.py`
|
||||
- 加列 `tasks.image_task_id TEXT`(沿用现有 `ALTER TABLE ADD COLUMN` ad-hoc 迁移模式,参考 `db.py:363`)。
|
||||
- 提供读写 `image_task_id` 的辅助(或并入现有 task 更新路径)。
|
||||
- 加列 `tasks.image_task_id TEXT`、`tasks.image_task_key TEXT`(沿用现有 `ALTER TABLE ADD COLUMN` ad-hoc 迁移模式,参考 `db.py:363`)。
|
||||
- 同步 `Task` dataclass、历史 DB 迁移、`SELECT t.*` 映射与相关测试,避免 SQLite 多列后 dataclass 构造失败。
|
||||
- 提供读写 helper(命名可按实现调整):
|
||||
- `ensure_image_task_key(task_id)`:无 key 时生成并写入,返回稳定 key。
|
||||
- `set_image_task_submitted(task_id, image_task_id, image_task_key)`:submit 202 后立即落库。
|
||||
- `clear_image_task(task_id)`:清空 `image_task_id/image_task_key`,用于失败终态或用户重置封面后重新生成。
|
||||
- 生命周期规则:
|
||||
- submit 前先落 `image_task_key`;submit 成功后立即落 `image_task_id`。
|
||||
- 本地总预算超时、用户点击停止、程序退出:保留 `image_task_id/image_task_key`,下次可续查。
|
||||
- poll 返回 `succeeded`:下载保存并 `set_generated()`;可保留 `image_task_id/image_task_key` 作审计,不影响已生成任务。
|
||||
- poll 返回 `failed` / `expired`(已退点):清空 `image_task_id/image_task_key`,再按重试次数决定是否新 submit,避免后续一直续查旧失败任务。
|
||||
- `reset_generated(..., reset_cover=True)` 或重置全部生成结果时必须清空 `image_task_id/image_task_key`;只重置标题且保留封面时不必清空。
|
||||
- submit 阶段 `content_blocked`、`insufficient_points`、`bad_request` 没有有效任务时不保留 key;`idempotency_conflict` 说明本地 key/payload 复用错误,应清空 key 后提示或失败。
|
||||
|
||||
### 文档
|
||||
- `docs/cmhub-integration-design.md` 出 v3.6:不推翻 v3.5 全量,标注「旧同步接口沿用 v3.5 口径 + 新增任务化接口口径」,记弃用计划。
|
||||
@@ -51,17 +68,24 @@ cmhub 侧已实现并测试通过异步任务化接口(新增 `/generate/image
|
||||
- submit 返回 202 → `image_task_id` **已落库**、进入轮询、不阻塞主线程;单测断言写库发生在轮询前。
|
||||
- 轮询 `running`→`succeeded` → 走下载保存、`gen_cover` 仍返回已存 JPEG 路径;`failed`/`expired`(已退点) → 抛结构化错误、按可重试、**不重复扣点**。
|
||||
- **重启续查**:已有 `image_task_id` 的 task 直接 GET,命中 `succeeded` 取图,**无二次 submit、无二次扣点**(断言未调 POST /tasks)。
|
||||
- **幂等键 per-attempt 隔离**:同 task 同 `generate_attempts` 重发 → 同 `task_id`;`generate_attempts` 递增后 → 新 `task_id`。
|
||||
- **幂等键 per-attempt 隔离**:同 task 同 `image_task_key` 重发 → 同 `task_id`;用户重置封面、旧任务 `failed/expired` 或需要重新 submit 时清空旧 key 并生成新 `image_task_key` → 新 `task_id`。
|
||||
- 撞本地总预算未出结果 → 超时错误但 `image_task_id` 已持久化、可续查。
|
||||
- 轮询期 `should_cancel()` → 停止轮询、不再下载(服务端继续跑完,按结果结算)。
|
||||
- 轮询期 `should_cancel()` → 停止轮询、不再下载(服务端继续跑完,按结果结算),GUI 日志明确提示“已停止等待生图结果;服务端任务可能仍在完成,下次可继续查询”。
|
||||
- 错误码 `upstream_timeout`/`task_timeout`/`content_blocked`/`insufficient_points`/`idempotency_conflict` 各有对应处理与中文提示。
|
||||
- `gen_cover()` 单独直接调用不破坏旧调用方;②批量生成才要求完整持久化和重启续查。
|
||||
- 未配置 cmhub / Base URL 双拼等既有错误口径不回归。
|
||||
- 验证命令:`py -3.10 -m pytest tests/test_ai.py -q`(新增异步路用例,mock `_cmhub_session()` 的 request/get 按 status 分支返回)。
|
||||
- 验证命令:新增异步路用例用 `unittest`,不要引入 pytest。至少运行:
|
||||
- `py -3.10 -m unittest tests.test_ai tests.test_db`
|
||||
- `python -m ruff check app tests main.py`
|
||||
- `py -3.10 -m compileall app main.py`
|
||||
- `py -3.10 -m unittest discover -s tests`
|
||||
- `git diff --check`
|
||||
|
||||
## 边界(不改什么)
|
||||
|
||||
- 不改旧同步 `POST /api/v1/generate/image` 的对接(保留为兼容/回滚路径)。
|
||||
- 不改 `gen_cover(...)` 对外返回值(仍返回已存 JPEG 路径)、`generate_batch` 编排骨架、`_download_and_save_cmhub_cover` 下载/保存逻辑、`data/images/<batch_id>/<slug>/` 路径、封面开关。
|
||||
- 不改 `gen_cover(...)` 对外返回值(仍返回已存 JPEG 路径);单独调用没有 DB 上下文时不强制支持重启续查。
|
||||
- 不改 `generate_batch` 编排骨架、`_download_and_save_cmhub_cover` 下载/保存逻辑、`data/images/<batch_id>/<slug>/` 路径、封面开关。
|
||||
- 不改 CDP 选择器 / Shopee 交互 / Excel / 生文链路。
|
||||
- 不引入 cancel 接口(cmhub 本期未实现)。
|
||||
- 不做 DB 版本化迁移重构(沿用 ad-hoc ALTER;版本化仍在 backlog)。
|
||||
|
||||
Reference in New Issue
Block a user