feat(video): DashScope video specialist with correct i2v request bodies

- Add video_generation_client: async video-synthesis, t2v vs i2v (Wan 2.7 input.media first_frame vs legacy img_url).

- Coerce *-t2v* to *-i2v* when first frame present; AIA_VIDEO_I2V_INPUT_STYLE / I2V_MODEL overrides.

- Gateway/direct_loop early return for video specialist; workspace video + factory allowlist.

- Normalize WS and admin chat attachments (image_ref parity); mp4 attachment store roundtrip tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-05-10 16:20:43 +08:00
parent 398d85ba06
commit 4335040a27
21 changed files with 1152 additions and 56 deletions

View file

@ -393,6 +393,67 @@
> **说明:** 历史上曾使用 `AIA_IMAGE_BASE_URL` / `AIA_IMAGE_API_KEY` / `AIA_IMAGE_MODEL` / `AIA_IMAGE_CHAT_ENDPOINT` 作为同一路由;当前实现 **不再读取** 上述变量作 OCR 通道配置,请统一改为 `AIA_OCR_*`。(`AIA_IMAGE_TOOL_RESULT_REPLAY_CAP_CHARS` 等为 **另一用途**,与 OCR 网关无关,仍保留原名。)
---
## 视频生成专家专线(``AIA_VIDEO_EXPERT_*``)
**路由 specialist=`video`** 时由 **`send_video_generation_request`** 调用百炼 / DashScope **异步文生视频** HTTP(``POST .../video-synthesis`` + ``GET .../api/v1/tasks/{task_id}``)。**区域**:北京 ``https://dashscope.aliyuncs.com``、新加坡 ``https://dashscope-intl.aliyuncs.com``、美东 ``https://dashscope-us.aliyuncs.com`` 等须与 API Key 一致。说明全文见 ``docs/VIDEO_SPECIALIST_LANE.md``。
- `AIA_VIDEO_SPECIALIST_DISABLE_LEGACY_GATEWAY_LANE`
- 默认:未设置(关闭等价于 **启用** 专用 Early Return)
- 作用:设为 `1` / `true` / `yes` / `on` 时,网关 **不再** 走专用视频 HTTP 线,改与普通 Chat 相同的模型环路。
- 生效:`runtime/direct_loop.py`
- `AIA_VIDEO_EXPERT_BASE_URL`
- 默认:无(必填,除非在调用处传入 `base_url`)
- 作用:DashScope 根 URL(可为 ``compatible-mode/v1`` 前缀,实现会剥离后拼接原生路径)。
- 生效:`oclaw/platform/llm/video_generation_client.py`
- `AIA_VIDEO_EXPERT_API_KEY`
- 默认:无(必填,除非显式传 `api_key`)
- 作用:``Authorization: Bearer`` API Key。
- 生效:`oclaw/platform/llm/video_generation_client.py`
- `AIA_VIDEO_EXPERT_MODEL`
- 默认:无(会话所选模型的 `model` 字段优先;无则读本变量)
- 作用:Wan 模型 id:**文生视频**用 t2v 系列(如 ``wan2.2-t2v-plus``);**图生视频**(消息带首帧图时自动传 ``input.img_url``)须用 **i2v** 系列(如 ``wan2.6-i2v-flash``,以控制台为准)。
- 生效:`oclaw/platform/llm/video_generation_client.py`
- `AIA_VIDEO_EXPERT_SYNTHESIS_PATH`
- 默认:`api/v1/services/aigc/video-generation/video-synthesis`
- 作用:相对 `AIA_VIDEO_EXPERT_BASE_URL` 剥离 compatible 后缀后的根路径拼接用。
- 生效:`oclaw/platform/llm/video_generation_client.py`
- `AIA_VIDEO_EXPERT_POLL_INTERVAL_SEC`
- 默认:`15`(限制在约 `3`~`120` 秒)
- 作用:轮询任务状态间隔。
- 生效:`oclaw/platform/llm/video_generation_client.py`
- `AIA_VIDEO_EXPERT_MAX_WAIT_SEC`
- 默认:`900`(限制在约 `30`~`3600` 秒)
- 作用:自提交任务起的最大等待时间;超时返回错误。
- 生效:`oclaw/platform/llm/video_generation_client.py`
- `AIA_VIDEO_EXPERT_PARAMETERS_EXTRA`
- 默认:无
- 作用:JSON 对象,**浅合并**到请求体 `parameters`(后写入,故与 `DASHSCOPE_VIDEO_*` 同名键时 **以该 JSON 为准**)。
- 生效:`oclaw/platform/llm/video_generation_client.py`
- `AIA_VIDEO_EXPERT_INPUT_EXTRA`
- 默认:无
- 作用:JSON 对象,合并到请求体 `input`(`prompt` 仍由运行时代码写入并覆盖同名键)。
- 生效:`oclaw/platform/llm/video_generation_client.py`
- `AIA_VIDEO_EXPERT_DEBUG_PRINT_PAYLOAD`
- 默认:未设置
- 作用:设为 `1` 时在 stderr 打印提交 URL 与 JSON 请求体(勿在生产长期开启)。
- 生效:`oclaw/platform/llm/video_generation_client.py`
- `DASHSCOPE_VIDEO_SIZE` / `DASHSCOPE_VIDEO_DURATION` / `DASHSCOPE_VIDEO_PROMPT_EXTEND` / `DASHSCOPE_VIDEO_NEGATIVE_PROMPT` / `DASHSCOPE_VIDEO_AUDIO_URL` / `DASHSCOPE_VIDEO_SHOT_TYPE` / `DASHSCOPE_VIDEO_WATERMARK` / `DASHSCOPE_VIDEO_SEED`
- 默认:均未设置则不附加对应字段。
- 作用:与官方示例字段对齐的便捷变量;细粒度控制可改用 `AIA_VIDEO_EXPERT_PARAMETERS_EXTRA` / `AIA_VIDEO_EXPERT_INPUT_EXTRA`。
- 生效:`oclaw/platform/llm/video_generation_client.py`
## Memory / RAG
- `AIA_RAG_MODE`

