runner.py 如何在每个 Phase 完成后自动同步资产 + 结构化参数到无限画布
cfg.on_phase_complete(episode_id, phase_id, result)load_canvas() → normalize_loaded_graph() — 加载当前画布所有节点+边,确保结构合法
ensure_zone_node(graph, "p03") + ensure_zone_chain(graph) — 每个 phase 一个椭圆,按 P01→P02→…→P14 链式排列
_build_phase_node() → upsert_node(graph, "n-p03_script_audit", data) — 摘要节点放在 zone 内,包含描述/分数/审核状态
_extract_artifacts() — 根据 _PHASE_OUTPUT_MAP 从 result["outputs"] 中提取每个资产项,透传全部字段
_build_artifact_node() → _apply_schema_mappings() — snake_case→camelCase、中文 enum→英文 enum、派生字段
_add_reference_links() — 如果资产标记了 consumes: ["p07/S01_front"],自动画一条上游依赖箭头
sanitize_v2_graph(graph) — 清理所有字段确保通过 v2 zod schema → save_canvas(graph) → POST /api/canvas/v2/save-v2
_PHASE_OUTPUT_MAP + _PHASE_TYPE_MAP 定义了每个 Phase 的哪些 output key 会变成画布节点,以及节点的 canvas type
| Phase | Output Key | 节点标签 | Canvas Type | 展开子节点? |
|---|---|---|---|---|
| P01 | topic_kernel | 选题核 | script | ✅ |
| hook_design | 钩子设计 | script | ✅ | |
| retention_engineering | 留存工程 | script | — | |
| P03 | script | 剧本 | script | ✅ |
| audit_report | 审计报告 | script | ✅ | |
| P04 | character_bible | 角色设定集 | asset | ✅ |
| character_assets | 角色资产 | asset | ✅ | |
| P07 | scene_images / images | 场景图 | asset | — |
| scene_multi_angle | 场景多角度 | asset | — | |
| style_vector | 风格设定 | script | — | |
| color_intent | 色彩意图 | script | — | |
| P09 | shot_list | 分镜列表 | storyboard | — |
| P10 | voice_clips | 语音片段 | audio | — |
| P11 | video_clips | 视频片段 | video | — |
_apply_schema_mappings() (canvas_sync.py L299-459) 负责把 Phase 产出的原始字段翻译成画布前端 canvasAssetSchema.ts 能识别的字段
编排器。每个 phase 完成后调用 on_phase_complete(episode_id, phase_id, result)。如果 CanvasClientError(HTTP 4xx)→ 停管线(Kai 原则:不掩盖问题)
同步引擎。7 步流程:load → zone → summary → artifacts → field mapping → reference links → sanitize + save
图操作。zone 位置计算、节点 upsert、边创建、v2 schema sanitize、V4 语义布局(characterId/sceneId 分组)
HTTP 客户端。load_canvas() POST load-v2 → save_canvas() POST save-v2。处理 project_id + episodes_id
资产清单。每个 phase 写 manifest.json 到 OSS 目录,记录 nodes + edges + params。文档即画布 — manifest IS the topology
自动连线。当 sync_canvas=None 时从环境变量 KAIS_CANVAS_URL + KAIS_CANVAS_PROJECT_ID 自动构建 sync 函数
以 camera_movement: "缓慢推进" 为例
{"shot_id": "S01-01", "camera_movement": "缓慢推进", "framing": "中景", "duration_sec": 3.5, ...}
p09_shot_breakdown.py 把 delegate_task 返回的 JSON 解析后写入 asset_bus["shot_list"]
关键设计(L1838-1858):不是白名单提取,而是透传 artifact dict 的所有字段。camera_movement、shot_type、duration_sec 等全部保留
再次透传全部字段到 data dict(L2107-2111),然后调 _apply_schema_mappings()
P09 分支:_SNAKE_CAMEL 映射表把 camera_movement → 查 _CAMERA_MOVEMENT_MAP["缓慢推进"] → data["cameraMovement"] = "zoom_in"
确保所有字段值符合 canvas v2 zod schema(枚举值合法、必填字段存在、类型正确)
整个 graph 序列化为 JSON POST 到画布后端。前端 React Flow 读取 data.cameraMovement 显示在 StructuredFieldPanel
记录该 phase 的全部资产节点 + 拓扑边。每个 node 携带 params 结构化参数和 consumes 上游依赖。canvas_sync 可直接读 manifest 构建画布
每次写文件操作追加一行日志,记录 path, phase, engine, input_refs, hash。用于审计追溯——哪个 phase 用什么引擎生成了什么文件
双层设计:manifest.json 是结构化拓扑(给画布消费),asset-manifest.jsonl 是操作日志(给审计消费)。两者独立,互不依赖。
| 路径 | 触发方式 | 同步入口 | 适用场景 |
|---|---|---|---|
| CLI 路径 | python -m pipeline.runner --canvas-url ... --canvas-project-id N |
register_canvas_sync() → cfg.on_phase_complete |
正式管线运行(runner.py 全自动) |
| Agent 路径 | Hermes agent 手动执行 phase / ad-hoc 调用 | sync_phase_result() 或 auto_canvas_sync()(env var 触发) |
调试、重跑单个 phase、agent 驱动执行 |
设置 KAIS_CANVAS_URL + KAIS_CANVAS_PROJECT_ID + KAIS_CANVAS_EPISODES_ID 后,即使 agent 手动调 phase(不走 runner.py),auto_canvas_sync() 也会自动构建同步函数并执行。无需在代码中显式传入 sync_canvas。
| 错误类型 | 行为 | 原因 |
|---|---|---|
| CanvasClientError HTTP 4xx(数据有问题) |
停管线 | Kai 原则:同步失败就停,不掩盖问题。通常是 zod schema 校验失败(缺 shot_type/duration_sec 等) |
| ConnectError / Timeout 网络层错误 |
降级容忍(不阻塞管线) | 画布不可达不应阻断视频生产。错误记录到 sync-errors.jsonl |
| write_manifest ValueError 缺必填 params |
停管线 | _validate_node_params() 检查每个 node type 的必填字段。video 必须有 shot_id/engine/duration_sec/resolution |
核心源码: canvas_sync.py (2696L) · canvas_graph.py · canvas.py · _manifest.py (272L) · _canvas_helper.py (40L)
HTTP API: POST /api/canvas/v2/load-v2 · POST /api/canvas/v2/save-v2
前端 schema: canvasAssetSchema.ts (zod) · StructuredFieldPanel.tsx