☰
AI Agent 的 Office 运行时:Univer 表格交互与并发实践
2026/10/1 4:29:37 网站建设 项目流程

1. 为什么 AI Agent 需要一个“Office 运行时”

1.1 从“会聊天”到“会干活”的断层

过去两年我一直在做 AI Agent 相关的落地项目,从最早的纯对话机器人,到后来接工具调用、接 RAG、接工作流编排,踩过的坑基本能写一本书。但真正让我意识到问题严重性的,是去年帮一家做供应链的公司做智能报表 Agent 的时候。

那个项目的需求听起来特别朴素:让 Agent 每天自动读取业务数据,生成一份带格式的 Excel 周报,里面要有合并单元格的标题、条件格式标红异常值、还有几个公式联动。我当时的第一反应是“这不就是个 openpyxl 的活儿吗”,结果真上手才发现,Agent 生成的表格结构稍微复杂一点,openpyxl 的代码就开始变得又臭又长,而且一旦用户想在生成结果上手动改两笔,整个链路就断了——因为文件是“死”的,改完再让 Agent 处理,它根本不知道用户改了什么。

这就是我想聊的核心问题:AI Agent 缺的不是生成能力,缺的是一个能承载“交互式文档”的运行时环境。传统的 Office 文件处理库,本质上都是“批处理工具”——读进来、算一遍、写出去,中间没有状态,没有交互,没有增量。而 Agent 要干的活儿,恰恰是“边生成边协作边修改”的活儿。

Univer 这个项目,就是冲着这个断层来的。它把自己定位成“为 AI Agent 准备的一体化 Office 运行时”,关键词是运行时三个字。不是文件格式转换器,不是渲染库,而是一个能让 Agent 和人类在同一份表格/文档上持续协作的执行环境。

1.2 Univer 到底是什么,能解决什么问题

先把话说直白一点。Univer 是一个开源的、支持表格、文档、幻灯片的在线协作套件内核,它提供了完整的 SDK,可以嵌入到 Web 应用里,也可以在 Node.js 环境里跑。它最核心的能力,是把“电子表格”这件事抽象成了一套可编程的模型——单元格、公式、样式、权限、协同,全都是可以通过 API 操作的。

那它跟 AI Agent 有什么关系?我理解下来有这么几层:

第一层,Agent 需要一个结构化的操作对象。你让大模型直接吐一个 xlsx 文件,它做不到;你让它吐一段 JSON 描述表格,它能做,但这段 JSON 怎么变成用户能看能改的东西?Univer 就是那个“把 JSON 变成活表格”的中间层。

第二层,Agent 需要感知用户的修改。用户在一个单元格里填了数,Agent 应该能拿到这个变更事件,然后决定要不要重算、要不要提醒、要不要触发下一步。这种“事件驱动”的能力,普通文件库是没有的。

第三层,Agent 需要做权限控制。这是热词里提到的那个场景——“支持用户定义表格,然后让用户去填写一些单元格,其他的单元格用户无法修改”。这个需求在真实业务里太常见了:Agent 生成一个模板,锁定公式区和表头区,只开放数据录入区给用户。Univer 的权限模型天然支持这种粒度。

第四层,Agent 需要扛并发。热词里有个“ai agent 怎么扛并发”,这个问题在 Office 场景下尤其尖锐。如果每个用户会话都要起一个独立的表格实例,内存和 CPU 怎么控?Univer 支持服务端渲染和实例复用,这是它能进生产环境的关键。

所以你看,Univer 不是一个“更好用的 Excel 库”,它是把 Office 这件事从“文件”重新定义成了“服务”。这个视角的转换,才是它对 Agent 生态真正的价值。

1.3 适合谁来读这篇内容

这篇东西我尽量写得实操一点,适合几类人:

  • 正在做 AI Agent 产品,需要让 Agent 输出结构化文档(尤其是表格)的开发者;
  • 想在自己的 SaaS 里嵌入在线表格能力,又不想被商业组件绑死的技术负责人;
  • 对“Agent + 协作文档”这个方向感兴趣,想找个练手项目的独立开发者;
  • 单纯好奇 Univer 这套东西怎么用、能不能替代传统方案的前端/全栈工程师。

