Univer 开源办公 SDK:让 AI Agent 原生操作表格与文档

发布时间:2026/11/3 8:48:51
Univer 开源办公 SDK:让 AI Agent 原生操作表格与文档

1. 为什么“办公应用 SDK”在 AI Agent 时代突然变得重要

过去两年,AI Agent 从一个偏学术的概念迅速变成了工程团队日常讨论的落地话题。我身边不少做企业软件的朋友,从 2024 年开始陆续接到“能不能让 Agent 直接操作表格、文档、幻灯片”的需求。一开始大家的做法很粗暴:用截图加坐标点击,或者用 DOM 选择器去模拟人工操作。这类方案在演示阶段看着很惊艳,一旦进入真实业务环境,问题就全暴露出来了——分辨率一变坐标就偏,页面结构一改选择器就失效,更别提多人协作场景下多个 Agent 同时操作同一份文档时的冲突问题。

Univer 这个项目之所以值得单独拿出来聊,是因为它切入的角度和上面那些“外挂式”方案完全不同。它把自己定位成面向 AI Agent 的开源办公应用 SDK,也就是说,它不是做一个让 Agent 去操作的成品办公软件,而是提供一套底层能力,让开发者可以把表格、文档、演示这些办公形态直接嵌入到自己的 Agent 产品里,并且让 Agent 以“原生”的方式去读写和操作这些内容。

这个定位解决的核心问题是:Agent 需要一个结构化的、可编程的办公数据操作层。传统的 Office 文件对 Agent 来说是一团黑盒,你得先解析、再理解、再操作、再序列化,中间任何一步出错都会导致结果不可用。而 Univer 把表格、文档这些对象抽象成了 SDK 层面的 API,Agent 调用 API 就像调用数据库一样直接,读写的是结构化数据,不是像素,也不是脆弱的 DOM 节点。

适合读这篇内容的人大概有三类。第一类是正在做 AI Agent 产品、需要让 Agent 具备办公文档处理能力的开发者;第二类是想把办公能力集成到自己 SaaS 产品里的前端或全栈工程师;第三类是对开源办公引擎感兴趣、想了解这类项目架构设计的技术负责人。不管你之前有没有接触过 Univer,只要你对“Agent 怎么真正操作办公文档”这个问题感兴趣,下面的内容应该都能给你一些可以直接参考的东西。

2. Univer 的整体架构与设计思路拆解

2.1 从“文档编辑器”到“办公能力层”的定位转变

理解 Univer 的第一步,是把它和传统的在线表格库区分开。市面上常见的表格组件,比如 Handsontable、AG Grid,本质上是“渲染 + 交互”的库,它们解决的是“怎么把数据漂亮地显示出来、让用户方便地编辑”。Univer 的野心不止于此,它想解决的是“办公文档作为一种数据结构,怎么被程序化地创建、修改、计算和协作”。

这个区别在 AI Agent 场景下会被放大。假设你的 Agent 需要根据一份销售数据自动生成一张带公式的汇总表,如果用传统表格库,你得自己管理数据模型、自己实现公式计算、自己处理单元格依赖关系。而 Univer 把这些都做进了内核:它有独立的数据模型层、公式计算引擎、协同层和渲染层。Agent 只需要调用公式相关的 API,把=SUM(A1:A10)写进去,剩下的计算和依赖更新由引擎负责。

我用一个生活化的类比来说明这个差异。传统表格库像是一个画板,你告诉它“在这个位置画一个格子,里面写 100”,它就画出来。Univer 更像是一个会计系统,你告诉它“这个月的支出记在 B 列,收入记在 C 列,利润用公式算”,它会理解这些数据之间的关系,并且在你修改任何一笔支出时,自动把利润重新算出来。对 Agent 来说,后者才是它真正需要的能力。

2.2 分层架构:为什么这样拆

Univer 的架构大致可以分成四层,我从下往上说。

最底层是核心数据层,负责管理表格、文档、演示等不同文档类型的数据结构。这一层不关心怎么渲染,只关心数据怎么存、怎么变、怎么保证一致性。它采用了类似命令模式的变更机制,每一次修改都是一条可追溯的命令,这为后面的协同和撤销重做打下了基础。

