先说结论,我个人认为,AI-Native 的编辑器,不在于形式,无论是左编辑器右输入框,还是选中后弹出 AI 润色、续写框,又或是正文下方放类似对话形式的输入框,本质并没有任何不同。在 Tiptap 体系下,只要不是通过命令原生操作,我认为就全都不算 Native。个人认为真正的 Native,是模型能原生操作编辑器,不是先生成 markdown 再通过兼容层转为编辑器内容,也不是将 markdown 转为兼容的 HTML 再通过兼容层转为编辑器内容,并且你不需要为每个功能都为模型编写提示词,你只需要一种机制,让模型能够知道编辑器有哪些能力,并且知道这些能力的参数,调用方式就够了,至于如何调用,那就得看模型的调度能力到底几何了。
下面是 QuireKit Classic 里的一次真实运行:一句话让 AI 把第一行设为标题并居中、改写第二段、把百分比和日期加粗标红,再在文末插入表格。修改先以修订呈现,最后一次批准。演示用的模型是 DeepSeek Flash,后面两段也一样。
具体的实现方案来自一个之前做的 CLI
前段时间想要把公司后端的所有接口包装成一套对 AI 友好的 CLI,让 Codex、Claude Code、DSH、WorkBuddy 等任何 Agent 都能自己学会使用我们的产品。接口有上百个,版本有几十条标准包、几千家定制包,给每个 Agent 写提示词显然不现实,所以我设计了一套自描述的方案,Agent 只需要会三个命令:
cli api list --target test --json # 这个环境能用哪些能力
cli api explain <id> --target test --json # 能力的参数 Schema、风险、认证方式
cli api call <id> --target test --input @request.json --json
有哪些接口、参数如何填写,都由 Agent 在运行时向 CLI 询问。这样一来,更换 Agent 不需要做任何修改,新增接口或升级版本之后,Agent 下一次 list 时自然就能知道。
做 QuireKit 编辑器的 AI 时,面对的其实是同一个问题,只不过对象从成百上千的后端接口换成了成百上千的编辑器命令。
第一版设计的 AI 输出正确性评测只有 48%
我最早的做法,是每个插件分别给 AI 注册单独的工具,各写各的说明和提示词。九月底用 20 个日常任务做评测,例如“标题居中”“行距 1.5 倍”“文末加个三列的表”,第一次通过率只有 48.3%。让 AI 分析失败记录后发现,问题基本不在模型:有的能力压根没开放,有的写入失败却回了成功,表格和页眉各有一套定位方式,模型来回尝试,直到轮次耗光,最终以失败结束。
后来我推翻了让插件各自给 AI 注册工具的方案。仔细想想,这不就跟之前做的 CLI 一样吗?为什么不能让编辑器自己描述自己的能力?于是改成了自描述的方案,定位方式也统一成了一种。同一个模型、同一批任务,通过率到了 98.3%,平均每个任务 1.1 次工具调用,剩下那一次失败也是因为题目本身有歧义。到这里,我感觉应该是对了。下面是重做时改的几件事:
插件命令自描述的改造
模型能看到的工具改为固定的几个:读文档、改文档、查帮助。工具叫什么、参数长什么样、出错怎么提示,都由 AI 核心统一定。插件不再专门给模型写工具和提示词来告诉它怎么用,而是只描述自己的情况:节点在视图里怎么显示,有哪些属性能改、取值范围是什么、用哪个命令改。所有开放给 AI 的命令,在定义时都会用 defineCommand 包一层描述:用途说明、参数的 JSON Schema,以及作用在哪个位置。
开放给 AI 的命令,插件在 addCommands() 里用 defineCommand 声明。比如书签插件的“加书签”命令:
addWordBookmark: defineCommand({
// 给模型看的用途说明
description: "Adds a bookmark named `name` on `text` in the block (the whole block when `text` is left out), replacing a bookmark of that name. A link to \"#name\" jumps to it.",
// 作用位置:块里的一段文字
target: "text",
// 参数的 JSON Schema
params: { type: "object", required: ["name"], properties: { name: visibleNameSchema }, additionalProperties: false },
// 真正执行的命令,和工具栏调用的是同一个
run: createAddWordBookmarkCommand(startTypeName, endTypeName)
}),
defineCommand 本身只是一层带类型的包装:它接收一个描述对象,返回的仍然是一个命令函数,调用时原样转给 run;描述被冻结后挂在这个函数的隐藏属性上。Classic 用的核心包里,实现就这几行(MD Editor 用的 MSFE 也有同名的 defineCommand,区别是它直接返回 run 本身,把描述登记在一张以命令函数为键的表里):
const descriptorKey = Symbol.for("quirekit.command.descriptor");
export function defineCommand(definition) {
const { run, ...descriptor } = definition;
// 返回新的函数,调用时转给 run,对编辑器来说它就是普通命令
const command = function (...args) { return run.apply(this, args); };
// 描述挂在不可枚举的 Symbol 属性上
Object.defineProperty(command, descriptorKey, { value: Object.freeze({ ...descriptor }), enumerable: false });
return command;
}
包装之后,插件拿到的仍然是原来的命令函数,工具栏和快捷键照常调用;描述挂在函数的隐藏属性上,AI 核心再从所有命令里把带描述的提取出来,汇总成一张表:
除了命令,插件还用 addAiCapabilities() 声明格式属性。比如段落对齐,完整声明如下:
{
// 能力种类:格式属性
kind: "format",
// 模型在文档视图和编辑工具里看到的属性名
name: "align",
// 作用在段落上,而不是文字上
level: "paragraph",
// 给模型看的含义和取值说明
description: "Alignment: \"start\", \"center\", \"end\", \"justify\" or \"distribute\".",
// 合法取值,编辑工具按它校验参数
schema: { enum: ["start", "center", "end", "justify", "distribute"] },
// 读:从段落的 Word 属性里取当前对齐方式,没设置时返回 undefined,表示沿用样式
read: target => target.level === "paragraph" && target.node.attrs.wordParagraph !== undefined
? resolveWordParagraphFormatting(target.node.attrs.wordParagraph).alignment ?? undefined
: undefined,
// 写:调用段落插件自己的命令,和用户点工具栏上的“居中”是同一个命令
apply: ({ commands }, value) => commands.setWordParagraphProperties({ properties: { alignment: value } }),
// 审阅修改时给人看的名称和取值,不给模型看
present: {
label: "对齐",
value: value => ({ start: "左对齐", center: "居中", end: "右对齐", justify: "两端对齐", distribute: "分散对齐" })[value] ?? String(value)
}
}
加一个插件,"改文档"这个工具就多认识一种属性,即多一种操作能力,但模型所会的工具数量不变,已经学会的工具用法也不变。这就是开头说的那种机制:不为每个功能写提示词,只让模型知道有哪些能力、参数是什么、怎么调用。
比如下面这份简历排版:页边距、字号、加粗、颜色、字距、段落下边框、项目符号、行距和段前段后间距,都是各插件声明的能力。AI 先读取了内置的简历技能,再用一次编辑调用全部改好,最后一次批准。

