- Core types: Rational time, EntityId, ActionBatch, PngBytes (no Pillow dep) - Config: ~/.quercus/config.toml with 0600 perms, env var override for CI - LLMProvider: Claude (anthropic SDK) and OpenAI-compatible endpoint (covers custom gateways and llama.cpp server mode); graceful degradation without API keys - Tool schemas: 26 curated tools with JSON Schema and read_only flags - AgentLoop: sync dialogue loop with confirmation gate (read-only batches bypass, rejected mutations are reported back to the LLM), PNG frame feedback for multimodal providers - Session log: JSONL recording with frame sha256 hashes, replayable - HostAdapter ABC (signatures only, aligned with OPP/1 method families) - Mock LLM provider (scripted/replay/record) for network-free CI Tests: 69 passed via uv run pytest (no network access)
255 lines
15 KiB
Markdown
255 lines
15 KiB
Markdown
# AI Agent 插件设计(基于 OPP/1 外部插件系统)
|
||
|
||
> 本文是 Oak 引入 AI 能力的长期设计,**已按外部插件系统重写**(旧版假设
|
||
> RIIR 拆分后面对一堆 C ABI 小库、引擎内置 `oak-mcp-server`,已作废)。
|
||
> AI 能力现在是一个**外部功能插件**:独立进程、经 OPP/1 协议操作 Oak。
|
||
>
|
||
> 依赖文档(冲突时以它们为准):
|
||
> - [`external-plugin-system.md`](external-plugin-system.md)——插件系统总体设计
|
||
> (进程模型、能力位、确认模式、里程碑 P1–P6)
|
||
> - [`external-plugin-protocol.md`](external-plugin-protocol.md)——**OPP/1 协议全文**
|
||
> (方法/事件/事务/shm/UI 的冻结定义,本文引用的 §n 均指该文档)
|
||
>
|
||
> **一句话**:多模态 LLM 跑在一个独立插件进程里,经 OPP/1 的策展宿主 API
|
||
> 操作 Oak(事务化编辑、确认后执行),经 `render.get_frame/get_thumbnails`
|
||
> 的 shm 取帧通路把画面回喂模型,形成"编辑 → 看图 → 再编辑"的视觉闭环。
|
||
|
||
---
|
||
|
||
## 1. 定位与前提
|
||
|
||
### 1.1 前提(未满足不动工)
|
||
|
||
- 插件系统 **P1–P3 完成**:传输与生命周期、宿主 API 核心(事务 +
|
||
project/media/timeline/node 方法族)、取帧与导出的 shm 数据面可用。
|
||
- AI 面板需要 **P4**(声明式 UI);无头模式(脚本/CI)只依赖 P1–P3。
|
||
- Python SDK `oakxp`(P6 的一部分)是本插件的载体,二者同期开发、互为验证。
|
||
|
||
### 1.2 设计铁律(继承旧版,按插件系统重述)
|
||
|
||
1. AI Agent **只经 OPP/1** 访问 Oak——插件进程内一行 Oak 代码都没有,
|
||
不链接任何 Oak 产物(铁律:协议是唯一边界)。
|
||
2. 一切编辑动作**必须包在 `edit.begin/commit` 事务里**(协议 §6),
|
||
历史面板一次 Ctrl-Z 整段撤销;UI 默认"确认后执行"。
|
||
3. 不为 AI 发明新的引擎内部机制;Agent 工具面是 OPP/1 方法的组合,
|
||
OPP/1 方法又是现有 `graphops/renderops/oak_task` 的组合。
|
||
4. API key 绝不写入 **Oak 侧**的任何文件(`.ove` 工程、Oak 自身配置);
|
||
允许存**插件自己的**配置文件(§5.1),环境变量仅作 CI/无头场景的覆盖项。
|
||
|
||
## 2. 总体架构
|
||
|
||
### 2.1 插件进程内部分层
|
||
|
||
插件名 **`oak-plugin-ai`**(Python 3,实现语言与发行形态的论证见 §2.3;
|
||
参考实现即插件系统的 `examples/plugin-roughcut` 的完整版):
|
||
|
||
```
|
||
┌─ Oak 主进程 ────────────────────────────────┐
|
||
│ oak-plugin-host(P1–P4 提供) │
|
||
│ ├ OPP/1 控制面(JSON-RPC over stdio) │
|
||
│ └ shm down/up 区域 │
|
||
└───────┬─────────────────────────────────────┘
|
||
│ stdio + shm
|
||
┌───────┴─── oak-plugin-ai(独立进程)────────┐
|
||
│ ⑤ LLMProvider:Claude/GPT │ llama.cpp 本地 │
|
||
│ ④ Agent 编排:对话 loop、工具调用、视觉闭环 │
|
||
│ ③ 工具适配层:OPP 方法 → LLM tool schema │
|
||
│ ② 会话状态:事务令牌、帧缓存、操作日志 │
|
||
│ ① oakxp SDK:分帧/收发/shm attach/帧→PNG │
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
- 插件可以**纯无头**跑(CI、批处理脚本:spawn 后不注册面板,只走工具面);
|
||
注册面板时才是用户可见的"AI 剪辑助手"。
|
||
- **MCP 的位置**:如要让外部 LLM 客户端(Claude Desktop、agent 框架)直连,
|
||
由**插件自己**在进程内起一个 MCP server,把 §3 的工具面按 MCP 再暴露一次。
|
||
Oak 内核始终对 AI、对 MCP 无感知——这是与旧版"引擎内置 oak-mcp-server"
|
||
的根本区别。
|
||
|
||
### 2.2 视觉闭环(本设计的核心)
|
||
|
||
```
|
||
多模态 LLM ──► Agent 编排 ──► OPP 事务化编辑 ──► render.get_frame ──► PNG ──► 回喂 LLM
|
||
▲ │
|
||
└────────────────── 看图判断(效果/切点/内容定位) ◄─────────────────────────┘
|
||
```
|
||
|
||
- **验证式**:`edit.commit` 后立即 `render.get_frame` 取切口/效果帧,LLM 判断
|
||
"效果对不对",不对则 `edit.undo` 或追加修正事务。
|
||
- **内容感知式**:`render.get_thumbnails(range, count≤64)` 等间隔采样拼
|
||
contact sheet,LLM 扫图定位("人何时进画面""哪里该切"),据此下刀——
|
||
自动粗剪/打点的雏形。
|
||
- **连续回放**:`playback.play` + `playback.playhead_moved` 事件(≤30 Hz)+
|
||
按间隔 `get_frame` 采样。注意受 §12 限流约束(默认 8 帧/s),采样间隔
|
||
不得小于限流周期。
|
||
- 完整报文流水示例见协议文档 §14(握手→缩略图→事务下刀→验证帧→事件),
|
||
可直接作为本插件的集成测试夹具。
|
||
|
||
### 2.3 实现语言与跨宿主复用(决策已定)
|
||
|
||
**定为 Python,解释器嵌入式发行**:插件包内含私有 CPython(PyInstaller 或
|
||
python-build-standalone),`.oakplugin` 单包交付,用户无需自行安装 Python。
|
||
插件以 **GPLv3** 开源发布;作为独立进程经 stdio/JSON-RPC 与 Oak 通信,
|
||
许可证选择不影响 Oak 本体。
|
||
|
||
选 Python 而非 Rust 的理由:
|
||
|
||
1. **性能无关**:插件只做协议编解码、LLM 编排、PNG 编码(Pillow 为 C 实现);
|
||
延迟大头是 LLM 往返(秒级)与 Oak 侧渲染(与插件语言无关),插件进程
|
||
没有任何重计算。
|
||
2. **跨宿主复用**:同一套 AI 能力规划覆盖 Oak / DaVinci Resolve / Premiere,
|
||
但三家宿主的扩展 API 语言各异——Resolve 是 Python/Lua;**Premiere 没有
|
||
Python 入口**(UXP/JavaScript 面板 + C++ SDK)。因此正确结构不是
|
||
"一种语言通吃",而是**一份与宿主无关的 AI core + 各宿主薄适配层**:
|
||
|
||
```
|
||
┌─ AI core(宿主无关,一份代码)───────────────┐
|
||
│ Agent 编排 / LLMProvider / tool schema │
|
||
│ 提示词策略 / 会话状态 │
|
||
└──────┬───────────┬──────────────┬───────────┘
|
||
Oak 适配层 Resolve 适配层 Premiere 适配层
|
||
(OPP/1, (Resolve (UXP/JS 面板 →
|
||
oakxp SDK) Python API, localhost 调
|
||
直接 import) core 本地服务)
|
||
```
|
||
|
||
- Oak 适配层 = oakxp SDK(OPP/1),即本文的 `oak-plugin-ai`;
|
||
- Resolve 适配层直接 import core(Python 母语,零成本复用);
|
||
- Premiere 用 UXP 面板做壳,经 localhost 与本机 core 进程通信。
|
||
3. **生态**:主流 LLM SDK(anthropic / openai / llama.cpp 绑定)均为 Python
|
||
一等公民,Agent 框架与评测工具链也最全。
|
||
|
||
## 3. 工具面(LLM tool schema → OPP/1 方法映射)
|
||
|
||
Agent 暴露给 LLM 的是约 20 个**策展工具**,每个是 OPP/1 方法的薄组合
|
||
(协议 §8 是方法全文,下表"OPP 方法"列即最终调用):
|
||
|
||
| Agent 工具 | OPP 方法 | 说明 |
|
||
|---|---|---|
|
||
| `open_project` / `save_project` | `project.open/save` | 工程生命周期(事务 + 确认类) |
|
||
| `get_project_overview` | `project.get_info` + `timeline.get_structure` | 一次返回序列/轨道/块树,供 LLM 建立上下文 |
|
||
| `probe_media` / `import_footage` / `list_footage` | `media.probe/import_footage/list_footage` | 媒体探测与导入 |
|
||
| `add_track` / `place_clip` / `split_clip` / `trim_clip` / `move_clip` / `ripple_delete` / `add_transition` / `add_marker` | `timeline.*` | 时间线编辑,全部在事务内 |
|
||
| `add_effect` / `set_param` / `set_keyframe` / `list_effects` | `node.add_effect/set_param/set_keyframe/list_types` + `get_params` | 效果与关键帧;`get_params` 的 min/max/choices 回填进 tool schema,约束 LLM 出参 |
|
||
| `get_frame(time)` / `scan_timeline(range,n)` / `get_audio_levels` | `render.get_frame/get_thumbnails/get_audio_levels` | **视觉闭环取帧口** |
|
||
| `play` / `pause` / `seek` | `playback.*` | 回放控制 |
|
||
| `export_render(preset)` | `export.start` + `export.progress/done` 事件 | 导出(确认类) |
|
||
| `undo_last_action` | `edit.undo` | 仅用户明确要求时调用(协议 §6 规则 5) |
|
||
|
||
**事务编排是适配层的职责,不暴露给 LLM**:LLM 的一次"动作"(可能含多个
|
||
`timeline.*` 调用)由适配层包成一个事务——先 `edit.begin(label=LLM 动作摘要)`,
|
||
串行执行(协议保证同事务内按到达顺序),任一失败则 `edit.abort` 并把错误
|
||
回喂 LLM,全成功才 `commit`。LLM 看不到 `txn` 令牌,从根上避免"忘记 commit"
|
||
"嵌套事务"这类误用。
|
||
|
||
**取帧→PNG 通路**(关键路径):`render.get_frame` 返回 `FrameRef`(shm 形态:
|
||
`bgra8` + 槽位号),SDK `frame.to_png()`(Pillow)编码 → base64 → 作为图片
|
||
消息发给 LLM;随后**立即 `shm.release`**——批量扫描时必须流水线化释放,
|
||
否则 8 槽耗尽触发 `SHM_EXHAUSTED`(协议 §10.3)。小图(≤64 KiB)Oak 可能
|
||
直接 inline PNG,SDK 对两种形态透明。
|
||
|
||
## 4. AI 面板(声明式 UI,协议 §11.1)
|
||
|
||
面板在 `session.hello.panels` 声明 `"ui":"declarative"`,控件树:
|
||
|
||
```
|
||
column
|
||
├── chat_log #log 对话与操作日志(用户/助手/系统三角色)
|
||
├── list #pending 待确认动作清单(确认模式,见 §5)
|
||
├── row
|
||
│ ├── text_input #prompt 剪辑意图输入
|
||
│ └── button #send 执行
|
||
└── progress #job 扫描/导出进度
|
||
```
|
||
|
||
- 交互经 `ui.event` 上行(`submit`/`click`/`select`),插件用
|
||
`ui.set_props` 增量追加 `chat_log` 条目、更新进度。
|
||
- **"撤销整段会话"**:面板放一个按钮,逐个 `edit.undo` 回滚本会话提交的
|
||
事务(插件在自己的会话状态里记事务顺序)。
|
||
- 面板被关闭会收到 `panel_closed`,重开收到 `panel_shown` 时重发
|
||
`ui.set_tree` 恢复(协议 §11.1)。
|
||
- 像素面 UI(§11.2)本插件**不用**——聊天面板声明式足够。
|
||
|
||
## 5. 模型层与安全
|
||
|
||
### 5.1 LLMProvider
|
||
|
||
抽象接口(输入:消息 + 图片;输出:文本 + tool_calls),后端:
|
||
|
||
- **云端 BYOK**:Claude / GPT 多模态,用户自带 key(效果优先)。
|
||
- **本地**:llama.cpp 跑 Qwen-VL / LLaVA 类多模态模型(隐私、离线优先)。
|
||
- **自定义 endpoint**(OpenAI 兼容接口):把 provider 指向任何兼容
|
||
`/v1/chat/completions` 的服务(自建代理、企业网关等)——为后续接入
|
||
托管服务预留通用通路,不绑定特定厂商。
|
||
|
||
API key 存**插件自己的配置文件**(如 `~/.oak/plugins/oak-plugin-ai/config.toml`),
|
||
面板提供密钥输入框(`text_input`),用户无需手配环境变量;环境变量只作
|
||
CI/无头场景的覆盖项(优先级:环境变量 > 配置文件)。配置文件权限 0600、
|
||
不进版本库。存插件自己的配置是插件的内部事务,Oak 不感知——铁律 4 约束的
|
||
只是 **Oak 侧**的文件。无 key 时优雅降级:面板仍可用,但只做"工具说明 +
|
||
手动执行",不做对话编排。
|
||
|
||
### 5.2 能力位与确认(协议 §7 的具体化)
|
||
|
||
manifest 声明**最小必要集**:
|
||
|
||
```toml
|
||
capabilities = ["project.read", "media.read", "media.import",
|
||
"timeline.read", "timeline.edit", "node.read", "node.edit",
|
||
"render.frame", "playback", "export", "ui.panel"]
|
||
```
|
||
|
||
双层确认,职责分清:
|
||
|
||
1. **Oak 协议层**(§7.3):`timeline.edit` 等确认类方法默认弹窗
|
||
"插件 oak-plugin-ai 请求:split_clip …"。用户可选"本会话内允许"。
|
||
2. **插件会话层**:Agent 把 LLM 规划出的整段动作先列入 `#pending` 清单,
|
||
用户点"执行"才发事务——这是体验层确认,与协议层弹窗不冲突:
|
||
建议插件引导用户在 Oak 侧对本插件设"本会话允许",确认交互集中在面板内。
|
||
|
||
### 5.3 其他安全约束
|
||
|
||
- **限流遵守**:扫描采样按握手 `limits` 下发的配额规划(默认 8 帧/s、
|
||
短边 ≤1080),收到 `RATE_LIMITED` 按 `retry_after_ms` 退避,不得重试轰炸。
|
||
- **沙箱会话**(可选增强):对破坏性大改,Agent 可先 `project.save` 副本到
|
||
临时路径操作,用户接受后再回真实工程。有了事务 + 整段撤销后,此项降为
|
||
可选,默认不启用。
|
||
- 插件崩溃不丢编辑:已 commit 的事务都在 UndoStack 里,未决事务 Oak 自动
|
||
abort(协议 §6 规则 4)——AI 死在哪都不会留下半截剪辑。
|
||
|
||
## 6. 可测试(与项目风格一致)
|
||
|
||
1. **Mock LLM server**:录制/回放 tool_call 序列与固定回复,Agent loop 在
|
||
CI 无 key 无网络跑通。
|
||
2. **Mock Oak(协议级)**:`oakxp` SDK 自带回放 harness——把协议文档 §14 的
|
||
报文流水当夹具,插件不连真 Oak 也能单测工具适配层与事务编排。
|
||
3. **黄金帧校验**:连真 Oak 的端到端测试复用 render-worker harness
|
||
(真实渲染 ≥2 帧 + 像素非全黑 + 一致性断言),验证"Agent 的编辑确实
|
||
改变了画面"。
|
||
4. **会话回放**:tool_call + 帧哈希落盘日志,可回放复现、可作测试夹具。
|
||
|
||
## 7. 里程碑(对齐插件系统 P1–P6)
|
||
|
||
| 里程碑 | 内容 | 依赖 | 验收 |
|
||
|---|---|---|---|
|
||
| **A1 骨架** | `oakxp` SDK + 握手/心跳/重连;插件注册空面板 | P1、P4 | 杀掉插件 Oak 不崩、面板徽标与重启正常 |
|
||
| **A2 工具适配层** | §3 全表映射 + 事务编排(自动 begin/commit/abort)+ 取帧→PNG 通路 | P2、P3 | Mock Oak 夹具全绿;真 Oak 上"导入→铺轨→切开→删除→加效果"可整段撤销 |
|
||
| **A3 无头闭环** | Agent 编排 + LLMProvider + Mock LLM | A2 | CI 无网络跑通"LLM→工具→取帧→回喂";黄金帧校验过 |
|
||
| **A4 AI 面板** | §4 面板 + 待确认清单 + 撤销会话 | A3 | 真机对话粗剪一段素材,确认/撤销交互完整 |
|
||
| **A5 内容感知** | contact sheet 扫描打点、自动粗剪策略、本地模型 provider | A4 | 对 10 分钟素材自动出粗剪版,人工抽检切点可用 |
|
||
|
||
## 8. 风险与边界(明确不做)
|
||
|
||
- **不**把 LLM/推理放进任何 Oak 进程或引擎模块(引擎对 AI 无感知);
|
||
MCP server 如需存在,只在插件进程内。
|
||
- **不**绕过 OPP/1 访问 Oak(协议是唯一边界);LLM 不直接接触 `txn`
|
||
令牌(适配层封装事务)。
|
||
- **不**把 API key 写入 Oak 工程文件或 Oak 自身配置(插件自己的配置文件
|
||
除外,见 §5.1)。
|
||
- **不**让取帧回路阻塞 Oak GUI 线程——`render.*` 本来就走引擎 ticket/进程池
|
||
路径(协议 §8.6),插件侧并发流水线化而不是串行等帧。
|
||
- **不**突破协议限流;需要更高帧率的"连续回放分析"场景,先按 §13 版本
|
||
演进规则给协议加配额项,不在插件侧硬挤。
|
||
- 第三方大模型客户端的接入细节(OAuth、计费、配额)**超出本文范围**,
|
||
按需另立文档。
|