Compare commits

...

2 Commits

Author SHA1 Message Date
caoqianming 5faff8a127 feat(seedance): 加 seedance_2_pro variant + smoke 支持 --variant 参数
- yaml 加 seedance_2_pro (model_id=doubao-seedance-2-0-260128, ¥46/Mtok 文生,
  支持 480p/720p/1080p);放在 fast 后面,fast 仍是默认 (yaml 首位 = agent fallback)
- Pro 出片慢,poll_timeout_s 拉到 900s (实测 480p 4s 等了 177s)
- smoke_seedance.py 加 sys.argv[1] 选 variant:`smoke_seedance.py seedance_2_pro`
- SKILL 把 "30-90s" 校准到 "Fast 30-90s / Pro 2-3min" (实测)

smoke Pro 跑通:body schema 完全对,响应带真 usage.total_tokens (40594),tool
_extract_tokens 命中走真值不估算;480p 4s 实测 ¥1.87,与官方源数据线性外推吻合。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:11:31 +08:00
caoqianming da71daa789 feat(seedance): 加 generate_audio 参数对齐官方 API
火山方舟官方 curl 例子 (CreateContentsGenerationsTasks) body 里有 generate_audio 字段
— Seedance 2.0 旗舰特性,原生 AI 生成背景音 / 角色对白 / 音效。我之前没接,补上。

- yaml 加 default_generate_audio: false (控成本默认关)
- tool execute 加 generate_audio kw + 进 body / meta / usage_events extra_units
- tool banner 加 audio=<bool> 字段
- SKILL 加参数段说明 + prompt 写法变化 (开音轨时要写背景音/对白具体内容)
  + 贴 prompt 模板 + 反模式各加一条

resolution / watermark 官方例子里没发但其他文档源确认 fast 必传 + 有效,保留。
参考媒体 (image_url/video_url/audio_url + role:reference_*) 是 i2v/v2v/r2v 路径,
phase 1 不接,延后。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:44:15 +08:00
4 changed files with 86 additions and 9 deletions

View File

@ -26,6 +26,7 @@ image:
request_timeout_s: 60 # 出图慢于此判超时
video:
# fast 放第一个 → 默认 variant(成本敏感场景优先);开通了 Pro 的用户从顶栏下拉切。
seedance_2_fast:
model_id: doubao-seedance-2-0-fast-260128
display_name: 豆包 Seedance 2.0 Fast
@ -52,8 +53,41 @@ video:
default_ratio: "16:9" # 16:9 / 9:16 / 1:1 / 4:3 / 3:4 / 21:9 / adaptive
default_duration: 5 # 4-15s
default_watermark: false
# generate_audio: Seedance 2.0 旗舰特性 —— 原生 AI 生成背景音效 / 角色对白(无需后期配音)。
# 开启会增加 token 消耗(模型还要算音轨),cost 比纯视频高;默认关闭让 cost 可预测,
# 用户要带音的视频(广告 / 角色对白)时显式传 true。
default_generate_audio: false
# 轮询参数
request_timeout_s: 60 # submit POST 超时(异步,只是提交)
poll_interval_s: 5 # 单次 GET 间隔(秒);典型 30-90s 出片
poll_timeout_s: 600 # 总等待上限(10min)→ 超时返 [Error]
seedance_2_pro:
model_id: doubao-seedance-2-0-260128
display_name: 豆包 Seedance 2.0 Pro
# 同 fast 一套异步 task + 轮询接口,只换 model_id + 单价 + 分辨率上限。
endpoint_submit: /contents/generations/tasks
endpoint_poll: /contents/generations/tasks
# 计费(per-token,公式同 fast):
# 文生视频: ¥46 / Mtok (vs fast ¥37)
# 图生视频: ¥28 / Mtok (vs fast ¥22)
# 实测档位(Pro, 5s, 文生视频, 24fps):
# 720p 16:9 → ¥4.97 (vs fast ¥4.00)
# 1080p 16:9 → 更高(Pro 独占,fast 不支持)
price_cny_per_mtoken_text2video: 46.0
price_cny_per_mtoken_video2video: 28.0
fps: 24
# 支持参数 — Pro 支持 1080p,默认仍 720p 控成本
default_resolution: 720p # 480p / 720p / 1080p(Pro 独占)
default_ratio: "16:9"
default_duration: 5
default_watermark: false
default_generate_audio: false
# 轮询参数:Pro 出片慢于 Fast(更精细),拉长超时
request_timeout_s: 60
poll_interval_s: 5
poll_timeout_s: 900 # Pro 上限拉到 15min 保险