我会从架构思路讲到具体代码,从权限模型讲到并发处理,中间穿插我自己踩过的坑。不保证面面俱到,但保证每一条都是我实际验证过的。

2. Univer 的核心架构与选型逻辑

2.1 为什么是“运行时”而不是“文件库”

要理解 Univer 的设计,得先理解它跟传统方案的根本差异。我拿几个常见方案做个对比,这样更直观。

方案类型代表数据模型交互能力协同能力Agent 友好度
文件处理库openpyxl、xlsxwriter文件流无无低,只能批处理
前端表格组件Handsontable、AG Grid内存表格强需自建中,偏展示
在线文档内核Univer可编程模型强原生支持高,事件+API 完整

关键差异在“数据模型”这一列。openpyxl 处理的是文件,你读进来的是一个 workbook 对象,改完写出去,中间没有“活”的状态。AG Grid 处理的是内存里的二维数组,展示很强,但它的模型是“表格数据”,不是“文档结构”——公式、样式、权限这些它管不了。

Univer 的模型是分层的:最底层是数据层(Data Model),管单元格的值、公式、格式;中间是渲染层(Render),管怎么画;最上面是插件层(Plugin),管协同、权限、导入导出这些业务能力。这个分层的好处是,Agent 可以直接操作数据层,不用关心渲染;而用户看到的是渲染层的结果,两边通过事件同步。

我打个比方。传统文件库像是“打印店”——你把内容给它,它给你印出来,印完就结束了。Univer 像是“共享白板”——你可以在上面写,别人也可以改,改的过程所有人都能看到,而且白板本身知道每一笔是谁画的、什么时候画的。

2.2 核心模块拆解:从单元格到协同

Univer 的代码结构挺清晰的,我按自己的理解拆一下几个关键模块。

@univerjs/core是地基,定义了所有基础类型:Workbook、Worksheet、Range、Cell、Style、Formula 等等。你操作表格,本质上就是在操作这些对象。比如你要设置 A1 单元格的值,代码大概是这样:

const workbook = univerAPI.getActiveWorkbook(); const worksheet = workbook.getActiveSheet(); const range = worksheet.getRange('A1'); range.setValue('Hello Univer');

这段代码看着简单,但背后发生的事情不少:range 对象会去 core 里找到对应的单元格模型,改它的值,然后触发一个变更事件,渲染层收到事件后重绘,协同层收到事件后广播给其他客户端。这一整套链路,就是“运行时”的含义。

@univerjs/sheets是表格能力的实现,公式引擎、条件格式、数据验证都在这里。公式引擎这块我要多说一句,它是自己实现的,不依赖第三方库,支持大部分常用函数。这意味着 Agent 生成公式的时候,不用担心兼容性问题,而且公式的计算结果是可以被程序读取的——这对 Agent 做“自检”特别有用。

@univerjs/sheets-formula单独把公式能力抽出来了,这个设计我觉得挺聪明。因为公式计算是 CPU 密集型的,可以放到 Web Worker 里跑,不阻塞主线程。Agent 场景下,如果一次要算几千个单元格的公式,这个隔离就很重要。

@univerjs/sheets-ui和@univerjs/ui负责界面,包括工具栏、右键菜单、单元格编辑器这些。如果你只是想在 Node.js 里跑 Univer 做服务端计算,这两个可以完全不引入,包体积能省一大截。

@univerjs/sheets-collaboration是协同模块,基于 OT(Operational Transformation)算法。这块是 Univer 相对成熟的部分,多人同时编辑同一个单元格的冲突处理做得比较稳。Agent 场景下,如果多个 Agent 实例同时操作一份表格,这个模块就是刚需。

2.3 Node.js 环境下的运行方式