往上一层是公式与计算层。这是 Univer 比较有技术含量的部分。它实现了一套公式解析器和计算引擎,支持常见的电子表格函数,并且能处理单元格之间的依赖关系。当某个单元格的值变化时,引擎会自动找出所有依赖它的单元格并重新计算。对 Agent 来说,这意味着它可以像人类用户一样“写公式”,而不需要自己实现一套计算逻辑。

再往上是协同层。Univer 支持多人实时协作,多个客户端对同一份文档的修改会被同步和合并。这一层对 Agent 场景的意义在于:当多个 Agent 或者 Agent 与人类用户同时操作一份文档时,系统能保证数据不冲突、不丢失。我实测下来,它的协同机制在处理高频小修改时表现比较稳,不会出现传统方案里常见的“后写覆盖先写”问题。

最上层是渲染与交互层。这一层负责把数据画到屏幕上,处理用户的点击、拖拽、输入等操作。Univer 的渲染层是可选可替换的,你可以用它的默认渲染,也可以只拿数据层和公式层,自己写渲染。这种设计对 Agent 产品很友好,因为很多 Agent 场景根本不需要给人看的界面,只需要一个能读写文档的“无头”引擎。

2.3 插件化设计带来的扩展空间

Univer 另一个值得说的设计是插件化。它的核心功能被拆成了一个个插件,比如表格插件、公式插件、协同插件、导入导出插件。你可以按需加载,不需要的功能不引入,打包体积和运行时开销都能控制住。

这个设计对 AI Agent 开发者来说有两个实际好处。第一,你可以只引入数据层和公式层,做一个纯服务端的文档处理服务,不需要把整个渲染引擎拖进去。第二,你可以自己写插件来扩展能力,比如写一个“自然语言转公式”的插件,让 Agent 把用户的口语化描述翻译成 Univer 能理解的公式命令。

我个人的判断是,插件化是 Univer 能在 Agent 时代站住脚的关键设计之一。因为 Agent 的场景太分散了,有人需要表格,有人需要文档,有人需要演示,有人只需要公式计算。一个铁板一块的办公套件很难同时满足这些需求,而插件化让开发者可以按需组装。

3. 核心能力解析与 Agent 集成实操要点

3.1 数据模型:Agent 操作办公文档的入口

Univer 的数据模型是 Agent 集成的第一站。它把一份表格抽象成一个工作簿对象,工作簿里有工作表,工作表里有单元格,单元格有值、公式、样式等属性。Agent 要做的第一件事,就是通过 SDK 拿到这个工作簿对象,然后对它进行读写。

这里有一个实操中容易踩的坑:不要直接修改数据模型的内部字段。Univer 的数据变更应该通过它提供的命令 API 来执行,比如设置单元格值要用对应的 mutation 方法,而不是直接给对象的属性赋值。原因是直接改字段会绕过变更通知机制,导致公式不重算、协同不同步、撤销栈不记录。我一开始图省事直接改过字段,结果公式死活不更新,排查了半天才发现是这个问题。

正确的做法是使用 Univer 暴露的命令接口。下面是一个简化的示例,展示 Agent 如何通过命令设置单元格值并触发公式计算:

// 假设已经初始化了 univer 实例和当前工作簿 const workbook = univerAPI.getActiveWorkbook(); const worksheet = workbook.getActiveSheet(); // 通过命令设置 A1 的值 worksheet.getRange('A1').setValue(100); // 设置 A2 的值 worksheet.getRange('A2').setValue(200); // 设置 A3 为公式,引擎会自动计算 worksheet.getRange('A3').setFormula('=SUM(A1:A2)'); // 读取 A3 的计算结果 const result = worksheet.getRange('A3').getValue(); console.log(result); // 300

这段代码看起来简单,但背后发生的事情不少:设置 A1 和 A2 时,引擎记录了变更;设置 A3 公式时,引擎解析了公式、建立了依赖关系;读取 A3 时,引擎返回的是计算后的值而不是公式字符串。对 Agent 来说,它不需要关心这些内部机制,只需要知道“我写公式,它给我结果”。