写到这里应该能看出来,这跟前面那个 CLI 是一个路子。document_read 相当于 list,document_help 相当于 explain,document_edit 相当于 call。参数写错了,报错会直接说是哪个字段。我们自己的 AI 助手和第三方自己写的 Agent 用的也是同一套工具,没有给自己留后门。
确保模型的修改和人工修改走同一套底层命令
通常情况下,大多数编辑器 AI 生成内容是生成一段 markdown 或 HTML,再经过兼容层转成编辑器内容替换选区。而由于 markdown 和 HTML 自身的限制,在复杂文档中本就不能完美存储所有有效信息,因此转换必然丢失一部分信息。在 Prosemirror 体系下,HTML 存储 Attrs 还会依赖各种标签属性,当存在大量属性时,生成复杂文档可能还存在出错的问题。
但是在 QuireKit 里,AI 的每次写入,走的都是跟用户点击工具栏时完全相同的一套底层命令。一次调用里如果有操作不合法,只需退回那几个操作并说明原因,其余照常写入。打开“修订编辑”模式,AI 的修改会记为成修订,这些修订与 Word 修订互通,可在 Word 中逐条通过或拒绝。
处理复杂格式配置专属命令包
对于一些特殊格式,我们专门做了相关的命令包,如公文生成。因为即便 AI 可正确操作编辑器,也并不意味着它可以正确理解某个标准。我们最初的想象是美好的,我认为 AI 可正确操作编辑器后,它就拥有了解锁一切文档格式的能力,理论上确实是这样。但实测下来,没有清晰的规则,AI 生成的内容仍需人工持续校对。它可能丢失红头,也可能划线不是红色,又或是字体设置某些地方会有遗漏。
因此,针对这种随意发挥,我们为编辑器增加了专属命令包以及 skill。典型的场景就是公文,公文本身有 GB/T 9704—2012 标准要求,因此我们可以将标准封装为不同的命令,再配合公文模板 skill,即可又快有准地生成公文。
下面是一份只有正文的通知草稿,一句话排成正式公文:红头、发文字号、红线、标题、版记都由程序按国标生成,模型只负责从草稿里整理出机关、标题、主送、正文层次和抄送这些内容。
Agent 工具确实不能贪多
通过自描述机制改造过后,其实它撞上的正是社区 Agent 工具设计的理念,Agent 工具设计不应该盲目球多。工具越多,Agent 反而越容易 “变蠢”,一套合理的机制才是让模型发挥最大价值的保障。
初版的做法是每个插件为 AI 声明注册一个工具,结果是正确性评测只有 48%,当工具数量过多时,模型在相似名称、重叠边界或参数结构相似的工具之间容易犹豫,会导致 “选择困难与误选率上升”。替换为自描述机制后,正确率上升到了 98% 以上,且唯一错误还只是因为输入描述有问题导致。
参考与使用
如果你也在给自己的编辑器做智能助手,可以参考 Quirekit 的解决方案是否对自己的产品有用,如果直接用 QuireKit,那这些都已经做好了:
- 对于 classic-editor,我们有
@quirekit/classic-ai(目前暂时为商业包,但可在官网使用,只需配置自己的模型 key 即可),它通过侧边栏助手提供使用入口,并配备模型设置和技能库。 - 我们也通过 MIT 开源了可以直接使用的
@quirekit/ai-runtime,它是@quirekit/classic-ai的底层核心,classic-ai 只是在其之上做了一层包装,将其封装为了可用成品。runtime 已包含document_read、document_help以及document_edit核心工具机制,任何人均可用它封装自己的 AI 助手。 - 对于 Markdown 编辑器的,对应的是
@quirekit/md-editor-ai。 - 针对 QuireKit 写插件,可参考
@quirekit/ai-contract里的类型声明格式能力,开放给 AI 的命令用defineCommand包一层描述,AI 就能用上,不用写提示词。
除了 Classic AI,其他 AI 相关的包最近都改成了 MIT 开源,已经发到 npm 的 alpha 版。接入文档在 docs.quirekit.com。
后续也长路漫漫
回头看,我们花在模型身上的功夫其实并不多,大部分时间在给编辑器设计合理的工具与机制:让模型通过编辑器自己的命令操作文档,用一种机制让它知道有哪些能力和参数,程序能保证的结构不交给它,然后用评测盯着。这些都做好以后,其实一个便宜的快模型也就够了,目前大多数模型处理文档的能力都非常强了,文中三段演示用的都是 DeepSeek Flash V4.1,换成更强的模型结果也一样。虽然已经取得了一部分进展,但其实 AI 的坑目前还有很多,很多犄角旮旯的操作仍有限制,因此长路漫漫,优化仍在进行。
最后 QuireKit 是我在做的开源文档编辑器框架,有 Word 式的分页编辑器 Classic-editor、块编辑器 Block-editor 和 Markdown 编辑器框架 MSFE 以及成品 md-editor,Classic 和 MD Editor 用的是同一套 AI 核心。现在还是 alpha,block-editor 也会是 classic 同一套,目前正在迁移适配。
- 官网:quirekit.com
- GitHub:github.com/quirekit/qu…
本文首发并持续更新于 yishuai.wang。