View file

@ -35,6 +35,11 @@ Transport selection happens in `oclaw/runtime/agents/factory.py`.
- **Not** a separate profile transport: when the user selects specialist **`image`** in `/chat`, `runtime/direct_loop.py` takes an early return and calls **`platform/llm/image_legacy_client.send_legacy_image_messages`** (DashScope-style `/chat/completions`), so that turn does **not** use `OpenAIResponsesModel` / chat transports above.
- Details, env vars, ACL, and UI hooks: **`docs/IMAGE_SPECIALIST_LANE.md`**.
### Chat UI「视频生成专家」(绕行本矩阵)
- When the user selects specialist **`video`**, `runtime/direct_loop.py` early-returns into **`platform/llm/video_generation_client.send_video_generation_request`** (DashScope async `video-synthesis` + task polling). Without an input image: **text-to-video**; with an image attachment (or session image fallback): **`input.img_url`** for **image-to-video** (use an i2v model id). That turn does **not** use the generic tool loop or `OpenAIResponsesModel`.
- **`docs/VIDEO_SPECIALIST_LANE.md`**.
### `anthropic` (Anthropic Messages streaming)
- **Transport**: `oclaw/platform/llm/transports/anthropic_messages.py::AnthropicMessagesModel`
- **API**: Anthropic `messages.stream` surface (gateway must provide Anthropic-compatible protocol)

View file