3.2 公式引擎:让 Agent 具备“计算思维”

公式引擎是 Univer 对 Agent 最有价值的能力之一。为什么这么说?因为 Agent 在处理数据时,经常需要做汇总、筛选、条件判断这些操作。如果 Agent 自己用代码算,它需要把数据拉出来、在内存里处理、再写回去,中间涉及数据搬运和格式转换,容易出错。而如果 Agent 直接写公式,计算发生在文档内部,数据不用搬来搬去,结果也更可靠。

Univer 的公式引擎支持的功能覆盖了电子表格的常见场景,包括数学函数、统计函数、文本函数、日期函数、逻辑函数等。Agent 可以像人类用户一样,把=IF(A1>100, "达标", "未达标")这样的公式写进单元格。

这里有一个实操心得:Agent 生成公式时,最好先做一次语法校验。因为 Agent 生成的公式字符串可能包含拼写错误、括号不匹配、参数数量不对等问题。Univer 在设置公式时会做解析,如果公式非法会抛错。你可以在 Agent 的工作流里加一步“公式预检”,把非法公式拦截下来,让 Agent 重新生成,而不是等到写入后才报错。

另外,公式引擎的依赖追踪是自动的。当你修改了 A1 的值,所有引用了 A1 的公式都会重新计算。这个特性对 Agent 场景很重要,因为 Agent 经常需要做“假设分析”——改一个输入,看所有相关输出怎么变。有了自动依赖追踪,Agent 不需要自己维护依赖图,改完输入直接读输出就行。

3.3 协同层:多 Agent 并发操作的保障

协同层是很多 Agent 开发者容易忽略、但在真实产品中又绕不开的部分。想象一个场景:你的产品里有一个 Agent 负责填数据,另一个 Agent 负责审核,还有一个人类用户在旁边做最终确认。这三方可能同时操作同一份表格。如果没有协同机制,后写的会覆盖先写的,数据就乱了。

Univer 的协同层基于操作变换(OT)或者类似的思想,把每个操作转换成可合并的变更,然后在各端之间同步。对 Agent 来说,它不需要理解协同的底层算法,只需要知道:通过 SDK 发起的操作会被正确地同步到其他端,并且冲突会被合理解决。

我在测试中模拟过两个 Agent 同时修改同一行不同列的情况,结果两边都正确合并了,没有出现覆盖。也模拟过两个 Agent 同时修改同一个单元格的情况,系统采用了“后到者生效”的策略,并且把变更记录保留在历史里,可以追溯。这个行为在大多数业务场景下是合理的,但如果你有特殊需求,比如“先到者优先”,就需要自己在业务层做控制。

注意:协同层在无头模式下(即没有渲染层)也能工作,但需要确保各端的时间戳或者版本号机制正确配置。如果版本号对不上,同步可能会失败。

3.4 导入导出:和外部文件格式的桥接

Agent 处理的办公文档往往不是凭空产生的,而是从外部来的。用户可能上传一个 Excel 文件,让 Agent 分析;或者 Agent 生成一份表格后,需要导出成 Excel 发给别人。Univer 提供了导入导出能力,支持常见的办公文件格式。

导入导出的实操要点在于格式兼容性。不同的 Excel 文件可能包含复杂的样式、合并单元格、数据验证、条件格式等。Univer 在导入时会尽量保留这些信息,但不可能做到 100% 还原。我的建议是:如果 Agent 只需要处理数据,导入时可以只关注值和公式,忽略样式,这样解析更快、更稳定。如果需要保留样式,就要做好部分样式丢失的心理准备,并且在产品层面给用户提示。

导出时也有类似的问题。Univer 导出的文件在主流办公软件里能正常打开,但一些高级特性可能表现不一致。我实测下来,纯数据和公式的导出兼容性最好,复杂样式的导出偶尔会有偏差。如果你的产品对导出格式要求很高,建议在导出后加一步校验,或者提供多种导出选项让用户选择。

