OpenMAIC 专业课程编辑指南:用 pro-editing 技能对外部 Agent 的既有课件做外科手术式精修
2026/9/11 14:08:22 网站建设 项目流程

OpenMAIC 专业课程编辑指南:用 pro-editing 技能对外部 Agent 的既有课件做外科手术式精修

【免费下载链接】OpenMAICOpen Multi-Agent Interactive Classroom — Get an immersive, multi-agent learning experience in just one click项目地址: https://gitcode.com/GitHub_Trending/op/OpenMAIC

本指南讲解 OpenMAIC(Open Multi-Agent Interactive Classroom)中pro-editingAgent 技能(skills/agent-runtime/pro-editing/SKILL.md)的完整用法:它面向已经存在并被持久化(persisted)的课程,为 Agent 提供一套"先盘点、再精读、后最小修改、最后一致性校验"的专业编辑工作流。读完本文,你将掌握list_scenes/read_stage/patch_stage/edit_deck/generate_tts/render_scene_preview这组工具的调用纪律,理解 JSON Pointer 寻址规则与身份不变约束,并能借助slide-dslslide-craft两个配套技能,把"改一个词、修一处重叠、重排一页"这类请求做成不会留下编辑痕迹的高质量修改。

技能定位:编辑一门已存在的课程,而不是重新生成它

pro-editing的元规则写在技能描述里:它只处理已持久化的课程——页面已经存在于存储中,会话挂接在一个既有课程上,或课堂已被构建。它的全部工作方式是"surgical"(外科手术式):读出现有内容、只改被要求的部分、其余一律不动

这决定了它与仓库中其他技能的分工:

  • pro-editing决定"改哪一页、用什么操作、如何校验"(编辑流程);
  • skills/agent-runtime/slide-dsl/SKILL.md 是页面 JSON 的字段参考手册——告诉 Agent 每个字段叫什么、合法值是什么、渲染器实际读取哪个字段;
  • skills/agent-runtime/slide-craft/SKILL.md 是设计法则——画布几何、字号高度表、类型层级、对比度与间距标准,即"什么样才算改得好";
  • stage-designcurriculum-planner仍在构建阶段负责页面生成与课程大纲规划,不参与编辑。

一个值得注意的实现细节:pro-editing目录下的 outline-constraints.json刻意不携带任何约束字段。原因写在文件的$comment中:pro-editing 的编辑回路只读取和修补已持久化页面,不运行大纲生成,因此没有大纲结构需要约束检查器强制,任何"结构性下限"反而会错误地约束用户并未要求的重建。该文件只是这一决策的书面记录。从源码看,约束文件是可选的兄弟文件,lib/server/agent-runtime/skills.ts 在加载技能时若发现同目录存在outline-constraints.json才会读取并注入大纲生成器的teacherContext槽位。

第一步:进入课程之前,先盘点全局

技能规定:任何编辑动作之前,必须先调用list_scenes对整个课程做一次盘点。这一步得到的不是假设,而是编辑所依据的唯一地图:

  1. 列出每个已持久化页面的完整清单:页码、顺序、类型;
  2. 不要规划替换性 stage——课程已有结构,你不是在重新规划它;
  3. 如果请求很宽泛(如"让它更好看"),把它转化为针对页面清单的具体计划,并在动手前说明将触及哪些页面。

从实现上看,list_scenes是基础课程工具之一(见 lib/server/agent-runtime/generation-tools.ts),它的提示语明确要求"每页使用一次 generate_scene,每次成功调用都是一个持久化检查点";edit_deck则负责retitle/insert/delete/reorder这类整页级操作(lib/server/agent-runtime/course-edit/tools.ts)。盘点页数、顺序、标题正是这一层的职责。

第二步:每次修改前,用 read_stage 精读目标页

read_stagepath:/scenes/<order|id>定位页面,紧邻每次编辑之前调用。返回的清单——元素 id、题目 id、动作 id、widget 配置——是你唯一可以编辑的地址空间,同时也揭示页面的真实类型。过期的 id 会导致失败的编辑;对页面内容的过期假设会导致错误的编辑。如果一次编辑落在了错误的页面类型上,这是"重新读取"的信号,而不是"强行执行操作"的信号。

read_stage提供三档深度(detail参数),三者不可互换:

