一句话:把 vertex-ai/gemini-3-flash-preview 换成 doubao-seed-2.1-pro。模型本身能看视频,所以这事能做;难的不是协议,是视频喂给模型的格式跟我们现在的管线对不上。复用已有的 ChatOpenAI 分支,改动能压到 4 处。
先搞清一件容易踩坑的事:frame_extractor 抽帧靠的是它自己那个 LLM 在看视频,不是某个独立的视频服务。所以换了模型 = 换了那双眼睛,新模型必须能吃视频。
video_probe 先量时长subagent_analyze_media 只把视频上传 GCS、准备好"零件",自己不调 LLMdefault_modelextract-frame 抽帧 → 自审去重 → exit_subagentHumanMessage 注入子代理本轮调用的(subagent_executor.py ~line 788)。换 default_model 就是换看视频的那个模型 —— 它不能看视频,这个子代理就瞎了。
好消息先说:doubao-seed-2.1-pro 原生支持 文本/图像/视频/音频,256K context,能看 2h+ 视频。ZenMux 两个端点都支持多模态。所以"模型看不了视频"这条已作废。真正卡住的是下面三个。
现在注入的是 Vertex/Gemini 形状,doubao 不认:
// 现状(Gemini) {"type": "media", "file_uri": "...", "mime_type": "video/mp4"} // doubao / ZenMux 要的(OpenAI 协议) 图片 → {"type":"image_url", "image_url":{"url":"..."}} 视频 → {"type":"file", "file":{"filename":"v.mp4","file_data":"..."}}
这是本任务唯一有工作量的地方(改动 D)
config_service.py:372 把 provider zenmux 硬绑 ChatAnthropic + /api/anthropic 端点,而且 enable_reasoning=True 时强塞 Anthropic thinking —— doubao 不吃。
所以不复用 zenmux,改走已存在的 openai → ChatOpenAI 分支(line 254),它不加 thinking,最干净。
_upload_to_gcs 返回的 data["url"](subagent_analyze_media.py:126)—— Gemini 走的是同 GCP 生态。doubao 要自己去拉这个 URL,若它是私有 gs:// 就拉不到,需要换签名/公开 URL 或 base64。必须实测确认。
A / B / C 都是几行的配置和路由,D 是格式适配(主要工作量,且老 Gemini 路径必须零改动,回滚才干净)。
| # | 文件 | 位置 | 改动 | 量 |
|---|---|---|---|---|
| A | config.toml |
provider 区 | 新增 [zenmux_openai](url=https://zenmux.ai/api/v1)+ bytedance/doubao-seed-2.1-pro 模型条目 |
几行 |
| B | config_service.py |
line 254 | 让新 provider 复用 ChatOpenAI 分支(顺手给 268 行加 or {} 防御) |
1 行 |
| C | subagent_configs.yaml |
frame_extractor ~107 |
切 default_model,只改这一个 |
1 行 |
| D | subagent_executor.py |
pre_model_hook ~712–793 | 按 provider 构造 image_url / type:"file" part;Gemini 老路径不动 |
主要 |
- if model_config.provider == 'openai': + if model_config.provider in ('openai', 'zenmux_openai'): ... - params.update(model_config.extra_params) + params.update(model_config.extra_params or {})
frame_extractor:
...
- default_model: "vertex-ai/gemini-3-flash-preview"
+ default_model: "zenmux_openai/bytedance/doubao-seed-2.1-pro"
model_config.name = bytedance/doubao-seed-2.1-pro,正好是 ZenMux 要的 model 值,无需额外映射。成本最低、风险最高的点在 ZenMux 视频通道本身。先用独立脚本验证它真能用,过了再做 A–D,否则后端改完照样是瞎的。
curl https://zenmux.ai/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ZENMUX_API_KEY" \ -d '{ "model": "bytedance/doubao-seed-2.1-pro", "messages": [{"role":"user","content":[ {"type":"text","text":"描述视频关键画面,给时间点"}, {"type":"file","file":{"filename":"v.mp4","file_data":"<公网视频URL>"}} ]}] }'
用最小 python 跑 ChatOpenAI(...).invoke([...]),确认它原样透传 {"type":"file"}。不支持就改 base64 data-url 或升级 langchain-openai。
| 优先级 | 风险 | 对策 |
|---|---|---|
| 最高 | ZenMux 视频 type:"file" + 我们的 GCS URL 端到端能否解析 | §04 必须先验 |
| 高 | langchain ChatOpenAI 是否透传 type:"file" 视频 block | 不行就 base64 / 升级依赖 |
| 中 | _upload_to_gcs 返回若是私有 gs://,doubao 拉不到 | 换公开 / 签名 URL 或 base64 |
| 中 | tool_choice="any"(doubao 非 anthropic 会保留)能否映射成 OpenAI required | frame_extractor 依赖 exit_subagent 收尾,须确认强制工具调用可用 |
| 低 | 256K context vs 长视频 token —— 2h 视频可能撑爆 | frame_extractor 多为短广告素材,留意即可 |
用户在对话里明文贴过 ZenMux key(sk-ai-v1-e140…),视为已泄露 —— 务必去 ZenMux 后台轮换后再写进 config.toml。
config.toml 含明文密钥,不要提交进 git。
单点回滚:把改动 C 的 default_model 改回 vertex-ai/gemini-3-flash-preview 即恢复。A/B/D 是新增 / 旁路逻辑,不影响其它子代理 —— 前提是 D 严格保证 Gemini 老路径零改动。
advanced_creator、researcher 也用 gemini-3-flash-preview,只改 frame_extractor 这一个。
§04 的 curl + langchain 双验,过了再动后端。
配置和路由先上,D 的格式适配最后做。
触发一条 clone-video 抽帧,看日志 injected N part(s),确认模型真看到视频、extract-frame 与 exit_subagent 正常。
同一条视频跟旧 Gemini 版比抽帧质量,确认没退化。