4. 从零搭建一个 Agent 办公能力的完整流程

4.1 环境准备与依赖安装

搭建一个基于 Univer 的 Agent 办公能力,第一步是把环境准备好。Univer 是一个前端为主的 SDK,所以你需要一个 JavaScript 或 TypeScript 的运行环境。如果你要做的是服务端 Agent,可以用 Node.js;如果要做的是浏览器端 Agent,直接用前端工程即可。

安装依赖时,Univer 采用了多包结构,核心包和各个插件包是分开的。你需要根据自己要用的功能来安装对应的包。比如只需要表格和公式,就装核心包、表格包、公式包;如果需要协同,再加协同包。

# 以 npm 为例,安装核心包和表格相关包 npm install @univerjs/core @univerjs/sheets @univerjs/sheets-formula # 如果需要协同能力 npm install @univerjs/sheets-collaboration # 如果需要导入导出 npm install @univerjs/sheets-import-export

这里有一个版本管理的坑:Univer 的各个包版本需要保持一致,否则可能出现 API 不匹配的问题。我建议在 package.json 里锁定版本号,或者用 monorepo 的方式统一管理。另外,Univer 迭代比较快,升级版本时最好先看一遍变更日志,确认没有破坏性变更再升。

4.2 初始化引擎与创建文档

环境准备好之后,下一步是初始化 Univer 引擎并创建一份文档。初始化的过程就是把你需要的插件注册进去,然后创建一个 Univer 实例。

import { Univer, LocaleType } from '@univerjs/core'; import { UniverSheetsPlugin } from '@univerjs/sheets'; import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'; // 创建 Univer 实例 const univer = new Univer({ locale: LocaleType.ZH_CN, }); // 注册插件 univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverSheetsFormulaPlugin); // 创建工作簿 const workbook = univer.createUniverSheet({ id: 'agent-workbook', sheetId: 'sheet-1', name: 'Agent 数据表', });

这段代码做了三件事:创建引擎、注册能力、创建文档。对 Agent 来说,它拿到workbook对象之后,就可以开始操作了。

这里有一个设计上的选择:你是把 Univer 跑在浏览器里,还是跑在服务端?如果跑在浏览器里,Agent 的逻辑也在前端,适合做交互式的产品;如果跑在服务端,Agent 可以批量处理文档,适合做后台任务。Univer 本身是跨环境的,但服务端运行时需要注意一些浏览器 API 的兼容性,可能需要 polyfill。

4.3 让 Agent 读写表格数据

Agent 读写表格数据是核心环节。读的时候,Agent 需要拿到指定区域的数据;写的时候,Agent 需要把处理结果写回指定位置。

读取数据的典型场景是:Agent 需要分析一份销售表,找出销售额最高的产品。它需要先读取数据区域,然后在内存里做分析,最后把结果写到一个汇总区域。

// 读取 A1 到 C10 的数据 const range = worksheet.getRange('A1:C10'); const values = range.getValues(); // values 是一个二维数组,比如 [[...], [...], ...] // Agent 在内存里分析 values,找出最大值 // ...分析逻辑... // 把结果写到 E1 worksheet.getRange('E1').setValue('最高销售额'); worksheet.getRange('E2').setValue(maxValue);

写入数据时,有一个性能上的注意点:批量写入比逐单元格写入快得多。如果你要写 1000 个单元格,逐个调用setValue会产生 1000 次变更通知,而用setValues一次性写入,只产生一次通知。在 Agent 场景下,数据量可能很大,批量写入能显著提升性能。

// 不推荐:逐单元格写入 for (let i = 0; i < 1000; i++) { worksheet.getRange(`A${i + 1}`).setValue(data[i]); } // 推荐:批量写入 worksheet.getRange('A1:A1000').setValues(data.map(v => [v]));

4.4 公式与计算的自动化处理

Agent 使用公式的场景通常有两类:一类是生成公式让文档自动计算,另一类是读取公式的计算结果做进一步处理。