detail返回内容适用场景
tree(默认)每个元素一行紧凑信息:idtype、纯文本textlefttopwidthheightsrc定位元素。绝不能作为补丁来源——它剥离了样式
source页面精确持久化 JSON——所有样式字段、带内联标记与换行的原始 content HTML、精确几何、z 序每次补丁的根。写入后再读一次验证
text每个含文本元素以{ path, id, type, text }输出,外加整页combinedText证明旧文案没有残留

关键规则:幻灯片写入必须用detail:"source"tree只是紧凑地图,足够找到元素,却永远不够编辑它。source返回整页 JSON(含typeschemaVersion与完整 canvas),因此数组索引不会在读取与写入之间漂移——你在/content/canvas/elements/2/left读到的字段,就通过同一路径写回。text档则是残留检查的有力工具:每个path指向该元素自己的/content/canvas/elements/N,检查结果直接交给你要修补的指针;注意latex元素返回的是 LaTeX 源码、code是换行连接的代码行、table|连接的单元格。

从源码看,read_stage(lib/server/agent-runtime/dsl-tools.ts)对source档会省略大块内联媒体字节(omitReadSceneMediaBytes),避免超大字符串阻塞事件循环;sourcetext档在超过 12000 字符后分页,通过nextOffset继续读取。

第三步:选择最小的操作——patch_stage 操作矩阵

一次幻灯片编辑不外乎三种操作中的一种,而第一种就是针对detail:"source"刚返回内容的单次 JSON Pointer 写入。技能的决策矩阵如下:

用户意图工具操作
修复或改写幻灯片上的文字patch_stage对该元素内容路径执行setstr_replace——/content/canvas/elements/N/content…/text/content…/data/0/0/text…/lines/1/content…/latex
移动 / 缩放 / 旋转元素、修复重叠patch_stage/content/canvas/elements/N/left(或top/width/height/rotate)执行set,每次操作一个数字
重新着色或重设元素样式patch_stage对渲染器持有的样式路径执行set——…/defaultColor…/fill…/text/defaultColor…/color
删除可选字段patch_stage对该路径执行remove
替换图片或媒体源patch_stage/content/canvas/elements/N/src执行set
重排幻灯片元素层级patch_stage对整个/content/canvas/elements数组执行set——整体重排,id 与类型保持一致
新增或删除幻灯片元素patch_stageadd_element(完整 JSON,无id)/delete_element
修复测验题、选项、答案、评分patch_stage/content/questions/...执行set/remove;增删需重写整个数组
修改交互页patch_stage/content/widgetConfig/.../content/html执行set/remove/str_replace
改写 / 插入 / 删除 / 重排旁白patch_stage指向/actions/...的 scene 根级指针;插入/重排需重写完整数组
编辑 PBL 简报、角色、里程碑、微任务patch_stage/content/projectV2/...执行set/remove
重命名 / 插入 / 删除 / 重排整页edit_deckretitle/insert/delete/reorder
从零重写一页generate_scene+instruction仅限用户明确要求整页重写时

矩阵背后的硬性规则(原文档的"Rules of the matrix"):

  • 一个意图 → 一个操作。不要捆绑你无法逐一命名的变更。
  • 寻址叶子。隔离你的变更所需的最小路径才是正确的路径;把整个对象写回去,正是相邻样式字段消失的原因。
  • patch_stage无法改变身份——不能改 canvas id、不能改元素 id 集合、不能改元素的type。这些必须走add_element/delete_element;类型变更等于"删除+新增"。内容本身不受检查——它按原样存储,所以slide-dsl是挡在你写的标记与坏页面之间的唯一防线。
  • 被拒绝的补丁什么都没改。坏路径、越界索引、未知字段、错误类型或非法结果页,都会带着原样页面响亮地失败。重新读取、重新提交,绝不硬来。
  • add_element接收一个完整的元素 JSON 但不含id——服务端校验与补丁相同的结构契约,分配 id,并按afterIdindex(二选一)插入。
  • 没有任何东西会规范化你的值。颜色、字体、主题都不会被重写成"家风格",所以看起来不对的编辑是你的值的问题,不是工具的问题。
  • edit_deck insert创建空桩;用generate_scene配合该新页的显式typebrief填充它,或修补一个已合法的 scene。
  • generate_sceneinstruction会丢弃整页并重新生成。这是重写,不是编辑:留待用户明确要求重建页面时使用,绝不可作为绕过精细编辑的捷径。
  • 如果会话已附加资料或网络访问权限,用它们来夯实编辑内容——绝不是重建无人要求的页面的借口

