让 Agent 在浏览器中交付可用文件
文章建议让模型输出符合既定 schema 的结构化数据,再由浏览器代码生成文档、表格或演示文件。该方案减少服务端渲染设施,也避免为导出而将内容再次上传到服务器。
AI 深度解读
Agent 写完报告,用户往往还得追问一句:文件呢?能在聊天框里读到结论,和拿到可排序的 Excel、可修改的 Word、能直接演示的 PowerPoint,是两回事。
这篇文章给出了一条具体的实现路线:让模型生成结构化数据,再由浏览器里的代码导出文件。它值得前端开发者读的地方,是把 AI 内容生成和文件交付拆成了两个可独立维护的环节,也交代了字体、数据类型和加载体积这些容易漏掉的问题。
为什么模型只该交数据,不该直接写文件
作者最重要的建议是:不要让模型直接生成文件格式,而是让它返回符合约定结构的 JSON。
模型负责组织内容,程序负责把内容变成文件。
原文用下面的数据结构承接销售周报,让标题、段落和表格都有明确的位置:
{
"title": "Weekly Sales Report",
"sections": [
{ "heading": "Summary", "paragraphs": ["Revenue rose 8% week on week."] },
{ "heading": "Stalled deals", "table": {
"columns": ["Deal", "Owner", "Days stalled"],
"rows": [["Acme renewal", "Sam", 12], ["Globex pilot", "Priya", 9]]
}}
]
}
要留意 rows 中的 12 和 9:它们是数字,不是带单位的字符串。
这套设计的好处,是 Word、PDF、Excel、PowerPoint 可以共用一份内容结构,各自实现渲染函数,不必维护四套生成提示词。模型偶尔返回不符合约定的内容,也可以先用结构校验拦住,避免错误一路进入文件。
不过,原文只提出了校验原则,没有提供 schema 定义和失败处理。落到项目里,还得明确哪些字段必填、哪些类型允许出现,以及校验失败后如何重试。结构正确也不代表内容正确,报告里的收入是否真的增长了 8%,仍要靠数据来源支撑。
四种导出格式,分别要处理什么
Word 使用 docx。作者把标题和段落映射为 Paragraph,通过 HeadingLevel.TITLE、HeadingLevel.HEADING_1 设置层级,再用 Document 组织文档,最后调用 Packer.toBlob。表格也能生成,但原文示例没有实现表格映射。
Excel 使用 SheetJS,导出流程更短。下面这段代码解决的是把列名和二维数组装进工作簿:
import * as XLSX from "xlsx";
function exportExcel(table, fileName) {
const ws = XLSX.utils.aoa_to_sheet([table.columns, ...table.rows]);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Data");
XLSX.writeFile(wb, fileName);
}
关键是 aoa_to_sheet 接收到的数据类型。模型返回 12,用户才能按数值排序;返回 "12 days",得到的就是文本。文件能打开,还远没到交付完成的程度。
作者还提醒,SheetJS 应按项目文档从其自有 CDN 安装,并称公共 npm 仓库里的副本已落后数年。这是原文给出的依赖提醒,不能据此推定所有同名包当前都是什么状态。
PowerPoint 使用 PptxGenJS,通过 addSlide、addText 和 writeFile 生成演示文稿。作者建议一节内容对应一页幻灯片,并限制文字量。代码里的固定坐标只能完成基础排版,长内容是否溢出、表格如何摆放,仍需要额外规则。
PDF 使用 jsPDF,简单报告可以通过 text 写入内容,长行用 pdf.splitTextToSize(text, width) 处理。多语言是明确的风险点:内置字体不覆盖日文、阿拉伯文等文字,需要嵌入支持相应字符的字体。面向中文用户时,也应在导出前验证字体覆盖情况。
浏览器生成省了服务,但还要管下载和体积
作者选择浏览器,主要看中隐私、成本和流程简单:导出时不用把报告再交给文件生成服务,也不用维护队列、存储桶和文件就绪通知。
原文给出的通用下载方式是把文件交给 Blob,调用 URL.createObjectURL 生成临时地址,设置链接的 download 属性,再触发点击。下载触发后等待 1000 毫秒,调用 URL.revokeObjectURL 释放地址占用的资源,避免立即回收与下载过程冲突。
不过,示例中的 SheetJS、PptxGenJS 和 jsPDF 都调用了各自的保存接口,并没有全部接入这个下载函数。它是一种可复用方案,不是四段代码已经完成的统一实现。
另一个实际问题是体积。作者明确提醒,不要首屏加载全部四个库,而是在用户点击导出按钮后,通过 await import("./exporters/word.js") 加载对应模块,再执行 exportWord(currentReport)。
这把下载导出代码的时机推迟到了真正需要的时候。代价也很直接:首次点击需要等待模块加载,界面应给出可感知的处理状态。
我的判断
我认同把内容结构与文件渲染分开。和为每种格式单独写提示词相比,这种方式更方便控制字段、复用数据,也更容易定位错误到底来自内容还是排版。
但「四个小渲染函数」适用于基础导出。企业模板、复杂分页、长表格和幻灯片溢出,都会让实现继续增长。作者自己也保留了服务端渲染的适用范围,包括超大文件、严格的企业模板和定时报表。
隐私结论也要划清范围:浏览器生成可以减少导出环节的数据副本,不能证明整条 Agent 链路里的数据都没离开设备。报告此前在哪里生成、是否进入日志,原文没有说清。
作者称其所在的 Magnus 已支持四种文件导出,但没有给出文件规模、耗时、内存占用或服务端对照测试。因此,这是一套有产品背景的工程建议,性能和成本收益仍属于经验判断。
能拿来做什么
- 给 AI 报告功能先定义共享 JSON 结构,区分标题、段落、表格和数字字段,校验通过后再允许导出。
- 为前端导出模块按格式拆分文件,用动态
import()加载用户选择的格式,并处理加载中和导出失败状态。 - 验收 Excel 时检查数字排序,验收 PDF 时检查中文字体,验收 PowerPoint 时检查长内容溢出,把文件里的实际操作纳入验收。
- 遇到定时报表、超大文件或严格企业模板时,单独评估服务端生成,不必强行沿用浏览器方案。