生成公式时,Agent 需要把业务逻辑翻译成公式字符串。比如“计算每个产品的利润率”,翻译成公式就是=(收入-成本)/收入。Agent 可以遍历每一行,把公式写进对应的单元格。

// 假设收入在 B 列,成本在 C 列,利润率写到 D 列 for (let row = 2; row <= 10; row++) { worksheet.getRange(`D${row}`).setFormula(`=(B${row}-C${row})/B${row}`); }

读取计算结果时,Agent 拿到的是计算后的值,不是公式字符串。这个行为符合大多数场景的需求,因为 Agent 通常关心的是结果而不是公式本身。如果你确实需要拿到公式字符串,Univer 也提供了对应的 API。

这里有一个实操技巧:公式的重新计算是异步的。当你设置了一个公式,引擎可能需要一点时间来计算。如果你紧接着读取结果,可能读到的是旧值或者空值。稳妥的做法是监听计算完成事件,或者在读取前加一个短暂的等待。我在实际项目里遇到过这个问题,Agent 写完公式立刻读结果,读到了 undefined,后来改成监听事件就稳定了。

4.5 协同场景下的 Agent 行为控制

当多个 Agent 或者 Agent 与人类用户协同操作时,需要一些额外的控制策略。最核心的问题是:怎么避免 Agent 的操作和人类用户的操作互相干扰。

一个实用的做法是给 Agent 的操作加上“意图标记”。比如 Agent 在修改某个区域之前,先检查这个区域是否正在被人类用户编辑。如果是,Agent 可以等待或者选择其他区域。Univer 的协同层提供了选区感知能力,你可以通过 API 查询当前有哪些用户在哪些区域活动。

另一个做法是使用“事务”机制。Agent 把一组相关的修改打包成一个事务提交,协同层会把整个事务作为一个原子操作同步。这样其他端看到的是要么全部生效,要么全部不生效,不会看到中间状态。这个机制在 Agent 做批量修改时特别有用,能避免其他用户看到“改了一半”的文档。

提示:事务机制会增加协同层的负担,如果事务太大,同步可能会有延迟。建议把大事务拆成合理大小的批次。

5. 常见问题排查与避坑经验实录

5.1 公式不计算或计算结果不对

这是最常见的问题之一。表现是设置了公式,但单元格显示的还是公式字符串,或者显示的值和预期不符。

排查思路按顺序来:第一,确认公式字符串本身是否合法,括号是否匹配、函数名是否拼写正确、参数分隔符是否用对。第二,确认公式引用的单元格是否存在,如果引用了不存在的单元格,计算结果可能是错误值。第三,确认公式引擎插件是否已经注册,如果没有注册公式插件,公式不会被解析。第四,确认是否在设置公式后触发了重算,有些情况下需要手动触发。

我踩过的一个坑是:在无头模式下使用公式,但没有正确初始化计算引擎的依赖,导致公式一直不计算。后来发现是缺少了一个必要的插件注册。所以如果你在非浏览器环境使用 Univer,一定要仔细检查插件列表。

5.2 数据写入后读取不到

这个问题的表现是:Agent 写入了数据,但紧接着读取时读到的是空值或者旧值。

原因通常有两个:一是写入操作是异步的,读取时变更还没生效;二是写入和读取操作的不是同一个文档实例。第一个原因可以通过监听变更完成事件来解决,第二个原因需要检查你的代码里是不是创建了多个 Univer 实例,或者获取工作簿的方式不对。

我的建议是:在 Agent 的工作流里,写入和读取之间加一个明确的“等待变更生效”的步骤。Univer 提供了变更事件的监听接口,你可以等事件触发后再进行下一步。

5.3 协同同步失败或冲突

协同同步失败的表现是:一个端的修改没有同步到另一个端,或者同步后数据不一致。

排查时先确认各端的文档 ID 是否一致,如果文档 ID 不同,它们根本不是在协同同一份文档。然后确认协同插件的配置是否正确,包括同步通道的地址、认证信息等。最后检查网络连接是否稳定,协同依赖实时通信,网络抖动会导致同步延迟或失败。