View File

@ -48,6 +48,14 @@ def main() -> int:
if not video_cfg:
print("[SKIP] doubao.yaml 无 video 段")
return 0
# 允许命令行指定 variant:`smoke_seedance.py seedance_2_pro`;不传走 yaml 第一个
want_variant = sys.argv[1] if len(sys.argv) > 1 else ""
if want_variant:
if want_variant not in video_cfg:
print(f"[FAIL] variant {want_variant!r} 不在 yaml video 段, 已知: {list(video_cfg.keys())}")
return 2
variant_key, variant_cfg = want_variant, video_cfg[want_variant]
else:
variant_key, variant_cfg = next(iter(video_cfg.items()))
print(f"[setup] variant={variant_key} model={variant_cfg.get('model_id')}")
print(f" price_text2video=¥{variant_cfg.get('price_cny_per_mtoken_text2video')}/Mtok")

View File

@ -1,6 +1,6 @@
---
name: videogen
description: 用豆包 Seedance 2.0 Fast 生视频(`seedance` tool)。**任何生视频任务调 tool 前必须 load 本 skill**。触发词:视频 / 动画 / 动起来 / 做个 video / 做段视频 / 出段视频 / 生成视频 / mov / mp4 / 短片 / 镜头 / 运动镜头 / 演示视频 / 动效。核心:视频比图贵 10 倍(¥1.86-¥4+ / 段),且要等 30-90s,问清楚再画 + 强制确认。
description: 用豆包 Seedance 2.0 Fast 生视频(`seedance` tool)。**任何生视频任务调 tool 前必须 load 本 skill**。触发词:视频 / 动画 / 动起来 / 做个 video / 做段视频 / 出段视频 / 生成视频 / mov / mp4 / 短片 / 镜头 / 运动镜头 / 演示视频 / 动效。核心:视频比图贵 10 倍(¥1.86-¥4+ / 段),且要等 Fast 30-90s / Pro 2-3min,问清楚再画 + 强制确认。
---
# Videogen
@ -20,7 +20,7 @@ description: 用豆包 Seedance 2.0 Fast 生视频(`seedance` tool)。**任何
- 用户**沉默 / 长时间不回 / 追问别的事** → 不算确认,**继续等**,不要自作主张
- 用户回 "看起来不错" / "差不多" / 模棱两可 → **主动追问一句"这就开烧 ¥X?"**,拿到明确"是"再调
**为什么比 imagegen 还严**:视频单价 ¥4 起,比图贵 10 倍以上;一次失败相当于 18 张错图;且要等 30-90s,用户改方向的等待代价也很高。**装配 prompt 不等于授权调用** —— 装配是模型脑内运算,授权要落到用户的"嗯,画吧"上。
**为什么比 imagegen 还严**:视频单价 ¥4 起,比图贵 10 倍以上;一次失败相当于 18 张错图;且要等 Fast 30-90s / Pro 2-3min,用户改方向的等待代价也很高。**装配 prompt 不等于授权调用** —— 装配是模型脑内运算,授权要落到用户的"嗯,画吧"上。
## 何时用本 skill
@ -49,7 +49,7 @@ description: 用豆包 Seedance 2.0 Fast 生视频(`seedance` tool)。**任何
**模糊时主动问一句**:
> 你这是想要 **一张静态图**(seedream,¥0.22,3-5 秒出图),还是 **一段短视频**(seedance,¥4 起,等 30-90s)?静态图够的话省钱省时间。
> 你这是想要 **一张静态图**(seedream,¥0.22,3-5 秒出图),还是 **一段短视频**(seedance,¥4 起,等 Fast 30-90s / Pro 2-3min)?静态图够的话省钱省时间。
## 诊断模糊度 — 六维清单(运动是新增的必填维)
@ -78,7 +78,7 @@ description: 用豆包 Seedance 2.0 Fast 生视频(`seedance` tool)。**任何
> - **场景**:工地中景,远处有塔吊
> - **镜头**:固定俯视 + 缓慢推近模板中心
> - **风格**:写实工程纪录片
> - **时长 / 分辨率 / 比例**:5s / 720p / 16:9(ppt 用) — ¥4.00,等 30-90s
> - **时长 / 分辨率 / 比例**:5s / 720p / 16:9(ppt 用) — ¥4.00,等 Fast 30-90s / Pro 2-3min
>
> 这样画可以吗?或者告诉我:想突出什么?(浇筑工艺细节 / 振动棒动作 / 大场面气势 / 慢动作)
@ -115,11 +115,12 @@ description: 用豆包 Seedance 2.0 Fast 生视频(`seedance` tool)。**任何
> ratio: 16:9(ppt 横版)
> duration: 5 秒
> watermark: false
> generate_audio: false(静音视频,后期 ppt 加 BGM)
> 预计花费: ¥4.00
> 预计等待: 30-90 秒
> ````
>
> 这样开烧?要改什么?(改 prompt 文字 / 改时长 / 换比例 / 降到 480p 省钱)
> 这样开烧?要改什么?(改 prompt 文字 / 改时长 / 换比例 / 降到 480p 省钱 / 开 audio 加 AI 配音)
然后 ⛔ **BLOCKING:等用户明确回复**
@ -169,6 +170,21 @@ description: 用豆包 Seedance 2.0 Fast 生视频(`seedance` tool)。**任何
|---|---|
| `false` | 默认无水印(申报 / ppt / 客户交付都不该带);仅当用户明确说"加水印"才传 `true` |
### `generate_audio`(Seedance 2.0 旗舰特性)
| 默认 | 何时开 |
|---|---|
| `false` | 默认关 —— 控成本 + 大多数 ppt / 申报场景用静音视频(后期配音 / 当背景视频)。**模型还要算音轨,开启 cost 会高于纯视频估算**(具体增幅未实测,首次开时盯一下返回的 tokens)。 |
| `true` | 用户明确要**带声音的视频**:广告 / 短剧 / 角色对白 / 配乐场景。**调用前必须告诉用户**"开启 audio,cost 会高于估算 ¥X"并等确认。 |
prompt 写法变化:开 `generate_audio=true` 时,prompt 里要描述**声音是什么**(背景音 / 音效 / 对白台词具体说啥 / 音色),否则模型随机配,效果难控:
```
... 背景音「鲜切现摇」,女生音色,轻快鼓点卡点
```
参考火山方舟官方 r2v 例子(`r2v_tea_*`):每个镜头段都明示"背景音 XX / 卡点 XX / 音效 XX"。**不写 = 不可预期**。
## 调用范式
**前置条件**:用户已经看过最终 prompt + 所有参数,明确回复"可以" / "OK" / "出片吧" 之类。**没看到这个确认就不要调**。
@ -185,7 +201,7 @@ seedance(
)
```
**调用是同步阻塞 30-90s** —— tool 内部 submit 后轮询直到 succeeded,期间 LLM 卡住。这不是 bug,告诉用户"提交了,等 30-90 秒"再耐心等返回。
**调用是同步阻塞 Fast 30-90s / Pro 2-3min** —— tool 内部 submit 后轮询直到 succeeded,期间 LLM 卡住。这不是 bug,告诉用户"提交了,等 30-90 秒"再耐心等返回。
返回串首行是 `[seedance] model=... · resolution=... · ratio=... · duration=Xs · cost=¥... · elapsed=...s` —— 原样保留给用户(SPA 会 parse 挂徽章)。第二行 `saved: <相对路径>` 是产物路径,告诉用户。
@ -232,6 +248,7 @@ seedance(
- ❌ prompt 里写否定 "no shaking, not blurry" —— Seedance 不支持 negative,反向起效
- ❌ 让用户在 seedream / seedance 之间默默替他决定 —— 模糊就一句话问明白
- ❌ phase 1 拿用户已生成的图试图 i2v —— **当前不支持**,明确告诉用户
- ❌ 用户没说要声音就把 `generate_audio` 设 true —— 多花钱且大概率配出不合用户预期的随机音效;开它前必须问"要 AI 配音吗?cost 会高于估算"
- ❌ 用 `run_python``requests` 裸打豆包 API —— 走 `seedance` tool(已封装异步轮询 + 计费 + 落盘 + meta + cancel)
## 输出

View File

@ -102,6 +102,13 @@ class SeedanceTool(Tool):
"type": "boolean",
"description": "是否打豆包水印。默认 false(申报/PPT 场景反需求)。",
},
"generate_audio": {
"type": "boolean",
"description": (
"是否同步生成 AI 背景音 / 对白(Seedance 2.0 旗舰特性)。默认 false 控成本;"
"广告 / 短剧 / 角色对白等场景传 true,模型会一并算音轨,cost 高于纯视频。"
),
},
},
"required": ["prompt"],
}
@ -135,6 +142,7 @@ class SeedanceTool(Tool):
ratio: Optional[str] = None,
duration: Optional[int] = None,
watermark: Optional[bool] = None,
generate_audio: Optional[bool] = None,
) -> str:
if not (prompt or "").strip():
return "[Error] prompt 不能为空"
@ -145,6 +153,9 @@ class SeedanceTool(Tool):
chosen_ratio = ratio or cfg.get("default_ratio", "16:9")
chosen_duration = int(duration) if duration is not None else int(cfg.get("default_duration", 5))
chosen_watermark = bool(cfg.get("default_watermark", False)) if watermark is None else bool(watermark)
chosen_generate_audio = (
bool(cfg.get("default_generate_audio", False)) if generate_audio is None else bool(generate_audio)
)
fps = int(cfg.get("fps", 24))
submit_timeout = float(cfg.get("request_timeout_s", 60))
@ -162,6 +173,7 @@ class SeedanceTool(Tool):
"resolution": chosen_resolution,
"duration": chosen_duration,
"watermark": chosen_watermark,
"generate_audio": chosen_generate_audio,
}
t0 = time.monotonic()
@ -232,6 +244,7 @@ class SeedanceTool(Tool):
"duration_s": chosen_duration,
"fps": fps,
"watermark": chosen_watermark,
"generate_audio": chosen_generate_audio,
"tokens": tokens_actual,
"tokens_estimated": tokens_estimated,
"price_cny_per_mtoken": price_t2v,
@ -258,7 +271,11 @@ class SeedanceTool(Tool):
price_cny_per_mtoken=price_t2v,
has_video_input=False, # phase 1 仅 t2v;i2v 接入后这里读 body 判断
watermark=chosen_watermark,
extra_units={"cgt_id": cgt_id, "elapsed_s": round(elapsed, 1)},
extra_units={
"cgt_id": cgt_id,
"elapsed_s": round(elapsed, 1),
"generate_audio": chosen_generate_audio,
},
)
except Exception as e:
print(f"[seedance] record_video_usage failed: {type(e).__name__}: {e}", flush=True)
@ -268,7 +285,8 @@ class SeedanceTool(Tool):
# 前端 extractMediaBanner 已 whitelist seedance,正则抓 key=value 挂徽章
return (
f"[seedance] model={model_id} · resolution={chosen_resolution} · ratio={chosen_ratio} · "
f"duration={chosen_duration}s · cost=¥{cost_cny:.2f} · elapsed={elapsed:.1f}s\n"
f"duration={chosen_duration}s · audio={chosen_generate_audio} · "
f"cost=¥{cost_cny:.2f} · elapsed={elapsed:.1f}s\n"
f"saved: {disp}\n"
f"prompt={prompt!r}\n"
f"watermark={chosen_watermark} cgt_id={cgt_id}"