---
title: "QuireKit 编辑器的 AI-Native 是如何设计的？谈谈我对真正 AI-Native 的理解"
language: zh
description: "我们可以让模型真正原生操作编辑器的内容与样式。AI-Native 的编辑器不在于 AI 入口长什么样，而在于模型能不能像用户一样通过编辑器自己的命令操作文档，并且不需要为每个功能写提示词，只要一种机制让模型知道编辑器有哪些能力、参数是什么、怎么调用。文中附改写排版、简历排版和公文生成三段真实运行录屏。"
url: "https://yishuai.wang/articles/2b6a83db-7faf-4884-bf69-cb901e461bd3"
translations: [{"language":"zh","url":"https://yishuai.wang/articles/2b6a83db-7faf-4884-bf69-cb901e461bd3"}]
published: 2026-11-03T00:00:26.436Z
updated: 2026-11-03T00:00:26.436Z
---

# QuireKit 编辑器的 AI-Native 是如何设计的？谈谈我对真正 AI-Native 的理解

先说结论，我个人认为，AI-Native 的编辑器，不在于形式，无论是左编辑器右输入框，还是选中后弹出 AI 润色、续写框，又或是正文下方放类似对话形式的输入框，本质并没有任何不同。在 Tiptap 体系下，只要不是通过命令原生操作，我认为就全都不算 Native。个人认为真正的 Native，是模型能原生操作编辑器，不是先生成 markdown 再通过兼容层转为编辑器内容，也不是将 markdown 转为兼容的 HTML 再通过兼容层转为编辑器内容，并且你不需要为每个功能都为模型编写提示词，你只需要一种机制，让模型能够知道编辑器有哪些能力，并且知道这些能力的参数，调用方式就够了，至于如何调用，那就得看模型的调度能力到底几何了。

下面是 QuireKit Classic 里的一次真实运行：一句话让 AI 把第一行设为标题并居中、改写第二段、把百分比和日期加粗标红，再在文末插入表格。修改先以修订呈现，最后一次批准。演示用的模型是 DeepSeek Flash，后面两段也一样。

![AI 在 QuireKit 里一句话完成改写、排版和插入表格](https://cdn.jsdelivr.net/gh/kingshuaishuai/ys-imgs@main/quirepub/1265bad3a98395d90da5fe409b0dd439.gif)

## 具体的实现方案来自一个之前做的 CLI

前段时间想要把公司后端的所有接口包装成一套对 AI 友好的 CLI，让 Codex、Claude Code、DSH、WorkBuddy 等任何 Agent 都能自己学会使用我们的产品。接口有上百个，版本有几十条标准包、几千家定制包，给每个 Agent 写提示词显然不现实，所以我设计了一套自描述的方案，Agent 只需要会三个命令：

```bash
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` 声明。比如书签插件的“加书签”命令：

```ts
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` 本身，把描述登记在一张以命令函数为键的表里）：

```ts
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 核心再从所有命令里把带描述的提取出来，汇总成一张表：

![流程图：命令注册与描述提取](https://cdn.jsdelivr.net/gh/kingshuaishuai/ys-imgs@main/quirepub/29d2e154af08cf333fa5902954f4a9e1.png)

除了命令，插件还用 `addAiCapabilities()` 声明格式属性。比如段落对齐，完整声明如下：

```ts
{
  // 能力种类：格式属性
  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 先读取了内置的简历技能，再用一次编辑调用全部改好，最后一次批准。

![AI 一次调用完成简历排版](https://cdn.jsdelivr.net/gh/kingshuaishuai/ys-imgs@main/quirepub/b8e16a321e67aaf71741e011934a45f3.gif)

写到这里应该能看出来，这跟前面那个 CLI 是一个路子。`document_read` 相当于 list，`document_help` 相当于 explain，`document_edit` 相当于 call。参数写错了，报错会直接说是哪个字段。我们自己的 AI 助手和第三方自己写的 Agent 用的也是同一套工具，没有给自己留后门。

![流程图：QuireKit AI 机制](https://cdn.jsdelivr.net/gh/kingshuaishuai/ys-imgs@main/quirepub/53c50a8808779d6a9bba50691cabcc75.png)

## 确保模型的修改和人工修改走同一套底层命令

通常情况下，大多数编辑器 AI 生成内容是生成一段 markdown 或 HTML，再经过兼容层转成编辑器内容替换选区。而由于 markdown 和 HTML 自身的限制，在复杂文档中本就不能完美存储所有有效信息，因此转换必然丢失一部分信息。在 Prosemirror 体系下，HTML 存储 Attrs 还会依赖各种标签属性，当存在大量属性时，生成复杂文档可能还存在出错的问题。

但是在 QuireKit 里，AI 的每次写入，走的都是跟用户点击工具栏时完全相同的一套底层命令。一次调用里如果有操作不合法，只需退回那几个操作并说明原因，其余照常写入。打开“修订编辑”模式，AI 的修改会记为成修订，这些修订与 Word 修订互通，可在 Word 中逐条通过或拒绝。

## 处理复杂格式配置专属命令包

对于一些特殊格式，我们专门做了相关的命令包，如公文生成。因为即便 AI 可正确操作编辑器，也并不意味着它可以正确理解某个标准。我们最初的想象是美好的，我认为 AI 可正确操作编辑器后，它就拥有了解锁一切文档格式的能力，理论上确实是这样。但实测下来，没有清晰的规则，AI 生成的内容仍需人工持续校对。它可能丢失红头，也可能划线不是红色，又或是字体设置某些地方会有遗漏。

因此，针对这种随意发挥，我们为编辑器增加了专属命令包以及 skill。典型的场景就是公文，公文本身有 `GB/T 9704—2012` 标准要求，因此我们可以将标准封装为不同的命令，再配合公文模板 skill，即可又快有准地生成公文。

下面是一份只有正文的通知草稿，一句话排成正式公文：红头、发文字号、红线、标题、版记都由程序按国标生成，模型只负责从草稿里整理出机关、标题、主送、正文层次和抄送这些内容。

![AI 一句话把通知排成正式公文](https://cdn.jsdelivr.net/gh/kingshuaishuai/ys-imgs@main/quirepub/0035f9c4c1f8e69794f5172fcc543e7e.gif)

## 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 版。接入文档在 [https://docs.quirekit.com](https://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 同一套，目前正在迁移适配。

*   官网：[https://quirekit.com](https://quirekit.com)
*   GitHub：[https://github.com/quirekit/quirekit](https://github.com/quirekit/quirekit)

* * *

本文首发并持续更新于 [yishuai.wang](https://yishuai.wang)。

![](https://cdn.jsdelivr.net/gh/kingshuaishuai/ys-imgs@main/quirepub/b10103f1f3a13f877cbf3b6d77c07c9e.png)