热词里“node.js”出现频率很高,我猜很多人关心的是:Univer 能不能在服务端跑?答案是能,而且这是它区别于纯前端表格组件的关键优势。

在 Node.js 里跑 Univer,核心是不引入 UI 相关的包,只引入 core 和 sheets。大概的初始化代码是这样:

import { Univer, LocaleType } from '@univerjs/core'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverSheetsPlugin } from '@univerjs/sheets'; const univer = new Univer({ locale: LocaleType.ZH_CN, }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverFormulaEnginePlugin); const workbook = univer.createUniverSheet({ id: 'agent-workbook', sheetContainer: [], });

这段代码跑起来之后,你就有了一个纯内存的表格实例,可以往里塞数据、算公式、读结果,全程不需要浏览器环境。我实测下来,一个空的 Univer 实例在 Node.js 里占用的内存大概在 20-30MB 左右,具体取决于你注册了多少插件。

这里有个坑要注意:Univer 的某些插件依赖浏览器 API(比如window、document),在 Node.js 里直接 import 会报错。解决办法是用动态 import 按需加载,或者用jsdom之类的库做 shim。我自己的做法是,服务端只注册 core、sheets、formula 这三个,其他一律不碰,这样最干净。

2.4 与 AI Agent 的集成切入点

Univer 给 Agent 留的集成点,我总结下来有三个层次。

最浅的一层是“生成即导出”。Agent 生成表格数据,通过 Univer 的 API 写入,然后导出成 xlsx 给用户下载。这种用法最简单,但没发挥出运行时的价值。

中间一层是“生成即交互”。Agent 生成一个带公式和格式的表格,用户在线打开,可以改数据,公式自动重算。Agent 通过监听变更事件,知道用户改了什么,可以给出反馈。这种用法已经能覆盖大部分报表场景了。

最深的一层是“Agent 作为协作者”。Agent 和用户同时在线,Agent 负责填一部分单元格,用户负责填另一部分,权限隔离,实时同步。这种用法就是热词里说的“用户定义表格,让用户填一些单元格,其他单元格无法修改”的完整形态。

我后面会重点讲第二层和第三层的实现,因为这两层才是 Univer 真正区别于其他方案的地方。

3. 权限控制与单元格锁定实操

3.1 权限模型的设计思路

先讲权限,因为这是热词里最具体的一个需求,也是很多 Agent 产品落地时的刚需。

Univer 的权限模型是分层的,我画个表说明:

权限层级控制对象典型场景
工作簿级整个文件只读分享、禁止下载
工作表级单个 sheet隐藏敏感 sheet
区域级单元格范围锁定公式区、开放录入区
单元格级单个单元格精细控制

Agent 场景下最常用的是区域级权限。比如 Agent 生成一个预算表,表头行和合计行是锁定的,中间的明细行开放给用户填。这个用 Univer 的setRangePermission就能实现。

3.2 锁定公式区、开放录入区的完整代码

我直接上一段我实际项目里用过的代码,场景是:Agent 生成一个销售报表,A1:C1 是标题(锁定),A2:C2 是表头(锁定),A3:C10 是数据区(开放),C11 是合计公式(锁定)。

const fWorkbook = univerAPI.getActiveWorkbook(); const fWorksheet = fWorkbook.getActiveSheet(); // 先设置整个 sheet 为可编辑 fWorksheet.setSheetPermission({ edit: true }); // 锁定标题行 const titleRange = fWorksheet.getRange('A1:C1'); titleRange.setRangePermission({ edit: false, copy: true, paste: false, }); // 锁定表头行 const headerRange = fWorksheet.getRange('A2:C2'); headerRange.setRangePermission({ edit: false, copy: true, paste: false, }); // 锁定合计行 const totalRange = fWorksheet.getRange('C11'); totalRange.setRangePermission({ edit: false, copy: true, paste: false, }); // 数据区保持可编辑(默认就是可编辑,这里显式声明一下) const dataRange = fWorksheet.getRange('A3:C10'); dataRange.setRangePermission({ edit: true, copy: true, paste: true, });