源码级印证:patch_stage 的原子批处理

lib/server/agent-runtime/dsl-tools.ts 中patch_stage的实现揭示了文档背后的真实机制:

  • 原子性地修补/scenes/<order|sceneId>下的单个 sceneops是操作数组,任意一个操作失败(applyPatchOp返回ok: false)都会拒绝整个批次并报告失败的操作序号;
  • 幻灯片路径的set/remove/str_replace最终被归一化为{ op: 'patch', action, path, value }交给applySlideEdit,其中path剥去/content前缀后才是/canvas/...部分——这与文档中"从read_stage detail:"source"返回的精确 scene 根寻址"一致;
  • slideOperation会拒绝以/actions/...之外的路径冒充幻灯片指针的调用,并明确报错slide content pointer must start /content/canvas/
  • 一批补丁提交前还有最终态占位符守卫:逐操作检查是隔离的,模型可以用多个str_replace各携带一个片段,拼凑出一个完整的只读媒体占位符从而绕过逐操作检查;因此整个批次在序列化最终 scene 后会重新整体校验,命中即整体拒绝、绝不落盘(绝不进入putScene)。

寻址规则速查

path是锚定在read_stage detail:"source"返回的精确 scene 上的 JSON Pointer,幻灯片字段以/content/canvas/开头:

  • 寻址叶子而非分支:给出隔离变更所需的最小路径。把整个对象写回,相邻样式字段会被悄悄丢弃。
  • 数组索引必须规范且有界012合法;03-1+1和越界索引一律被拒。
  • 最后一段之前的每一段都必须已存在;最后一段可以不存在——set一个对象尚无的键会新增该可选字段(如给文本元素加fill),remove则删除它;对不存在的路径remove会失败。
  • 路径不得穿越标量/content/canvas/elements/0/content/0会失败,因为content是字符串而非容器。
  • 对数组索引remove会缩短数组;除整数组重写外,没有"按索引插入"。
  • ~1~0转义键内的/~;裸~~2被拒。
  • 值在进入时深拷贝,你发送的嵌套对象会作为自己的树存储。

工作中的示例(每次调用一个):

变更pathvalue
标题的富文本/content/canvas/elements/0/content<p><span style="color:#00a870">新标题</span></p>
单个表格单元格/content/canvas/elements/5/data/0/0/text"净利润"
一行代码/content/canvas/elements/9/lines/1/content"total = price * count"
形状标签/content/canvas/elements/3/text/content<p>第二阶段</p>
字形默认色/content/canvas/elements/2/defaultColor"#1f4e79"
单个图表标签/content/canvas/elements/6/data/labels/2"Q3"
元素位置/content/canvas/elements/2/left120
页面背景/content/canvas/background/color"#f7f7f5"
删除可选字段/content/canvas/elements/4/shadowop: 'remove'
层级重排/content/canvas/elements整个数组,重排后返回

z 序没有叶子可寻址,因为绘制顺序就是数组位置:索引 0 最先绘制(底层),最后绘制顶层;渲染器直接以数组索引分配 CSSz-index。要重排层级,就整体重写/content/canvas/elements——id 集合与每个 id→type 配对必须原样返回。这就是"唯一的没有叶子可寻址的变更"。

最小编辑纪律(Minimum-edit discipline)

  • 只改被要求的部分。不重设未触碰元素的样式,不改写没人抱怨的旁白,不在修一页的同时"顺手改进"相邻页面。
  • 保持课程自身的声音:匹配其既有术语、语调和视觉语言。一次编辑不应被看出来是编辑。
  • 只要目标比整页更窄,优先用细粒度的逐字段patch_stage,而不是generate_scene
  • 任何涉及几何、颜色、文本长度或富文本结构的幻灯片编辑,都要加载slide-craft——它承载了页面被绘制时所依据的设计法则:文本高度表、类型层级、对比度配对、间距节奏,以及哪个字段在哪个元素类型上真正到达屏幕。
  • 当你需要字段本身——它的名字、合法值、寻址它的路径、渲染器读取两个字段中的哪一个——那就是slide-dsl

slide-craft 的关键设计法则(编辑时遵守)