@ -0,0 +1,68 @@
# 视频生成专家(Chat UI)专用链路
本文描述 **Admin `/chat` 选择「视频」专家**(工作区 id **`video`**)时的端到端路径。与图片专家类似,这是一条 **与通用 Responses / 主对话工具环路隔离** 的分支:在 `skill_binding_role == "video"` 时 **Early Return**,不进入常规 `run_oclaw_direct_loop` 多轮工具循环。
参考 API 形态:阿里云 Model Studio **Wan**(DashScope 异步:同一 `POST …/video-synthesis` → `GET …/api/v1/tasks/{task_id}`)。**文生视频**仅 `input.prompt`;**图生视频**额外设置 `input.img_url`(公网 HTTPS 或 `data:image/...;base64,...` 首帧),需使用 **i2v** 模型(如 `wan2.6-i2v-flash`,以控制台为准)。区域化的 **`base_url` 必须与 API Key 区域一致**。控制台 API 入口示例:[百炼控制台](https://bailian.console.aliyun.com/)。
---
## 1. 触发条件与隔离边界
| 条件 | 说明 |
|------|------|
| UI / Gateway | 用户选择专家 **`video`**,请求携带 `skill_binding_role` 为 **`video`**。 |
| 入口守卫 | `runtime/direct_loop.py` 中 **`_maybe_video_specialist_legacy_gateway_turn`**:仅当 `skill_binding_role.lower() == "video"` 且未设置禁用开关时执行。 |
| 禁用开关 | `AIA_VIDEO_SPECIALIST_DISABLE_LEGACY_GATEWAY_LANE=1`:关闭本 Early Return,视频专家改走与普通会话相同的模型/传输栈。 |
实现集中在 **`platform/llm/video_generation_client.py`**(HTTP + 轮询 + 附件落地),避免在 `openai_responses` 中分叉。
---
## 2. 运行时数据流(网关 → 落库)
1. **`run_oclaw_direct_loop`** 在用户消息落库后,在图片专家分支之后调用 **`_maybe_video_specialist_legacy_gateway_turn`**。
2. **Prompt**:使用本轮用户文本;若为空则使用 **`VIDEO_SPECIALIST_DEFAULT_PROMPT_ZH`**(`video_generation_client`)。图生视频时 `prompt` 仍建议填写(描述期望动态与镜头)。
3. **首帧图**:与图片专家相同,使用 **`collect_legacy_lane_images_with_session_fallback`**(`image_legacy_client`)从**本轮附件**或(未关闭 **`AIA_IMAGE_SPECIALIST_SESSION_IMAGE_FALLBACK`** 时)**会话历史**中取 **1 张**图,转为 URL / data URL 后写入 **`input.img_url`**。无图则走纯文生视频。
4. **鉴权与根 URL**:优先使用会话所选模型的 **`model` / `base_url` / `api_key`**;缺省字段由 **`AIA_VIDEO_EXPERT_*`** 环境变量补全。若 `base_url` 指向 **`compatible-mode/v1`**,实现会剥离该后缀以拼接原生 DashScope 路径。
5. **调用**:`send_video_generation_request` — `POST .../video-synthesis`(`X-DashScope-Async: enable`),再轮询 **`GET .../api/v1/tasks/{task_id}`** 直至 `SUCCEEDED` / 失败 / 超时。
6. **输出**:成功时从 `output.video_url` 下载为本地 blob,产出 **`video_ref`**;下载失败时退化为仅带 **`url`** 的 `video_ref` 行(前端仍可尝试外链播放)。
7. **占位文案**:`legacy_video_assistant_body_with_placeholder` 与图片专家对称(仅附件、无正文时插入中英文短句)。
8. **编排**:`runtime/agents/specialist_agent.py` 在 `step.specialist == "video"` 时调用同一客户端(按父任务附件 + 父会话历史解析首帧),保证综合模式子专家与专家模式行为一致。
---
## 3. 执行器与工具面
- **`runtime/agents/factory.py`**:`video` 与 `image` 一样使用 **不可能工具名 allowlist**,避免默认工具注册混入。
- **`runtime/gateway.py`**:综合模式 Manager 白名单包含 **`video`**,否则子专家选择会被回退。
---
## 4. 鉴权与附件(ACL)
与 **`image_ref`** 相同,助手消息上的 **`video_ref`** 依赖 **`attachment_acl`** 才能在严格模式下通过下载接口访问;落库时由 `SqliteStore.add_message` 链路处理。参见 **`docs/attachment-acl.md`**。
---
## 5. 前端
- **`interfaces/admin/static/chat.js`**:`specialistLabel` 对 **`video`** 显示短标签;`video_ref` 卡片在可用 blob URL 或外链 URL 时附加 **`<video controls>`** 便于预览。
---
## 6. 环境变量(索引)
详见 **`docs/ENVIRONMENT_VARIABLES.md`** 中 **`AIA_VIDEO_EXPERT_*`** 与 **`DASHSCOPE_VIDEO_*`** 小节。
---
## 7. 测试
- **`tests/test_video_generation_client.py`**:提交 / 轮询 HTTP 形态的单元测试(mock `httpx`)。
---
## 8. 变更原则
1. 默认只改 **`video_generation_client.py`**、`direct_loop` 的 **`_maybe_video_specialist_*`**、`specialist_agent` 视频分支、`factory` / `gateway` 白名单、**`chat.js`** 附件展示、本文与 **`ENVIRONMENT_VARIABLES.md`**。
2. 勿在通用 **`openai_responses`** 中为视频专家单独绕路,除非产品明确要求统一传输。