这段代码跑完之后,用户在界面上点标题行,会发现根本进不了编辑态;点数据区,正常编辑。这就是“用户定义表格,让用户填一些单元格,其他单元格无法修改”的实现。

3.3 权限与 Agent 的联动:让 Agent 知道“谁改了什么”

光锁定还不够,Agent 需要知道用户改了什么,才能做后续处理。Univer 提供了事件监听机制,我一般会监听RangeValueChanged事件:

univerAPI.getActiveWorkbook().onCommandExecuted((command) => { if (command.id === 'sheet.command.set-range-values') { const { range, value } = command.params; console.log('用户修改了区域:', range); console.log('新值:', value); // 这里可以触发 Agent 的后续逻辑 // 比如:检查数据是否合法、重算某个指标、发送通知 handleUserEdit(range, value); } });

这个监听机制是 Agent 感知用户行为的核心。我踩过的一个坑是:事件触发频率很高,用户连续输入的时候会疯狂触发。解决办法是做防抖,比如 500ms 内的连续修改合并成一次处理。这个在 Agent 场景下尤其重要,因为每次触发都可能意味着一次 LLM 调用,成本扛不住。

提示:权限设置和事件监听要配合使用。如果只锁权限不监听,Agent 就是“瞎子”;如果只监听不锁权限,用户可能把公式改坏,Agent 拿到脏数据。

3.4 权限控制的常见坑与规避

我整理了几个实际踩过的坑:

坑一:权限设置顺序问题。如果你先锁了整个 sheet,再想开放某个区域,需要显式设置该区域为可编辑,否则继承的是 sheet 级的锁定。这个逻辑跟 CSS 的继承有点像,子级不设置就继承父级。

坑二:复制粘贴绕过权限。默认情况下,用户虽然不能编辑锁定单元格,但可以从别处复制内容粘贴进去。解决办法是在setRangePermission里把paste设为false。

坑三:公式引用被锁区域。如果用户的数据区公式引用了锁定的合计行,而合计行又被锁了,公式重算会不会出问题?实测下来不会,因为公式计算走的是数据层,不受权限层影响。权限只控制“用户能不能手动改”,不控制“程序能不能算”。

坑四:协同场景下的权限同步。多人协作时,权限变更需要广播给所有客户端。Univer 的协同模块会自动处理这个,但如果你自己实现了权限逻辑,记得把权限变更也纳入协同范围。

4. 并发处理与服务端 Agent 架构

4.1 Agent 并发为什么是个真问题

热词里“ai agent 怎么扛并发”这个问题,在 Office 场景下会被放大。原因很简单:表格是有状态的,而且状态还不小。

假设你的产品是一个“AI 报表助手”,每个用户会话都需要一个独立的表格实例来承载他的数据。如果同时有 1000 个用户在线,你就有 1000 个表格实例。每个实例按 30MB 算,就是 30GB 内存。这个数字对大多数团队来说是扛不住的。

所以并发问题的本质,是如何在有限资源下管理大量有状态的表格实例。我总结了几种策略,从简单到复杂。

4.2 策略一:实例池化与懒加载

最简单的做法是实例池。维护一个 Univer 实例池,用户请求来了就从池里取一个,用完还回去。但这里有个问题:表格实例是有数据的,还回去之前得清空,清空本身也有成本。

我的做法是分级池化。把实例分成“热实例”和“冷实例”。热实例常驻内存,处理活跃会话;冷实例序列化成 JSON 存到 Redis 或数据库,用户下次访问时再反序列化回来。

// 伪代码示意 class UniverPool { constructor(maxHot = 50) { this.hotPool = []; this.maxHot = maxHot; } async acquire(sessionId) { // 先从热池找 let instance = this.hotPool.find(i => i.sessionId === sessionId); if (instance) return instance; // 热池满了,把最久未用的序列化出去 if (this.hotPool.length >= this.maxHot) { const lru = this.hotPool.shift(); await this.serializeToRedis(lru); } // 从 Redis 恢复或新建 const snapshot = await this.loadFromRedis(sessionId); instance = snapshot ? this.deserialize(snapshot) : this.createNew(); this.hotPool.push(instance); return instance; } }

这个策略的关键参数是maxHot,需要根据你的服务器内存和单实例占用来算。我的经验值是:单实例 30MB,服务器留 50% 内存给系统和其他服务,剩下的除以 30MB 就是 maxHot 的上限。比如 8GB 内存的机器,maxHot 设 100 左右比较稳。

4.3 策略二:计算与渲染分离

Univer 的一个优势是,计算层和渲染层是分开的。这意味着你可以在服务端只跑计算层,把渲染交给客户端。

具体做法是:服务端用 Node.js 跑 Univer 的 core + sheets + formula,负责数据存储、公式计算、权限校验;客户端用浏览器跑完整的 Univer,负责展示和交互。两边通过 WebSocket 同步数据变更。

这样做的好处是,服务端的实例可以做得更轻。我实测下来,纯计算层的实例内存占用能降到 10MB 左右,是完整实例的三分之一。而且计算层不需要渲染,CPU 占用也低很多。

坏处是架构复杂度上去了,需要自己实现一套同步协议。如果你的团队没有协同编辑的经验,这块会比较痛苦。

4.4 策略三:无状态化与快照

最激进的策略是彻底无状态化。每次请求都从数据库读快照,创建一个临时实例,处理完就销毁。这种模式下,并发能力取决于数据库的读取速度和实例创建速度。

我测过 Univer 实例的创建速度,一个空的 workbook 大概 50ms 左右,加载 1000 行数据大概 200ms。如果 QPS 是 100,那每秒要创建 100 个实例,CPU 会吃不消。

所以无状态化适合低频场景,比如“用户点一下生成报表”这种。高频交互场景还是得用池化。

4.5 并发场景下的 Agent 调用优化

除了实例管理,Agent 本身的调用也要优化。我的经验是:

批量合并。用户连续修改多个单元格,不要每次都调 Agent,攒一批再调。我一般设 1 秒的窗口,窗口内的修改合并成一次 Agent 请求。

结果缓存。同样的输入,Agent 的输出应该缓存起来。比如用户把 A1 从 100 改成 200,Agent 算出一个结果;用户又改回 100,Agent 应该直接返回缓存的结果,不用重新算。

异步化。Agent 调用是慢操作,不能阻塞表格的交互。我的做法是把 Agent 调用放到消息队列里,表格先响应用户操作,Agent 结果算出来了再通过事件推回去。

注意:异步化会带来一致性问题。用户可能看到表格已经改了,但 Agent 的反馈还没到。这个要在 UI 上做 loading 状态,让用户知道“后台在算”。

5. 从零搭建一个 Agent 表格助手

5.1 环境准备与依赖安装

我按从零开始的顺序讲一遍。假设你已经装好了 Node.js(建议 18 以上,热词里提到的 22.12+ 也可以),先建项目:

mkdir univer-agent-demo cd univer-agent-demo npm init -y

然后装依赖。服务端只需要核心包:

npm install @univerjs/core @univerjs/sheets @univerjs/engine-formula

如果你还要在浏览器里展示,再加 UI 相关的:

npm install @univerjs/sheets-ui @univerjs/ui @univerjs/design

这里有个版本坑要注意:Univer 的包版本要一致,不能 core 用 0.1.x 而 sheets 用 0.2.x,会报类型不匹配。我一般用npm install @univerjs/core@latest统一装最新版,或者锁定同一个版本号。

5.2 服务端初始化与数据写入

服务端初始化的代码我前面给过,这里补充数据写入的部分。假设 Agent 生成了一批销售数据,要写进表格:

const sheet = workbook.getActiveSheet(); // 写入表头 sheet.getRange('A1:C1').setValues([['产品', '销量', '单价']]); // 写入数据 const data = [ ['产品A', 100, 25.5], ['产品B', 200, 18.0], ['产品C', 150, 32.0], ]; sheet.getRange('A2:C4').setValues(data); // 写入公式 sheet.getRange('D2').setFormula('=B2*C2'); sheet.getRange('D3').setFormula('=B3*C3'); sheet.getRange('D4').setFormula('=B4*C4'); // 写入合计 sheet.getRange('D5').setFormula('=SUM(D2:D4)');

写完之后,你可以通过getValue读回计算结果:

const total = sheet.getRange('D5').getValue(); console.log('合计:', total); // 应该输出 25.5*100 + 18*200 + 32*150 = 10950

这个“写入公式、读回结果”的能力,是 Agent 做自检的基础。Agent 生成公式后,可以自己读一遍结果,判断是否符合预期,不符合就重试。

5.3 权限配置与用户交互

权限配置我前面详细讲过,这里补充一个完整的初始化流程:

function setupAgentSheet(univerAPI) { const workbook = univerAPI.getActiveWorkbook(); const sheet = workbook.getActiveSheet(); // 1. 写入 Agent 生成的内容 sheet.getRange('A1:C1').setValues([['产品', '销量', '单价']]); sheet.getRange('A2:C4').setValues([ ['产品A', 100, 25.5], ['产品B', 200, 18.0], ['产品C', 150, 32.0], ]); sheet.getRange('D2:D4').setFormulas([['=B2*C2'], ['=B3*C3'], ['=B4*C4']]); sheet.getRange('D5').setFormula('=SUM(D2:D4)'); // 2. 设置权限 sheet.getRange('A1:D1').setRangePermission({ edit: false }); sheet.getRange('D2:D5').setRangePermission({ edit: false }); sheet.getRange('A2:C4').setRangePermission({ edit: true }); // 3. 监听用户修改 workbook.onCommandExecuted((command) => { if (command.id === 'sheet.command.set-range-values') { debouncedHandleEdit(command.params); } }); }

这段代码跑起来,用户看到的就是一个“表头和公式锁死、数据区可填”的表格。用户改了销量或单价,D 列的公式会自动重算,Agent 也能通过事件知道改了什么。

5.4 与 LLM 对接的完整链路

最后把 Agent 接进来。我用一个简化的例子说明链路:

async function handleUserEdit(params) { const { range, value } = params; // 1. 读取当前表格的完整状态 const sheet = univerAPI.getActiveWorkbook().getActiveSheet(); const snapshot = sheet.getSnapshot(); // 2. 构造 prompt 给 LLM const prompt = ` 当前表格数据:${JSON.stringify(snapshot)} 用户刚刚修改了 ${range},新值是 ${value}。 请分析这个修改是否合理,如果不合理给出建议。 `; // 3. 调用 LLM const response = await callLLM(prompt); // 4. 根据 LLM 的反馈,决定是否要修改表格 if (response.shouldUpdate) { sheet.getRange(response.targetRange).setValue(response.newValue); } // 5. 给用户提示 showNotification(response.message); }

这个链路的关键是快照的粒度。全量快照数据量大,LLM 的 token 消耗高;增量快照信息少,LLM 可能判断不准。我的经验是,对于小表格(100 行以内)用全量,大表格用“修改区域 + 上下文若干行”的增量。

5.5 一个完整的实操案例:预算填报助手

我把上面所有东西串起来,讲一个完整的案例。场景是:财务部门要填一份季度预算表,Agent 负责生成模板、校验数据、汇总结果。

第一步,Agent 生成模板。表头是“部门、Q1预算、Q2预算、Q3预算、Q4预算、合计”,合计列是公式。

第二步,设置权限。表头和合计列锁定,四个季度的预算列开放。

第三步,用户填报。用户在各个季度列填数,合计列自动算。

第四步,Agent 校验。用户每填一个数,Agent 检查是否超过部门上限、是否与历史数据偏差过大。如果异常,在单元格上加批注提示。

第五步,汇总。所有部门填完后,Agent 生成一份汇总表,按部门、按季度汇总。

这个案例里,Univer 承担的是“模板载体 + 权限控制 + 公式计算 + 事件通知”四个角色,Agent 承担的是“生成 + 校验 + 汇总”三个角色。两边通过事件和 API 解耦,各干各的。

6. 常见问题与排查实录

6.1 初始化相关的坑

问题:Node.js 里 import Univer 报window is not defined。

原因是有插件依赖浏览器 API。解决办法是只 import 核心包,或者用global.window = {}做 shim。我推荐前者,干净。

问题:公式计算结果不对。

先检查公式引擎插件有没有注册。Univer 的公式计算是独立插件,不注册的话公式就是个字符串,不会算。注册代码是univer.registerPlugin(UniverFormulaEnginePlugin)。

问题:中文乱码。

初始化的时候要指定 locale:new Univer({ locale: LocaleType.ZH_CN })。不指定的话默认是英文,中文可能显示异常。

6.2 权限相关的坑

问题:设置了权限但用户还是能编辑。

检查权限设置的顺序。如果先设了 sheet 级权限,再设 range 级权限,range 级会覆盖 sheet 级。反过来则不行。所以要先设 sheet 级,再设 range 级。

问题:协同场景下权限不同步。

Univer 的协同模块默认会同步权限变更,但如果你用的是自定义的协同实现,需要手动把权限变更纳入同步范围。

问题:权限设置后公式不重算。

权限只影响用户手动编辑,不影响程序计算。如果公式不重算,检查是不是公式引擎的问题,跟权限无关。

6.3 并发相关的坑

问题:实例池化后数据串了。

典型的池化 bug。从池里取实例时,一定要先清空数据,或者用 sessionId 做隔离。我推荐后者,每个 session 一个独立实例,用完销毁,不要复用。

问题:内存泄漏。

Univer 实例销毁时要调用univer.dispose(),否则事件监听器不会释放。我踩过这个坑,跑了一天内存涨到 4GB。

问题:Agent 调用把服务打挂。

加限流。我的做法是用令牌桶,每个用户每秒最多 1 次 Agent 调用,超过就排队。同时设一个全局上限,比如每秒 100 次,超过直接拒绝。

6.4 常见问题速查表

现象可能原因排查方向
import 报错依赖浏览器 API只 import 核心包
公式不算公式引擎未注册检查插件注册
中文乱码locale 未设置初始化时指定 ZH_CN
权限失效设置顺序错误先 sheet 后 range
内存泄漏实例未销毁调用 dispose()
Agent 超时未做限流加令牌桶
数据串号实例复用未清空用 sessionId 隔离

6.5 我个人的几条经验

第一,不要一上来就上协同。协同的复杂度很高,如果你的场景是单用户填报,先把单机版跑通,再考虑协同。

第二,公式能算的就别让 LLM 算。LLM 算数不靠谱,公式引擎算数靠谱。让 LLM 生成公式,让 Univer 算结果,这是最稳的分工。

第三,权限要前置设计。不要等表格做完了再想权限,要在设计表格结构的时候就规划好哪些锁定、哪些开放。

第四,快照要控制大小。给 LLM 的快照不要超过 2000 token,超了就截断或者摘要。我一般只传修改区域前后各 10 行。

第五,测试要覆盖边界。空表格、满表格、公式循环引用、权限冲突,这些边界情况都要测。我吃过亏,上线后用户填了个空值,公式直接报错。

这套东西我陆陆续续做了大半年,从最早的“用 openpyxl 硬扛”到现在的“Univer + Agent”,中间换过三次方案。Univer 不是完美的,它的文档还在完善,某些 API 会变,社区也不算大。但它在“Agent 友好的 Office 运行时”这个定位上,目前我没找到更好的替代品。如果你也在做类似的东西,建议先从服务端计算这个最简单的场景入手,跑通了再往上加交互和协同,这样踩的坑会少很多。

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

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

立即咨询