docs: 新增火山方舟 Seedance 接口事实与错误码映射取证页
CI / docs-build (push) Successful in 39s

记录从官方文档实际读到的事实,供实现与排查引用,并把开工时「待服务商文档确认
的五项」逐条落定。实现在 patbond-ai(58 测试)。

要点(详见正文):
- **不支持幂等键**:ADR-023 §4 设想的「透传为厂商幂等头」对本供应商不成立,
  风险窗口只能由 max_attempts 兜住。但厂商支持 callback_url,孤儿任务会通过
  回调把 ID 送回——回调在此供应商上是**孤儿回收通道**而非仅延迟优化,
  §4「M4 只做轮询」值得据此复评。
- **产物 URL 仅 24 小时有效**(2.5 另有 100 次下载上限):厂商 URL 不能当存储
  用,存成 external_url 会变死链。**这对挂起中的存储决策是硬约束**——无论选
  本地还是云,都必须在 24 小时内把文件复制到我们自己的存储。
- **两处文档自身的矛盾**已登记并按宽实现:状态枚举在查询文档与回调说明里
  不一致(后者多一个 expired);QuotaExceeded 一码两义(排队超限 vs 额度耗尽,
  只能按 message 判别)。
- 两个免费收益:last_frame_url 可直接作视频封面(R13 把缩略图列为视频成本项,
  实际免费);查询返回 ratio/resolution/fps/duration,Worker 写产物时维度与
  时长在手、无需解码,widthPx/heightPx 债在 AI 产物上零成本可修。
- 取消语义:厂商只支持取消 queued 状态,已 running 的任务取消后仍计费。
- 用户提供的两份文档**文件名与内容装反了**(火山错误码.md 里是查询 API),
  已按实际内容归档并注明,避免他人按文件名读错。

另:V7 目录种子仍待确认账号实际开通的 model ID——文档里的示例值不能直接当种子,
否则 provider_model_name 会指向无权访问的模型(InvalidEndpointOrModel.NotFound)。

