- docs/zh/plans: finished plans move to completed/ (the RIIR series, the event-bridge and dependency plans, the v04 manual test plan). - New design docs: the external (functional) plugin system (process-isolated, JSON-RPC/shm) and its protocol. - ai-agent-design refreshed; README pointers follow the moves.
15 KiB
AI Agent 插件设计(基于 OPP/1 外部插件系统)
本文是 Oak 引入 AI 能力的长期设计,已按外部插件系统重写(旧版假设 RIIR 拆分后面对一堆 C ABI 小库、引擎内置
oak-mcp-server,已作废)。 AI 能力现在是一个外部功能插件:独立进程、经 OPP/1 协议操作 Oak。依赖文档(冲突时以它们为准):
external-plugin-system.md——插件系统总体设计 (进程模型、能力位、确认模式、里程碑 P1–P6)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 设计铁律(继承旧版,按插件系统重述)
- AI Agent 只经 OPP/1 访问 Oak——插件进程内一行 Oak 代码都没有, 不链接任何 Oak 产物(铁律:协议是唯一边界)。
- 一切编辑动作必须包在
edit.begin/commit事务里(协议 §6), 历史面板一次 Ctrl-Z 整段撤销;UI 默认"确认后执行"。 - 不为 AI 发明新的引擎内部机制;Agent 工具面是 OPP/1 方法的组合,
OPP/1 方法又是现有
graphops/renderops/oak_task的组合。 - 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 的理由:
- 性能无关:插件只做协议编解码、LLM 编排、PNG 编码(Pillow 为 C 实现); 延迟大头是 LLM 往返(秒级)与 Oak 侧渲染(与插件语言无关),插件进程 没有任何重计算。
- 跨宿主复用:同一套 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 进程通信。
- 生态:主流 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 声明最小必要集:
capabilities = ["project.read", "media.read", "media.import",
"timeline.read", "timeline.edit", "node.read", "node.edit",
"render.frame", "playback", "export", "ui.panel"]
双层确认,职责分清:
- Oak 协议层(§7.3):
timeline.edit等确认类方法默认弹窗 "插件 oak-plugin-ai 请求:split_clip …"。用户可选"本会话内允许"。 - 插件会话层: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. 可测试(与项目风格一致)
- Mock LLM server:录制/回放 tool_call 序列与固定回复,Agent loop 在 CI 无 key 无网络跑通。
- Mock Oak(协议级):
oakxpSDK 自带回放 harness——把协议文档 §14 的 报文流水当夹具,插件不连真 Oak 也能单测工具适配层与事务编排。 - 黄金帧校验:连真 Oak 的端到端测试复用 render-worker harness (真实渲染 ≥2 帧 + 像素非全黑 + 一致性断言),验证"Agent 的编辑确实 改变了画面"。
- 会话回放: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、计费、配额)超出本文范围, 按需另立文档。