Files
TrueGrowth/docs/ASYNC_TASK_PROVIDER_ROUTE_LESSONS.md

131 lines
5.5 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.
# 异步任务供应商路由经验
更新日期2026-05-05
## 背景
图片、视频、音频异步任务都有“提交任务 -> 保存远端任务 ID -> 后续查询状态”的链路。多供应商接入后,提交时使用的供应商不一定等于用户刷新页面后的默认供应商。
这次 HappyHorse 用户反馈的错误是:
- 状态查询失败
- 接口返回“该令牌无权访问模型”
- request id: `202605020707307592757188268d9d67zVDq7SC`
## 根因
异步任务只保存 `remoteId` 不够。
如果任务恢复查询时只依赖当前 `task.params.model`、当前默认供应商,或当前全局 API Key就会出现
- 提交任务用的是供应商 A 的 key
- 刷新后默认路由切到了供应商 B
- 查询状态时拿供应商 B 的 key 去查供应商 A 的任务
- 服务端返回模型无权限、任务不存在或鉴权失败
这不是 HappyHorse 单模型问题,而是多供应商异步任务的通用一致性问题。
## 修复规则
### 1. 异步任务必须绑定创建时路由
任务进入异步轮询阶段时,必须保存轻量 `invocationRoute` 快照:
- `operation`: image / video / audio / text
- `providerProfileId`
- `providerType`
- `modelId`
- `modelRef`
- `binding` 的协议、请求/响应 schema、提交路径、轮询路径模板
不要保存 API Key。恢复查询时应通过 `providerProfileId` 找当前配置里的最新 key。
### 2. 恢复查询必须优先使用任务路由
恢复查询时优先级应是:
1. `task.invocationRoute`
2. 兼容旧任务的 `task.params.modelRef`
3. 最后才是 `task.params.model` 或默认路由
只要任务上存在 `providerProfileId`,就进入严格模式,不允许静默 fallback 到默认供应商。
### 3. 严格模式要显式失败
以下情况应直接给出配置错误,而不是换默认 key 继续查:
- 原供应商配置已删除
- 原供应商被停用
- 原供应商 API Key 缺失
- 原供应商 Base URL 缺失
- 原模型 binding 已不可用
这样用户看到的是“原供应商配置不可用”,而不是误导性的“令牌无权访问模型”。
### 4. 查询路径也属于供应商 binding
视频状态查询不能硬编码一种路径。不同供应商可能需要:
- `/videos/{taskId}`
- `/kling/v1/videos/{action}/{taskId}`
- 其他自定义 poll path
轮询路径应来自 binding 的 `pollPathTemplate`,并支持用任务参数填充 `{taskId}``{action}` 等占位符。
### 5. 同步和恢复必须 round-trip
`invocationRoute` 必须覆盖这些路径:
- IndexedDB 写入与读取
- 内存 task queue 同步
- 外部任务追踪
- fallback executor
- 主线程 resume
- GitHub sync compact / restore
- SW channel 类型
如果某条链路漏掉字段,刷新或远程同步后仍会回到默认供应商查询。
### 6. 异步图片恢复判定不能只依赖模型白名单
刷新恢复时,只要异步任务已经拿到远端任务 ID就应该优先恢复轮询而不是直接标记为“页面刷新中断”。
图片任务尤其容易踩坑:动态供应商模型不一定在内置 `ASYNC_IMAGE_MODEL_IDS` 白名单里,但任务创建时已经持久化了 `invocationRoute.binding`。因此恢复判定应满足:
- 任务类型是 image
- 已有 `remoteId`
- 模型命中内置异步图片白名单,或 `invocationRoute.binding` 表明它是 `openai.async.media` / `openai.async.image.form`
这样页面刷新后UI 会继续显示生成中并由执行器恢复轮询;只有真正的业务终态失败才进入失败界面。
不要为了隐藏错误去改 UI。失败界面只是任务状态的投影根因应在任务恢复层修正。
## 代码落点
- `task-invocation-route.ts`:创建、解析、严格校验任务路由快照。
- `core.types.ts`:任务模型增加 `invocationRoute`
- `task-queue-service.ts`创建任务、外部任务、storage sync 写入路由快照。
- `generation-api-service.ts` / `useTaskExecutor.ts`image/audio/video 恢复查询优先用任务路由,并在严格模式下校验原供应商。
- `task-utils.ts`:集中判断异步图片任务是否可恢复,避免 storage 和 executor 使用不同条件。
- `fallback-executor.ts`fallback 视频恢复查询使用原供应商。
- `video-binding-utils.ts`:统一视频状态查询路径模板。
- `github-sync`:远程同步保留 `invocationRoute`
## 验证清单
- 异步任务提交后storage 中应有 `remoteId``invocationRoute`
- 刷新页面后恢复 image/audio/video 任务时,请求应命中原 `providerProfileId` 对应的 key。
- 修改默认供应商后,旧异步任务仍应使用原供应商查询。
- 删除或停用原供应商后,任务应失败为明确配置错误,不应切到默认 key。
- 自定义 `pollPathTemplate` 的视频供应商应按模板查询状态。
- 动态异步图片模型即使不在内置白名单,只要保存了 async image binding 和 `remoteId`,刷新后也应继续轮询。
## 经验规则
- 异步任务的远端 ID 只在原供应商上下文中有意义。
- 多供应商系统里,“模型名”不是稳定路由键;稳定键应包含供应商 ID 和模型 ID。
- 不要把 API Key 持久化进任务;持久化供应商 ID查询时读取当前 key。
- 新增异步供应商时,必须同时补提交路径、轮询路径、恢复路径和同步路径测试。
- 任何状态恢复逻辑都要警惕静默 fallback它会把配置问题伪装成模型权限问题。
- UI 失败态不应作为异步中断兜底;先确认任务是否已有可轮询的远端 ID。