skills/agent-runtime/slide-craft/SKILL.md 定义了页面设计约束,编辑时这些数字是"修复"与"凹陷"的分界线:

  • 画布 1000 × 562.5 px,所有元素遵守50px 边距left ∈ [50, 950]top ∈ [50, 512.5],右边缘left + width ≤ 950、下边缘 ≤ 512.5;
  • 文本按表定高,不靠肉眼:文本元素四周有 10px 内边距,可用区为(width - 20) × (height - 20),高度来自字号表(line-height 1.5、含内边距)。例如 16px 字号:1 行 46、2 行 70、3 行 94、4 行 118、5 行 142;24px:58 / 94 / 130 / 166 / 202;32px:70 / 118 / 166 / 214 / 262。替换文字后重新推导高度characters_per_line = (width - 20) / font_size,最长行保持在≤ 75%,按内容中最大字号查表;
  • 高度是容器,不是夹子:溢出文本会溢出盒子而非收缩,页面会展示溢出。修复顺序是先缩短文字、再跳到下一档表行、最后加宽盒子
  • 对比度是一对,不是一个颜色:正文 ≥ 4.5:1,24px 及以上标题 ≥ 3:1。#333333配白约 12:1 安全;强调色#5b9bd5配白约 3:1,只够 32px 标题。稳妥模式是"浅色调填充 + 同色相深色文字":#1e40af#dbeafe#166534#dcfce7#92400e#fef3c7,各约 6–7:1;
  • 形状上的文字是一个对象text.width = shape.width - 40text.left = shape.left + (shape.width - text.width) / 2text.top = shape.top + (shape.height - text.height) / 2,中心点应相差 ≤ 2px——移动形状永远是"移动形状 + 重推导标签"两步;
  • 平行元素共享完全相同的数字:一行三张卡片共用一个widthheighttop和间距;标题→副标题 30–40px、标题→正文 35–50px、段落块之间 20–30px、多栏间距 40–60px、被箭头跨越的间距需要 60–80px;
  • 派生元素跟随锚点:标题下划线、分隔线、高亮条都是依据被装饰文本计算的形状,文本移动后必须重算;
  • 内容类型与元素类型一一对应:任何数学都是latex元素;表格单元格是纯文本;代码元素取纯文本行(默认 14px,高度要覆盖 32px 头部加每行);linewidth是描边粗细而非长度;图片保持宽高比,改width/height要一起改。

Look → edit → look:预览预算

对于布局敏感的变更(位置、密度、新元素、对齐),在render_scene_preview可用时用它验证:看渲染、编辑、再看一次。每页最多两轮预览——每次渲染都耗费一次工具调用,而整个运行有硬性的调用上限。不要预览未触碰的页面,不要超过预算:两轮未收敛就停下来,告诉用户剩余事项,而不是把整次运行烧在一页上。

从实现看,render_scene_preview(lib/server/agent-runtime/scene-preview.ts)是一个按能力注册的独立工具,import-pptx的提示语也建议导入后用它检查每个页面。

旁白音频跟随文本:generate_tts

patch_stage修改语音动作的/actions/N/text清除该行改写后的音频,而插入的行天然没有音频。因此任何语音措辞变更后,都必须在进入下一页前对该页调用generate_tts——它的默认模式只合成缺失音频的行,正好是所需行为。改写文本却不重新生成音频,交付的是一页静音。

源码印证(lib/server/agent-runtime/course-edit/tools.ts):generate_tts默认force: false,只补齐没有音频的语音动作;force: true才强制重合成所有语音音频。成功变更会落盘并打持久化检查点,返回Narration audio: X generated, Y skipped, Z failed.的汇总。另外,dsl-tools.tsstr_replace与非幻灯片指针的set/remove路径在写入后会调用clearStaleSpeechAudio——这是源码层面"音频跟随文本"的自动保障:文本一改,对应音频即被判定过期。

收尾:一致性检查(Consistency pass)

在告诉用户"完成"之前,必须过一遍:

  1. list_scenes——确认页面清单与意图一致:数量、顺序、标题;
  2. 每个编辑结果已经携带了该页的最新清单;对照验证,仅当结果留下疑问时才用read_stage重读;
  3. 扫一遍你触碰过的页面,检查你的编辑可能引入的跨页漂移:同一概念命名是否一致、单位与术语是否统一、你引入的样式是否应用于所有该用的地方;
  4. 被触碰页面上每个语音动作都必须有audioIdread_stage会显示);任何缺音频的页面用generate_tts补齐。