验证:secret-scan exit 0;mkdocs build --strict exit 0(已挂导航)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-20 11:52:01 +08:00
parent 296d454d30
commit 0b0317415b
2 changed files with 203 additions and 0 deletions
@@ -0,0 +1,201 @@
# 火山方舟 Seedance 视频生成:接口事实与映射
> 取证日期:2026-09-20 来源:用户提供的三份官方文档(`doc_temp/火山视频请求.md`、
> `doc_temp/火山错误码.md`、`doc_temp/火山查询.md`)
> 实现位置:`patbond-ai` 的 `app/providers/seedance.py` 与 `app/providers/errors.py`
本页记录**从官方文档实际读到的事实**,供实现与排查引用。凡本页与代码不一致,
以代码为准并回改本页;凡本页与厂商文档不一致,以厂商文档为准并回改两处。
> **文档文件名与内容对不上**:用户提供的 `火山错误码.md` 里装的是**查询 API**
> 文档,`火山查询.md` 里装的是**错误码表**。按文件名取用会读错,已按实际内容归档。
## 1. 三个端点
| 用途 | 方法与路径 |
| --- | --- |
| 提交任务 | `POST https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks` |
| 查询任务 | `GET https://ark.cn-beijing.volces.com/api/v3/contents/generations/tasks/{id}` |
| 鉴权 | `Authorization: Bearer $ARK_API_KEY` |
**提交是异步接口**:响应只有 `{"id": ...}`,状态靠查询接口拿。这与 ADR-023 §4
「Java↔Python 边界一律异步」天然吻合,无需在 `patbond-ai` 内部做后台任务承载。
## 2. 开工前问的五项,逐条答案
### ① 是否支持幂等键:**不支持**
请求参数全集为 `model`、`content`、`omni_reference_task_type`、`resolution`、
`ratio`、`duration`、`generate_audio`、`watermark`、`output_format`、
`return_last_frame`、`callback_url`、`execution_expires_after`、`priority`、
`safety_identifier`。**没有任何幂等相关字段。**
**后果**:ADR-023 §4 设想的「把我方 `idempotency_key` 透传为厂商幂等头」这条
缓解手段**对本供应商不成立**。「提交超时后不知道有没有扣费」的窗口只能由
`max_attempts` 兜住。**如实登记,不假装已解决。**
**两点减轻**(非消除):
1. 提交是异步的,只入队即返回,**耗时短、超时概率低**——与同步长连接型供应商
的风险量级不同。
2. 厂商支持 `callback_url`,任务状态变化时 POST 推送(请求体与查询接口返回体
一致)。**一个「提交超时但实际已创建」的孤儿任务会通过回调把 ID 送回来**,
因此回调在此供应商上不只是延迟优化,还是**孤儿任务回收通道**。这是 ADR-023
§4「M4 只做轮询、回调仅预留字段」之外的一个新论据,值得复评。
### ② 查询接口形态与频率限制
- 形态:`GET .../tasks/{id}`,返回体见 §3。
- **任务记录只保留最近 7 天**(区间 `[T-7天, T)`)。更早的任务查询不到。
- 频率限制**未给具体数值**,但错误码表里有多个 429 分支(见 §4),说明确实有限。
- **官方 SDK 示例用 10 秒轮询 + 30 分钟截止**。我们的 3s/10s/30s 退避阶梯与之
兼容,且厂商自己的示例也用 10 秒——说明这个量级是预期用法。
### ③ 单任务最长时长与超时行为
- `execution_expires_after`:默认 **172800 秒(48 小时)**,取值范围
**`[3600, 259200]`**(1~72 小时),从 `created_at` 起算。
- 超时任务被自动终止并标记为 **`expired`**。
- **实现取下限 3600**:48 小时对交互式应用过长,被放弃的任务会在厂商侧挂两天。
注意**无法设得更短**——1 小时是厂商下限。
- 官方 SDK 示例的客户端截止时间是 **30 分钟**。ADR-023 §10 原先给视频客户端
硬超时排的是 15~20 分钟,**建议按 30 分钟对齐厂商示例**。
### ④ 产物返回形式:**有效期 24 小时的 URL**
- `content.video_url`:生成视频 URL,**有效期 24 小时**;Seedance 2.5 另有
**下载次数上限 100 次**。
- `content.last_frame_url`:尾帧图(png、尺寸与视频一致、**无水印**),仅在提交
时 `return_last_frame: true` 才返回。
**这一条对挂起中的存储决策是硬约束**:厂商 URL **不能**当存储用。把它存进
`media.assets.external_url` 会在 24 小时后变成死链,因此**无论最终选本地存储
还是云存储,都必须在 24 小时内把文件复制到我们自己的存储**。「直接存厂商 URL」
不是一个可选项。
**两个顺带的好处**:
- `last_frame_url` 可**直接用作视频封面图**,省掉我们自己抽帧。`iteration-4/03`
R13 把「缩略图」列为视频进 M4 的成本项之一,这一项实际是免费的。
- 查询返回 `ratio`、`resolution`、`framespersecond`、`duration`/`frames`,
**Worker 写产物时维度与时长都在手里、无需解码**。这正是 ADR-023 §11 所说
「AI 输出路径不需要图片解码」的具体依据,`widthPx`/`heightPx` 债在 AI 产物
上可零成本修掉。
**一个精度陷阱**:查询返回的 `duration` 是**向下取整的约数**——官方公式为
`duration = 实际总帧数 / 24` 向下取整,例如 133 帧实际 5.54 秒会返回 5。且
**`duration` 与 `frames` 只会返回其中一个**(提交时指定了 `frames` 才返回
`frames`)。故实现优先按 `frames / framespersecond` 精确换算,仅在只有
`duration` 时退回整数秒并**明确标注为约数**。
### ⑤ 限流与配额响应形态
见 §4 的完整映射。核心结论:**两族必须分开**,且 `QuotaExceeded` 一码两义。
## 3. 查询返回体的关键字段
```json
{
"id": "cgt-2026****-****",
"model": "doubao-seedance-2-0-260128",
"status": "succeeded",
"content": { "video_url": "https://...", "last_frame_url": "https://...png" },
"usage": { "completion_tokens": 108900, "total_tokens": 108900 },
"created_at": 1779348818,
"updated_at": 1779348874,
"resolution": "720p", "ratio": "16:9",
"duration": 5, "framespersecond": 24,
"execution_expires_after": 172800,
"error": null
}
```
`usage.completion_tokens` 可作**计费对账依据**(视频生成不统计输入 token,故
`total_tokens == completion_tokens`)。Seedance 2.0 系列有**最低 token 用量**,
实际低于下限时按下限计费。
### ⚠️ 状态枚举在两份文档里不一致,必须按宽的实现
| 来源 | 列出的状态 |
| --- | --- |
| 查询接口文档 | `queued` / `running` / `cancelled` / `succeeded` / `failed` |
| 创建接口的 `callback_url` 一节 | `queued` / `running` / `succeeded` / `failed` / **`expired`** |
**只按查询文档实现,线上遇到超时任务会掉进未知分支。** 实现按**六个**状态处理,
并把 `expired` 当作**可重试的失败**(是超时而非内容或参数问题);另注意厂商在
`expired` 时不一定给 `error.code`,故单独分类而非走通用路径。
另:`cancelled` **只支持取消 `queued` 状态的任务**,且取消状态 24 小时自动删除。
这意味着我方「取消正在 running 的任务」无法向厂商传导——**已 running 的任务取消
后仍会计费**。ADR-023 §6 关于取消的说明据此可以定稿:取消只改我方任务记录。
## 4. 错误码映射
实现在 `app/providers/errors.py`,测试在 `tests/test_errors.py`(32 项)。
### 🔴 `QuotaExceeded` 一码两义
官方错误码表里有**两行 code 完全相同**的 `QuotaExceeded`:
| message | 含义 | 处置 |
| --- | --- | --- |
| `The request has exceeded the quota.` | 当前账号**排队中任务数**超限 | **等几秒**,退避重试 |
| `...exhausted its free trial quota...` / `...exceeded the 5-hour/weekly/monthly usage quota...` | 额度耗尽 | **等到明天或充值**,重试无用 |
**按 code 一刀切必然有一半场景的文案和出口是错的。** 实现只能按 message 判别
(没有别的字段可依据),判别式与两条用例都在测试里钉住。
### 分族
| 族 | 代表码 | 重试 | 用户侧 |
| --- | --- | --- | --- |
| **内容被拒** | `*SensitiveContentDetected*`(输入/输出 × 文本/图片/视频/音频 × PolicyViolation / PrivacyInformation / DeepFake 等子类)、`*RiskDetection` | **NEVER** | 换提示词或换图 |
| **限流** | `RateLimitExceeded.Endpoint{RPM,TPM,FlexTPM}Exceeded`、`ModelAccount{Rpm,Tpm,FlexTpm,Ipm}RateLimitExceeded`、`APIAccountRpmRateLimitExceeded`、`AccountRateLimitExceeded`、`ServerOverloaded`、`RequestBurstTooFast` | BACKOFF | 稍后重试(SnackBar) |
| **配额/计费** | `SetLimitExceeded`、`InflightBatchsizeExceeded`、`AccountOverdueError`、`OperationDenied.Service{Overdue,NotOpen}`、`ModelNotOpen` | QUOTA | 去充值/开通(sheet) |
| **我方故障** | `AuthenticationError`、`InvalidEndpointOrModel.NotFound`、`InvalidParameter*`、`MissingParameter*`、`InvalidParameter.TaskType{Constraint,Mismatch}` | **NEVER** | 通用失败 |
| **上游内部** | `InternalServiceError` | BACKOFF | 稍后重试 |
两处容易搞错的地方,实现里各有测试钉住:
- **`ContentSecurityDetectionError` 不属于内容被拒**——它是风险识别产品自身请求
失败,不是内容真的违规,**可重试**。
- **内容安全族用前缀匹配而非穷举**:该族的码在持续增加(Input/Output × 媒体类型
× 子原因),穷举必然漏,而整族处置方式完全一致。
- **未知码一律按可退避重试处理**,不判死:厂商在持续新增码,把未知码当终态失败
会让一次可恢复的抖动变成用户看到的失败;重试预算本身有上限,最坏只多试两次。
**403 的欠费/未开通归入配额族**:HTTP 状态码不同(403 而非 429),但用户要做的
事一样——去充值。按状态码分族会把它们错分。
## 5. 模型与能力矩阵(供 V7 目录种子取用)
| 模型 | 文生视频 | 图生视频-首帧 | 首尾帧 | 全模态参考 | 有声 |
| --- | --- | --- | --- | --- | --- |
| Seedance 2.5 | ✓ | ✓ | ✓ | ✓(图 0-30 / 视频 0-10 / 音频 0-10,可仅音频) | ✓ |
| Seedance 2.0 系列 | ✓ | ✓ | ✓ | ✓(图 0-9 / 视频 0-3 / 音频 0-3,不可仅音频) | ✓ |
| Seedance 1.5 pro | ✓ | ✓ | ✓ | — | ✓ |
| Seedance 1.0 pro | ✓ | ✓ | ✓ | — | — |
| Seedance 1.0 pro fast | ✓ | ✓ | — | — | — |
分辨率与宽高比的可选值**随模型不同**(2.5 无 4k,2.0 fast/mini 只到 720p;
2.5 在首帧/首尾帧、视频编辑、视频延长任务中 **`ratio` 只支持 `adaptive`**)。
时长范围:2.5 为 `[4, 30]`,2.0 系列为 `[4, 15]`,均支持 `-1` 智能选择。
文档出现过的 model ID 示例:`doubao-seedance-2-5-260628`、
`doubao-seedance-2-0-260128`、`doubao-seedance-1-0-pro-250528`、
`doubao-seedance-1-0-lite-i2v-250428`。
> **V7 种子待定项**:实际可用的 model ID 取决于账号开通与推理接入点配置,
> 文档示例值不能直接当种子。需确认账号实际开通的模型后再写 V7,否则种子里
> 的 `provider_model_name` 会指向一个不存在或无权访问的模型
> (对应错误码 `InvalidEndpointOrModel.NotFound` / `ModelNotOpen`)。
## 6. 实现状态
| 项 | 状态 |
| --- | --- |
| 错误分类(`errors.py`) | ✅ 32 测试 |
| 提交与查询客户端(`seedance.py`) | ✅ 26 测试,全部离线(MockTransport) |
| 产物转存 | ⏸ 阻塞于存储决策(ADR-023 §7)。注意 §2-④ 的 24 小时硬约束 |
| V7 目录种子 | ⏸ 待确认账号实际开通的 model ID |
| Java 侧 provider 适配 | ⏸ 待本服务的 HTTP 端点与 `openapi-internal.yaml` 落地 |
+2
View File
@@ -11,6 +11,8 @@ nav:
- 发布记录: development/releases.md - 发布记录: development/releases.md
- 服务器暴露面清单: development/server-exposure.md - 服务器暴露面清单: development/server-exposure.md
- CI Runner 部署手册: development/ci-runner-setup.md - CI Runner 部署手册: development/ci-runner-setup.md
- 服务商接口:
- 火山方舟 Seedance(视频): development/providers/volcengine-seedance.md
- 第一迭代: - 第一迭代:
- 进展看板: development/iterations/iteration-1/index.md - 进展看板: development/iterations/iteration-1/index.md
- 01 任务分解: development/iterations/iteration-1/01-pm-task-breakdown.md - 01 任务分解: development/iterations/iteration-1/01-pm-task-breakdown.md