冲突的处理策略取决于业务需求。Univer 默认采用“后到者生效”,如果你需要其他策略,比如“字段级合并”或者“人工仲裁”,需要在业务层自己实现。我的经验是,对于 Agent 场景,尽量让 Agent 操作不同的区域,从源头上减少冲突,比事后处理冲突要简单得多。

5.4 导入导出格式丢失

导入导出时格式丢失是常见现象,尤其是复杂的 Excel 文件。表现是导入后样式变了、合并单元格没了、条件格式失效等。

这个问题的根本原因是不同办公软件对文件格式的实现有差异,Univer 不可能完全兼容所有细节。务实的做法是:明确你的产品需要保留哪些格式,只对这些格式做重点保障。比如你的 Agent 只需要处理数据,那导入时就可以忽略样式,只解析值和公式,这样又快又稳。

导出时,如果目标用户主要用某一种办公软件,可以针对性地做兼容性测试。我实测下来,纯数据和公式的导出在主流软件里都能正常打开,复杂样式的导出则需要多测试几轮。

5.5 性能问题:大数据量下的卡顿

当表格数据量很大时,Agent 的操作可能会变慢。表现是写入一批数据要等很久,或者公式重算导致界面卡顿。

优化的方向有几个:第一,批量操作代替逐个操作,前面已经说过。第二,关闭不必要的重算,比如在批量写入公式时,可以先关闭自动重算,写完后再统一触发一次。第三,只加载需要的数据区域,不要一次性把整个大表都读进内存。第四,如果不需要渲染,使用无头模式,省去渲染开销。

我在一个项目里处理过十万行级别的数据,最初的方案是逐行写入,耗时很长。后来改成批量写入加关闭自动重算,性能提升了一个数量级。所以遇到性能问题,先检查是不是操作方式太“碎”了。

问题现象可能原因排查方向解决建议
公式不计算插件未注册、公式非法、未触发重算检查插件列表、校验公式语法、监听计算事件注册公式插件、预检公式、等待计算完成
写入后读不到异步未完成、实例不一致检查变更事件、确认文档实例等待变更生效、统一实例管理
协同不同步文档 ID 不一致、配置错误、网络问题核对文档 ID、检查协同配置、测试网络统一文档标识、修正配置、增加重试
导入格式丢失格式兼容性差异确认需要保留的格式类型按需解析、重点保障关键格式
大数据卡顿操作太碎、自动重算频繁、渲染开销检查操作粒度、重算策略、是否无头批量操作、延迟重算、使用无头模式

6. 我对 Univer 在 Agent 场景下的一些实际体会

用 Univer 做 Agent 办公能力这段时间,最大的感受是:它把“办公文档”从一个需要模拟操作的目标,变成了一个可以直接编程的对象。这个转变带来的效率提升是实实在在的。以前做一个“Agent 自动填表”的功能,要处理坐标、处理焦点、处理各种边界情况,现在直接调 API,代码量少了一大半,稳定性还更好。

另一个体会是,公式引擎的价值被低估了。很多人一提到 Agent 处理表格,想到的就是把数据拉出来用 Python 算。但实际上,让 Agent 写公式、让文档自己算,在很多场景下更自然、更可靠。尤其是当文档需要被人继续编辑时,公式保留了计算逻辑,人改一个输入,结果自动更新,这比 Agent 算完写死一个值要灵活得多。

当然,Univer 也不是没有短板。它的文档和示例还在完善中,有些 API 的用法需要看源码才能搞清楚。社区虽然活跃,但中文资料相对少一些。如果你打算用它做产品,建议预留一些时间做技术调研和原型验证,不要一上来就全量投入。

最后分享一个小技巧:把 Agent 的操作封装成“意图”而不是“命令”。比如不要直接让 Agent 调用“设置 A1 为 100”,而是让 Agent 表达“把销售额更新为 100”,然后由你的中间层把意图翻译成 Univer 的命令。这样做的好处是,当底层 SDK 升级或者更换时,你的 Agent 逻辑不需要大改,只需要调整翻译层。这个思路在我后来的项目里帮了不少忙,推荐你也试试。