与人类编辑共享课程:last-write-wins 协作纪律

保存按页采用最后写入者胜出(last-write-wins),而同时在浏览器里编辑的人类作者可能覆盖你,就像你覆盖他们一样。把课程当作共享土地:

  • 先读新,再写。紧邻编辑前的read_stage不是形式——它正是避免覆盖用户自你上次查看以来所做更改的方式;
  • 小而增量地写。每次工具调用在落地的瞬间自动持久化其变更;一小串小写入相比一次大重写,冲突损失更小。绝不要积攒"已准备但未写入"的批次——边写边走
  • 用户正在编辑时让位。如果用户说他们正在改同一页,或一次新读取显示了你没写过的内容,停下来、重读、先协调再写。永远不要用你的陈旧快照覆盖人类更新的版本

这与patch_stage实现中"每次操作前structuredClone当前 scene、逐操作应用、整体校验后落盘"的原子语义相呼应:拒绝的批次不产生任何部分写入。

预算管理:把调用花在编辑上,而不是仪式上

运行时没有硬性的工具调用上限,但每一次额外调用都是用户等待的延迟。把调用花在编辑本身:入口一次list_scenes、每页编辑前一次read_stage、编辑本身、措辞变化处generate_tts、仅在布局真正有风险时的一轮预览。如果请求很大,先做最高价值的编辑,并告诉用户剩余事项。

为什么这样编辑是安全的:结构校验的唯一边界

pro-editing之所以能大胆做最小编辑,是因为写入路径的守卫只有一个:DSL 结构 schema。写入的内容按字节存储,没有标记允许列表、没有清理器、没有颜色或字体规范化、没有几何钳制、没有家风格。patch_stage只检查一件事——结果文档仍符合 DSL 结构契约(字段名、类型、必填字段、封闭对象)——然后逐字节持久化你的值。

因此文档强调(skills/agent-runtime/slide-dsl/SKILL.md):

  • 拒绝清单是穷尽的:非/content/canvas/开头的幻灯片路径、畸形~转义、非规范或越界数组索引、缺失中间段或穿越非容器的路径、对不存在键的remove、缺valueset或带valueremove,以及身份类拒绝(改/content/canvas/id、增删改重复元素 id、改 id 的typeadd_element带 id 或同时带afterIdindex),还有 schema 类拒绝(未知字段、错误类型、封闭联合外的值、移除必填字段、错误元数元组、错误类型上的字段);
  • 但不在清单上的更多:没有标签检查、没有 CSS 检查、没有颜色格式检查、没有长度限制、没有几何边界、没有对比度规则、没有htmllatex一致性检查、没有图表serieslabels匹配检查——这些都靠你(和slide-craft)来保证;
  • 服务端唯一自动重写:当补丁改变元素的latex时,服务端从新源码重渲染该元素的html快照(KaTeX,display 模式,错误渲染而不抛出)并存储;若渲染无结果则移除html。除此之外文档的任何部分都不会被触碰。

这也是"被拒绝的补丁什么都没改"的完整含义:错误是信息,不是损坏——每条检查都在任何内容存储之前运行,错误消息会点名违规路径。

快速参考:编辑流程全景

阶段工具调用目的
入口list_scenes盘点整门课:页数、顺序、类型,制定编辑计划
每次编辑前read_stage path:/scenes/<order|id> detail:"source"拿精确持久化 JSON,作为补丁路径的唯一地址空间
编辑patch_stageset/remove/str_replace/add_element/delete_element)或edit_deckretitle/insert/delete/reorder最小操作、寻址叶子、不碰身份
布局验证render_scene_preview每页至多两轮,只对有布局风险的页面
音频generate_tts每次语音措辞变更后补齐缺失音频
收尾list_scenes+ 逐页核对 + 跨页一致性扫描数量/顺序/标题正确、术语统一、无静音页

配合技能三件套:字段含义查 slide-dsl,设计标准查 slide-craft,编辑流程遵循本文的pro-editing。整条链路在 lib/server/agent-runtime/dsl-tools.ts 与 lib/server/agent-runtime/course-edit/tools.ts 中均有完整的原子实现支撑。

【免费下载链接】OpenMAICOpen Multi-Agent Interactive Classroom — Get an immersive, multi-agent learning experience in just one click项目地址: https://gitcode.com/GitHub_Trending/op/OpenMAIC

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询