这两年做 AI Agent 有一个绕不开的尴尬:对话、规划、调用 API 都挺好,可一旦要让 Agent 真正去操作一份表格、生成一张报表,很多人就开始手动拼 SQL、写 Excel 脚本,或者干脆让 Agent 输出一段“建议你这么做”的文字。办公能力,成了 Agent 化最拖后腿的一环。Univer 这个开源办公应用 SDK 就是冲着这个缺口来的,它把电子表格、文档、演示文稿这些核心办公能力拆成可嵌入的 SDK,让你自己应用里的数据能被 Agent 直接读写、计算和呈现。
和传统“浏览器里打开一个在线表格”的思路完全不同,Univer 从一开始就是工程师视角的产品:不强迫你接受一个完整软件,而是把表格引擎、公式引擎、渲染引擎、协同模型全部打包成可以按需使用的模块。你可以在自己的前端项目里嵌入一个高仿 Excel 的表格界面,也可以在无头模式下只调用它的计算引擎,让 AI Agent 像调用普通工具函数一样去填数据、算公式、生成图表。对于正在做 Agent 应用、但又不想重复造办公软件轮子的团队来说,这是一个很值得认真研究的底座。
这篇文章我会先拆 Univer 到底解决了什么问题,再逐个过它的核心能力,最后给一套从 0 到 1 把 Univer 接入报表类 Agent 的实操方案,以及我踩过的坑。无论你只是想做 Agent 练手小项目,还是想在企业项目里落地智能办公,都应该能从中找到可以直接抄走的经验。
1. 项目画像:Univer 到底解决了什么痛点
1.1 AI Agent 时代的办公能力缺口
先说一个很现实的问题:现在的 AI Agent 普遍具备理解、推理和生成能力,却严重缺乏“操作办公文件”的能力。
你让 Agent 分析一份销售数据,它最常见的做法是吐出一段 Markdown 文本,或者给你一段 Python 代码让你自己去跑 pandas。数据量一旦变大,文本输出根本没意义;代码输出又等于把活儿扔回给用户。真正的智能体应该能自己打开一张表、写入计算结果、把关键指标可视化,然后给你一张可以直接拿去开周会的报表。
市面上不是没有现成方案,但问题也不少。走传统 Office 自动化路线的,比如用 VBA 操作 Excel,本质上还是单机时代的思路,跟 Agent 这套在线协作的体系格格不入;走云文档 API 路线的,比如接管腾讯文档、钉钉文档,又等于把核心数据交到别人的生态里,企业级客户很难接受。
Univer 走的是第三条路:把办公能力做成一个开源 SDK,嵌入你自己的应用。数据在你的存储里,界面在你的前端里,计算逻辑在你的代码里,Agent 只是通过 API 去调用这些能力。对 AI Agent 来说,这不是“给一个软件装个外挂”,而是“让 Agent 天然拥有表格计算能力”,本质上更接近人在操作 Excel 时的完整闭环。
1.2 Univer 的技术底座与定位
Univer 的前身来自国内办公软件核心研发团队,开发之初就定位成一个「具备完整办公核心能力的高性能框架」,而不是某个业务的子功能。它在 GitHub 上的仓库叫 dream-num/univer,目前以 MIT 协议开源社区版,商业版则提供更完整的企业级服务。
从技术形态上看,Univer 最特别的地方是它没有把界面和逻辑强耦合。整个项目基于 TypeScript 编写,渲染层用的是自研 Canvas 引擎,把 DOM 依赖降到了很低。这意味着你可以在 React、Vue 甚至纯 JS 项目里嵌入它,也可以在 Node 环境里只跑核心逻辑。后者对 AI Agent 尤其重要,因为 Agent 很多时候不需要“看”到表格界面,它只需要一个能算数、能读写数据、能生成 Office 兼容文件的计算内核。
我经常拿外卖柜来类比 Univer 的定位。普通在线表格是一个开好的饭馆,你只能去店里吃饭;Univer 则是把食材加工能力做成标准化接口,你可以把它嵌进自己的外卖柜里,用户可以扫码取餐,Agent 也可以直接往里补货。这个定位上的差别,决定了它面向的是开发者,而不是终端用户。
1.3 为什么是 SDK 而不是成品软件
Univer 没有像大多数开源办公项目那样做一个“在线 Excel 网站”然后让所有人都用,而是选择做 SDK,这个取舍值得展开说。成品软件解决的是“我没有表格用”的问题,SDK 解决的是“我的产品必须包含表格能力”的问题。
对 AI Agent 开发者来说,SDK 形态几乎是必选项。因为 Agent 不可能跑到公网用某个在线表格网站,然后把文件传过来传过去,这种链路既不安全也不稳定。你需要在 Agent 的工作环境里直接初始化一个表格实例,向它下发命令,拿到计算结果,最后把成品导出成 xlsx。这要求办公能力必须像代码库一样能和你的 Agent 跑在同一个进程、同一套内存里。只有 SDK 形态的办公应用能满足这个条件。
此外,SDK 形态还有工程上的天然优势:可测试、可版本化、可单独升级。我见过很多团队想自研报表能力,最后都演变成在代码里硬编码一堆 HTML 表格拼接逻辑,维护成本极高而功能却越做越窄。Univer 这种把办公能力当成依赖包引入的方式,至少让团队可以站在一个被大量生产环境验证过的底座上,专心去写自己业务里真正差异化的部分。
2. 核心能力拆解:一个办公 SDK 凭什么适配 Agent
2.1 三大引擎:Sheet / Doc / Slide 各管什么
Univer 的能力框架由三大核心子产品构成:Univer Sheet,Univer Doc,Univer Slide。翻译过来就是电子表格、文档、演示文稿。
Univer Sheet 是当前生态最成熟、社区关注度最高的部分,几乎覆盖了主流 Excel 的常用能力。单元格编辑、公式计算、条件格式、冻结窗格、合并单元格、筛选排序、图表、数据透视表,以及 xlsx 的导入导出,这些基础能力都是有的。对 Agent 来说,Sheet 是天然的“结构化数据工作台”。Agent 可以通过命令在表格里定位单元格、写入数据、批量应用格式,也可以调用公式引擎做动态计算,甚至生成图表当作数据分析结果输出。
Univer Doc 负责富文本文档的渲染和编辑,适合做报告生成、合同起草、知识库文档这类场景。Slide 目前相对轻量,主要支撑演示文稿的创建与播放。AI Agent 的应用里,Doc 的可控程度决定了它能生成多“像样”的周报;Slide 则更适合做自动汇报材料生成。
要注意,这三个子产品不是三个独立的仓库,而是同一套架构下的插件集合。这意味着你可以把 Sheet 和 Doc 放在同一个 Univer 实例里,员工用文档写方案,Agent 在旁边的 Sheet 里算数据,两边还能协同。这种能力组合对复杂的办公 Agent 项目非常实用。
2.2 公式引擎与无头模式:让 Agent 有“算力”
如果说界面能力是办公 SDK 的皮,那公式引擎就是它的骨架。Univer 自研了独立的公式计算引擎,支持 400 多个常规函数,并且允许注册自定义函数。
为什么公式引擎对 Agent 重要?因为 LLM 做自然语言理解有一套,但做精确数值计算并不可靠。让 Agent 用大模型直接算“销售总额同比增长率”,结果经常出现低级错误;让 Agent 把自然语言翻译成公式,再由 Univer 公式引擎去算,结果就是确定性的、可验证的。这种“LLM 理解 + 引擎计算”的分工模式,本身就是 AI Agent 落地办公场景的标准姿势。
更重要的是,Univer 的公式引擎可以脱离 UI 独立运行,也就是所谓无头模式。你可以不创建任何单元格界面,直接在代码里初始化一个工作簿、填充数据、调用公式求值,然后拿结果走后续流程。对于服务端 Agent 来说,这等于给了 Agent 一个轻量级的“运算插件包”,它不需要启动浏览器,也不需要渲染 Canvas,只占用很小的资源就能完成任务。
我在实际项目里最喜欢的一个用法是:让 Agent 用自然语言描述统计口径,通过 LLM 生成一个 SUMIFS 公式,然后由 Univer 引擎在真实数据上执行,最后 Agent 拿着结果继续写汇报。这样既发挥了 LLM 的语义理解能力,又保证了计算结果的准确性,两边各干各擅长的活。
2.3 插件化体系与命令机制:Agent 操作的入口设计
Univer 从架构上就采用了插件化注册机制。Univer 本身是一个核心容器(Core),Sheet、Doc、Slide、公式引擎、协同能力全部以插件形式挂载到容器上。这种设计的工程收益非常直观:用到什么装什么,不需要的模块根本不会进打包产物。
对 AI Agent 而言,插件化体系更重要的价值在于命令机制。Univer 内部的所有操作都被建模为命令(Command),比如“设置单元格值”“插入行”“执行公式计算”都对应具体命令。这些命令天然就是 Agent 的工具函数:你只需要把常用命令封装成一组 JSON 格式的 Tool 描述,LLM 就可以通过 Function Calling 去调用它们。
我特别推荐的做法是,在 Agent 的系统提示词里给 Univer 工具写一个清晰的使用规范。比如:修改单元格数据必须走setValues命令,计算费用必须走evaluateFormula命令,导出必须走exportSheet命令。这样 Agent 的操作路径是稳定可控的,不会出现“它自己想了个办法,最后把表格弄乱”的情况。
插件化体系也意味着你可以写业务插件。比如给 Agent 加一个“数据校验插件”,在写入前检查数据合法性;或者加一个“审计插件”,记录 Agent 每次修改过的单元格。这些扩展因为从一开始就被设计成插件,接入成本远低于在成熟办公软件里做二次开发。
2.4 协同能力与文档模型:多人多 Agent 协作的基础
Univer 的协同能力也是它区别于一般“表格控件”的重要特征。它实现了基于 CRDT 思想的协同文档模型,支持多人同时编辑同一个文档,并能在断网重连后自动合并冲突。
有人可能会问:我的 Agent 是自己跑任务的,不需要协同。但真实场景里,往往是多个 Agent 共同处理一份业务数据。比如一个 Agent 负责定时拉取订单数据写入 Sheet,另一个 Agent 负责根据最新数据生成图表,还有一个 Agent 在协同文档里写分析结论。如果没有协同模型,这三个 Agent 同时往一个文件里写数据,冲突问题会让人崩溃。Univer 的 CRDT 模型天然解决了并发写入的合法性问题。
所以,协同能力不是锦上添花,而是把 Agent 从单机任务升级为多智能体协作任务的必要基础设施。开发者在规划系统时,最好一开始就把数据模型建立在支持协同的文档结构上,否则等到多个 Agent 并发写入时再迁移,代价会成倍增加。
3. 从 0 到 1:给一个报表 Agent 接入 Univer
3.1 需求设定与整体流程
先明确场景:我要做一个“销售周报生成 Agent”。用户输入一句自然语言,比如“统计北区本周各产品线销售额,生成柱状图,并输出来源数据表”,Agent 需要完成从数据查询到表格生成的完整链路。
整体流程分成四层:Agent 接收自然语言指令 -> LLM 解析出报表意图 -> 调用数据服务拿到原始数据 -> 调用 Univer 工具落表、计算、配图 -> 导出 xlsx 或直接渲染在网页上给用户预览。
在这个链路里,Univer 承担两个角色:一个角色是作为前端展示层,用户能直接在浏览器看到编辑好的表格和图表;另一个角色是作为无头计算层,Agent 在服务端调用它的 API 完成数据填充和公式计算。一个 SDK 同时支撑两个角色,这是它能嵌入 Agent 工作流的根本原因。
3.2 初始化一个 Univer 实例
前端部分,我用 React 项目演示。先安装官方依赖,需要注意 Univer 目前处于快速迭代期,不同版本的初始化 API 略有差异,下面的写法以主流版本为参考,具体请对照官方文档。
npm install @univerjs/core @univerjs/sheets @univerjs/sheets-ui @univerjs/presets然后创建一个基础工作簿:
import { Univer } from '@univerjs/core'; import { UniverSheet } from '@univerjs/sheets'; import { UniverPresetSheet } from '@univerjs/presets'; const univer = new Univer(); univer.registerPlugin(UniverPresetSheet); // 也可按需注册,例如只注册核心表格模块 // univer.registerPlugin(UniverSheet, { 中文: true });如果你只需要无头计算,不想要界面模块,可以只引入@univerjs/core和公式引擎,然后用代码命令创建工作簿:
import { Univer } from '@univerjs/core'; import { UniverFormulaEngine } from '@univerjs/engine-formula'; const univer = new Univer(); univer.registerPlugin(UniverFormulaEngine); // 创建一个工作簿实例 const workbook = univer.createWorkbook({ sheetOrder: ['Sheet1'] });这里的核心思想是,Univer 的所有操作都通过统一 API 下发给核心容器,UI 插件只是把相同的命令从界面触发而已。所以你在无头环境写的逻辑,和你以后在网页上渲染的表格,完全是同一套数据和命令。
3.3 把 Univer 封装成 Agent 工具集
最关键的步骤是把 Univer 能力封装成 Agent 可以直接调用的工具函数。我习惯用 JSON Schema 来描述函数签名,方便接入大部分 LLM 的 Function Calling。
const univerTools = { createSheetFromData: { description: '根据结构化数据创建一张新工作表', parameters: { type: 'object', properties: { sheetName: { type: 'string', description: '工作表名称' }, rows: { type: 'array', items: { type: 'object' }, description: '数据行,对象键为列名,值为单元格内容' } } }, handler: async ({ sheetName, rows }) => { // 在工作簿中创建新表并写入数据 const sheet = workbook.createSheet(sheetName); rows.forEach((row, rowIndex) => { const columns = Object.keys(row); columns.forEach((col, colIndex) => { sheet.setCellValue(rowIndex, colIndex, row[col]); }); }); return { ok: true, sheetId: sheet.id }; } }, evaluateFormula: { description: '在指定工作表上执行公式计算并返回结果', parameters: { type: 'object', properties: { sheetName: { type: 'string' }, formula: { type: 'string', description: 'Excel 公式,如 SUM(A1:A10)' } } }, handler: async ({ sheetName, formula }) => { // 这里的求值过程会走 Univer 公式引擎,返回确定性结果 const result = formulaEngine.evaluate(sheetName, formula); return { result }; } } };注意,实际接入时 handler 内部建议再包一层事务或者快照机制。因为 Agent 的调用顺序不一定符合你的预期,可能出现“先改了几格数据,然后又用了一个错误公式覆盖掉”的情况。有个可回滚的快照,至少能把损失控制在单个会话内部。
3.4 LLM 决策与工具编排
工具准备好之后,Agent 编排层就比较简单了。主流的大模型平台都支持 Function Calling,我们把univerTools注册进去,让 LLM 在看到用户指令后自行决定调用哪些工具。
用伪代码描述执行过程:
用户输入:统计北区本周各产品线销售额 Agent 执行: 1. 调用数据查询工具,拿原始订单数据 2. 判断需要生成表格,调用 createSheetFromData 写入订单明细 3. 判断需要计算汇总,调用 evaluateFormula 执行 SUMIF 公式 4. 调用图表工具生成柱状图 5. 导出 xlsx 文件并返回下载链接这套编排的理论基础是“LLM 理解需求 + 确定引擎执行”,把一切需要精确计算的地方交给 Univer 公式引擎,LLM 只负责解析和规划。实测下来,报表结果的可信度远比大模型直接输出数字高得多,因为每一步都有确定性的执行记录可以审计。
3.5 在线展示与最终交付
如果你的 Agent 应用需要在线展示结果,直接在页面里挂载 Univer 实例就好。把上一步生成的表格数据导入同一个工作簿实例,用户能点击单元格看到公式,能在图表上悬停看到数值,体验和本地打开 Excel 几乎没有差别。
这一步需要注意性能。如果需要展示的数据量很大,比如几万行,就要考虑虚拟滚动和分页,不要一次性把全部数据塞给渲染层。Agent 侧生成的报表通常规模可控,但如果是面向企业数据中台的报表,最好在写入前先做聚合,让 Agent 只落结果数据而不是原始流水。
4. 常见问题与排查技巧实录
4.1 API 版本迭代带来的兼容性问题
Univer 目前仍然处于 0.x 到 1.x 的演进过程中,API 变动频率在开源项目里属于偏快的。我最早接入的一个版本里,初始化方式还是univer.registerPlugin(UniverSheet),换了一个小版本之后,官方就推荐用 Preset 模式一次性注册整套插件。
这不是不可控,只是你需要建立好依赖隔离习惯。我的经验是:在源码里加一个univer-adapter.ts,统一封装初始化、建表、写入、导出这些动作,别让业务代码直接调 Univer 的底层对象。这样版本升级时只需要改这一个适配文件,业务代码不会大面积爆红。另一个建议是把 Univer 相关依赖锁定固定版本号,不要用^自动升级,至少等测试通过后再手动升。
4.2 公式引擎与 xlsx 导入导出的坑
Agent 场景里经常需要从用户上传的 xlsx 文件里读取数据,或者最终给用户导出一个 xlsx。Univer 的导入导出能力做得不错,但有几个细节要注意。
第一,公式在导入导出时可能会被缓存成“结果值”。如果对端工具要求保持公式可编辑,需要检查是否启用了公式序列化选项。第二,某些复杂条件格式、数据透视表样式,在跨格式转换时可能出现差异。这不是 Univer 的问题,Excel 自己打开不同厂商生成的文件也会有同样现象。碰到这种问题,排查思路是先做最小化验证:只导出一张五列十行的纯数据表,看问题是否仍然存在,逐步缩小范围。
我给的一个实在建议是:在 Agent 的报表生成流程里,尽量做到“能用简单公式就不用数组公式”“能用标准图表就不用自定义图表”。这在纯人工作业里是丧气话,但在 AI Agent 生成的场景里就是稳定性的保障。Agent 的任务是快速交付可用文档,而不是炫技。
4.3 协同模型的冲突处理与会话边界
如果项目里有多个 Agent 同时操作同一个工作簿,必须认真对待协同冲突。Univer 的 CRDT 模型处理底层并发写入是没问题的,但业务层面的冲突仍需你自己定义策略。比如订单数据更新 Agent 正在重写 A 列,而报表美化 Agent 正在调整 A 列单元格颜色,这两个操作如果同时发生,最后表格内容可能不是你想要的。
我的处理方式是给每个 Agent 分配独立的写入区域,或者使用会话锁。简单说,报表生成会话开始前,在工作表上标记一个“延迟批量写入”状态,所有写操作先进入队列,等会话结束后统一提交。这样既能利用 Univer 的协同能力,又避免了多个 Agent 在同一时间片内互相干扰。
4.4 Agent 场景下的性能优化
无头公式计算在数据量大时会有一个容易被忽视的性能瓶颈:公式依赖链过长。比如一个单元格的公式引用了另一个单元格,而另一个单元格又引用了第三个单元格,引擎需要按依赖顺序逐层求值。如果 Agent 生成了几千个公式,性能就会明显下降。
优化方案有两类。一类是在数据写入阶段尽量做聚合计算,避免每个单元格都是复杂公式;另一类是优先使用区域引用而不是逐单元格引用。举例子来说,SUM(B2:B10000)这类区域公式,比生成一万个B1+B2+...拼接公式要快几个数量级。LeetCode 式的聪明解法在办公场景里不总是最优,反而最朴素、最简单的公式结构往往性能最稳。
5. 选型对比与后续扩展建议
5.1 与基于传统 Excel 自动化方案的对比
很多团队做 Agent 报表,第一反应是写 Python + openpyxl 或者 pyxll,让 Agent 生成 Python 代码操作 Excel 文件。在纯离线、低并发场景下这没问题,但它有几个硬伤。首先是每次操作都要从头加载文件,缺少一个常驻的计算引擎;其次是没有协同能力,多个 Agent 写同一个文件会发生文件锁冲突;最后是前端展示层缺失,用户没法在浏览器里直接看到可交互的表格。
Univer 把这三块补全了:常驻内存里的工作簿实例、CRDT 协同模型、可嵌入的 Canvas 渲染界面。对做产品化的团队来说,这意味着不用再维护“后端生成 xlsx + 前端写表格渲染器”两套系统,一套 SDK 就能把数据通道和展示通道统一起来。如果你只是写内部脚本,Python 方案仍然够用;但如果你要把表格能力产品化,Univer 的架构优势是压倒性的。
5.2 无头模式扩展:公式生成与数据分析
把 Univer 接入 Agent 之后,最值得扩展的方向是“公式生成”。传统的表格软件里,用户遇到复杂统计需求,要么自己查函数文档,要么求别人写公式。在 Agent 场景里,你可以让用户直接用自然语言描述需求,LLM 生成候选公式,Univer 在真实数据上执行验证,再把结果连同公式一起展示给用户。这样用户不仅拿到了答案,还拿到了可复用的公式本身。
另外一个扩展方向是智能图表推荐。Agent 拿到数据后,先分析数据结构,判断是时间序列、类别对比还是占比分布,再调用 Univer 的图表能力自动生成对应类型。这个能力如果自己从零做起工作量不小,但 Univer 已经提供了图表基础能力,你只需要在它之上封装一个推荐策略。这类插件一旦沉淀下来,以后每个报表 Agent 项目都可以直接复用。
5.3 我的一点实操体会
根据我自己实际接入的经验,Univer 这个项目最打动我的地方,是它在“办公软件”和“开发者基础设施”之间找到了一个不错的平衡。它没有把自己包裹成一个封闭的在线 Office,而是像一个给 Agent 准备的办公能力组件库,尊重开发者的集成习惯。这在大厂办公套件生态里很少见。
如果你正在规划一个 AI Agent 相关的办公产品,我的建议是不要从零写表格引擎,也尽量别把一个在线文档应用当作 Agent 的运行宿主。花几天时间研究 Univer,把它的公式引擎和命令机制用好,你的 Agent 就能稳定地产出真实可用的办公文件。从目前社区活跃度和迭代速度来看,这个方向大概率还会继续往前跑,值得提前押注。